powershelluninstallationuninstallstring

How to uninstall MSIs using the Uninstall Path


I am trying to get the uninstall paths of a set of applications and uninstall them. So far i an get the list of uninstall paths. but i am struggling to actually uninstall the programs.

My code so far is.


    $app = @("msi1", "msi2", "msi3", "msi4")
     $Regpath = @(
                    'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
                    'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
                )
                   
    foreach ($apps in $app){
    $UninstallPath = Get-ItemProperty $Regpath | where {$_.displayname -like "*$apps*"} | Select-Object -Property UninstallString
    
    $UninstallPath.UninstallString
    #Invoke-Expression UninstallPath.UninstallString
    #start-process "msiexec.exe" -arg "X $UnistallPath /qb" - wait
    }

this will return the following results:


    MsiExec.exe /X{F17025FB-0401-47C9-9E34-267FBC619AAE}
    MsiExec.exe /X{20DC0ED0-EA01-44AB-A922-BD9932AC5F2C}
    MsiExec.exe /X{29376A2B-2D9A-43DB-A28D-EF5C02722AD9}
    MsiExec.exe /X{18C9B6D0-DCDC-44D8-9294-0ED24B080F0C}

Im struggling to find away to execute these uninstall paths and actually uninstall the MSIs.

I have tried to use Invoke-Expression $UninstallPath.UninstallString but it just displays the windows installer and gives me the option for msiexec.

I have also tried to use start-process "msiexec.exe" -arg "X $UnistallPath /qb" - wait however this gives the same issue.


Solution

  • Note:


    Problem:

    Solutions:

    You have two options:

    Note:

    Implementation of (a):

    # Simply pass the uninstallation string (command line) to cmd.exe
    # via `cmd /c`. 
    # Execution is synchronous (blocks until the command finishes).
    cmd /c $UninstallString
    
    $exitCode = $LASTEXITCODE
    

    The automatic $LASTEXITCODE variable can then be queried for the command line's exit code.

    Implementation of (b):

    # Split the command line into executable and argument list.
    # Account for the fact that the executable name may be double-quoted.
    if ($UninstallString[0] -eq '"') {
        $unused, $exe, $argList = $UninstallString -split '"', 3
    }
    else {
        $exe, $argList = $UninstallString -split ' ', 2
    }
    
    # Use Start-Process with -Wait to wait for the command to finish.
    # -PassThru returns an object representing the process launched,
    # whose .ExitCode property can then be queried.
    $ps = if ($argList) {
            Start-Process -Wait -PassThru $exe $argList
          } else {
            Start-Process -Wait -PassThru $exe 
          }
    $exitCode = $ps.ExitCode
    

    You could also add -NoNewWindow to prevent console program-based uninstallation command lines from running in a new console window, but note that the only way to capture their stdout / stderr output via Start-Process is to redirect them to files, using the -RedirectStandardOutput / -RedirectStandardError parameters.


    Edition-specific / future improvements:

    The Start-Process-based method is cumbersome for two reasons:


    [1] If you don't mind the extra overhead, you can (temporarily) import the Windows PowerShell PackageManagement module even from PowerShell (Core), using the Windows PowerShell compatibility feature:
    Import-Module -UseWindowsPowerShell PackageManagement.

    [2] As shown in your question, they are stored in the HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall (64-bit applications) and HKEY_LOCAL_MACHINE\Wow6432Node \Software\Microsoft\Windows\CurrentVersion\Uninstall (32-bit applications) registry keys, and possibly also - for user-specific installations - in their HKEY_CURRENT_USER counterparts; given that HKEY_CURRENT_USER only refers to the current user, looking for other users' user-specific installations would require more work.

    [3] Hypothetically, it is possible to author valid no-shell command lines that break when called from cmd.exe (e.g. foo.exe a&b or foo.exe "A \"B & C\ D"), but that rarely, if ever, happens in practice.