As a systems administrator, I am regularly tasked with removing applications from thousands of endpoints. No sane human would want to manually perform this task for even a few dozen computers, so we need to look into ways of automating this procedure.

I’ve seen countless posts online that say you can execute msiexec and feed it a bunch of GUIDs of the applications depending on the version that may or may not be installed on a computer. See below.

"C:\ProgramData\Package Cache\{ec40a028-983b-4213-af2c-77ed6f6fe1d5}\DellUpdateSupportAssistPlugin.exe" /uninstall /quiet
MsiExec.exe /qn /norestart /X{E98E94E2-12D1-48E5-AC69-2C312F466136}
MsiExec.exe /qn /norestart /X{806422F1-FC4E-4D7C-8855-05748AEFC031}
MsiExec.exe /X{0309AC01-330F-494C-B27D-58E297E4674F} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{122666A9-2995-4E47-A75E-6423A827B7AF} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{18EF001B-B005-46CB-917B-112BA69ED85E} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{1AE53ECE-2255-4191-998B-07741E5EFCDA} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{33E712C1-2183-421C-9BC8-C902DB9C596C} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{45FD01F4-B11B-4A58-B465-1D600B5CDF64} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{4CB4741A-20C1-454E-8276-993D06A76D67} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{50EF2C72-95EC-4206-AAC3-9E84004A6140} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{5A18ABE3-52D1-4CA5-9169-25EC7E789582} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{8D7B279C-A661-465C-9658-F62FBD6A6B91} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{9074E264-F615-4DDE-969E-1FDBCFEC3FB5} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{90881C8E-6C4F-4662-9923-85AFCA058C44} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{9DD6B149-CEBC-4910-B11A-242393EDF6D3} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{D793D5B1-A985-4443-90F4-E55A13CFF117} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{E98E94E2-12D1-48E5-AC69-2C312F466136} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{806422F1-FC4E-4D7C-8855-05748AEFC031} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{27130E51-9555-408B-8134-7BFF54EDE27B} /qn REBOOT=REALLYSUPRESS
MsiExec.exe /X{3ED468C2-2235-4747-90AD-A7A34F0FE70A} /qn REBOOT=REALLYSUPRESS

While this technically does work, it’s not the best approach in my opinion, and I think it should be avoided at all costs because it’s a lot of extra work. Imagine performing this for an application like Google Chrome that gets updated numerous times each week. The GUID is going to be different in most cases. This approach is about as bad as uninstalling it manually for each computer.


My Thought Process

For applications installed in the system context (the majority of app installs), there is almost always a registry key. This registry key contains an uninstall string that the system references when the user initiates an uninstall of the application. Uninstall strings typically appear as the following.

MsiExec.exe /X{5A18ABE3-52D1-4CA5-9169-25EC7E789582}

or

MsiExec.exe /I{5A18ABE3-52D1-4CA5-9169-25EC7E789582}

MsiExec.exe is the utility commonly used for installing and uninstall applications. /X means we want to uninstall. /I is to modify. It is followed by the GUID.

These uninstall strings are located in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall and HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall for 64-bit and 32-bit applications respectively.

Since the uninstall string can be /I, we will also need to make sure that it is /X. Additionally, we can append /qn to the end of the uninstalled string, which will perform all of this silently so that Sally in HR doesn’t panic and call the helpdesk when she sees her unlicensed installation of Microsoft Visio being uninstalled.


Finished Script

<#
.SYNOPSIS
    Uninstalls an application based on the provided application name.

.DESCRIPTION
    This script is used to uninstall an application from the Windows registry. It searches for the application name in the registry and retrieves the 
    uninstall string associated with it. The uninstall string is then modified to perform a silent uninstallation. Finally, the script executes the 
    uninstall string to uninstall the application.

.PARAMETER applicationName
    The name of the application to be uninstalled. If not provided, the default value is "Application Name".

.EXAMPLE
    PS C:\> .\script.ps1 -ApplicationName "Application Name"
    This command uninstalls the application with the name "Application Name" from the Windows registry.

.EXAMPLE
    PS C:\> .\script.ps1 -ApplicationName "VLC media player" -AdditionalParameter "/quiet"
    This command uninstalls the application with the name "VLC media player" from the Windows registry using the "/quiet" parameter.

.NOTES
    Author: Nicholas Tabb
    Date: 5/26/2024
    Version: 1.0.1 - Refactored for better readability.
#>

param (
    [Alias("AppName", "Name")][string]$ApplicationName = "Application Name",
    [string]$AdditionalParameter = ""
)

function Confirm-AdminRights {
    # Check if the script is running with administrative rights
    $currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    $adminRole = [Security.Principal.WindowsBuiltInRole]::Administrator

    if (-not $currentUser.IsInRole($adminRole)) {
        Write-Host "Please run this script with administrative rights."
        exit
    }
}

function Get-RegistryKey {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)][string]$ApplicationName
    )

    begin {
        $registryKeys = @(
            "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*",
            "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
        )

        $result = @()
    }

    process {
        foreach ($registryKey in $registryKeys) {
            $result += Get-ChildItem $registryKey | Get-ItemProperty | Where-Object { $_.DisplayName -eq $applicationName }
        }
    }

    end {
        return $result
    }
}

function Uninstall-Application {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)][System.Object]$RegistryKey
    )

    process {
        $uninstallString = $RegistryKey.UninstallString
        
        # if uninstallstring starts with msiexec, replace /I with /X and add /qn
        if ($uninstallString -match "msiexec") {
            $uninstallString = $uninstallString -replace "/I", "/X"
            $uninstallString += " /qn"
        }

        # add additional parameters if provided
        if ($AdditionalParameter -ne "") {
            $uninstallString += " $AdditionalParameter"
        }

        Write-Host "Uninstalling $($RegistryKey.DisplayName)..."
        & cmd /c $uninstallString

        # check if uninstallation was successful
        if ($LASTEXITCODE -eq 0) {
            Write-Host "$($RegistryKey.DisplayName) uninstalled successfully."
        }
        else {
            Write-Host "Failed to uninstall $($RegistryKey.DisplayName)."
        }
    }
}

function Main {
    # Check if the script is running with administrative rights
    Confirm-AdminRights

    $applicationName | Get-RegistryKey | Uninstall-Application
}

Main

You will find the finished product above. It incorporates all the topics that I had mentioned in the previous section. You can run the script using PowerShell in the terminal.

Parameters: applicationName

  • The name of the application to be uninstalled. If not provided, the default value is Application Name, which can be modified if it is easier to use in your environment.

For instance, you could run the following.

powershell -executionpolicy bypass -file .\script.ps1 -applicationName "VLC media player"

Feel free to review the code to get a better understanding of how it all works. To get the exact display name of the application, you’ll need to check the registry at the aforementioned keys. This will uninstall any application with that name regardless of the version, GUID, architecture, etc.

I’ve used very similar logic in our production environment countless times, and it works flawlessly each time. This can be used in something like an Intune Remediation, SCCM Configuration Baseline, and PDQ Deploy. Additionally, if PowerShell Remoting is enabled in your environment, that can be used as well.

If you experience any issues or would like further clarification on why I did something a certain way, feel free to reach out to me!