简体   繁体   中英

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.

Note:

  • This answer addresses the question as asked.
  • js2010's helpful answer shows a much more convenient alternative that avoids the original problem, via the PackageManagement module's Get-Package and Uninstall-Package cmdlets. Note, however, that these cmdlets only support installed applications in the Windows PowerShell edition - by contrast, PowerShell (Core) as of v7.1 lacks the relevant package providers, [1] and it's unclear (to me) whether they will ever be added.

Problem:

  • The uninstallation command lines stored in the UninstallString / QuietUninstallString registry values [2] are designed for no-shell / from- cmd.exe invocations .

  • They therefore can fail from PowerShell if you pass them to Invoke-Expression , namely if they contain unquoted characters that have no special meaning outside shells / to cmd.exe , but are metacharacters in PowerShell, which applies to { and } in your case.

Solutions :

You have two options:

  • (a) Simply pass the uninstallation string as-is to cmd /c

    • Note that - unlike when you call msiexec.exe directly from PowerShell or directly from cmd.exe - calling via cmd /c results in synchronous execution of msiexec , which is desirable.
  • (b) Split the uninstallation string into executable and argument list, which allows you to call the command via Start-Process

    • This allows you to use the -Wait switch to ensure that the installation completes before your script continues.

Note: The following commands assume that the uninstall string is contained in variable $UninstallString (the equivalent of $UninstallPath.UninstallString in your code):

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:

  • You cannot pass whole command lines and must instead specify the executable and arguments separately.

  • In Windows PowerShell (whose latest and final version is 5.1) you cannot pass an empty string or array to the (positionally implied) -ArgumentList parameter (hence the need for two separate calls above).

    • This problem has been fixed in the cross-platform, install-on-demand PowerShell (Core) edition (versions 6 and above).

[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.

或者例如:

get-package *chrome* | uninstall-package

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM