如何使用卸载路径卸载 MSI

How to uninstall MSIs using the Uninstall Path

我正在尝试获取一组应用程序的卸载路径并卸载它们。到目前为止,我得到了卸载路径列表。但我正在努力卸载程序。

到目前为止我的代码是。


    $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
    }

这将 return 以下结果:


    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}

我正在努力寻找执行这些卸载路径并实际卸载 MSI 的方法。

我曾尝试使用 Invoke-Expression $UninstallPath.UninstallString,但它只显示 windows 安装程序并为我提供了 msiexec 选项。

我也尝试过使用 start-process "msiexec.exe" -arg "X $UnistallPath /qb" - wait 但这会产生同样的问题。

注:

  • 这个答案解决了所问的问题。
  • shows a much more convenient alternative that avoids the original problem, via the PackageManagement 模块的 Get-PackageUninstall-Package cmdlet。但是请注意,这些 cmdlet 仅支持已安装的 applications,并且仅在 Windows PowerShell 版本中 - 相比之下,PowerShell (Core) 从 v7.1 开始完全缺少相关的包提供程序,[1] 并且(对我而言)不清楚它们是否会被添加。

问题:

  • 存储在UninstallString/QuietUninstallString注册表值[2]中的卸载命令行是设计的对于 no-shell / from-cmd.exe 调用.

  • 因此,如果您将它们传递给 Invoke-Expression,它们 可以从 PowerShell 失败 ,即如果它们包含 unquoted 字符,这些字符在 shells / 到 cmd.exe 之外没有特殊意义,但在 PowerShell 中是 metacharacters,这在您的情况下适用于 {}

解决方案:

你有两个选择:

  • (a) 只需将卸载字符串原样传递给 cmd /c

    • 请注意 - 与直接从 PowerShell 或直接从 cmd.exe 调用 msiexec.exe 不同 - 通过 cmd /c 调用会导致 同步 执行msiexec,这是可取的。
  • (b) 将卸载字符串拆分为可执行文件和参数列表,这样就可以通过[=44=调用命令],这可以让您更好地控制调用。

    • 请务必使用 -Wait 开关以确保安装在您的脚本继续之前完成。

注意:以下命令假定卸载字符串包含在变量 $UninstallString 中(相当于代码中的 $UninstallPath.UninstallString):

(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

然后可以在 automatic $LASTEXITCODE variable 中查询命令行的 退出代码

(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

您还可以添加 -NoNewWindow 以防止 console 基于程序的卸载命令行来自 运行 在新控制台 window 中,但是请注意,通过 Start-Process 捕获其 stdout / stderr 输出的唯一方法是使用 -RedirectStandardOutput / -RedirectStandardError 参数将它们重定向到 files


特定版本/未来改进:

基于Start-Process的方法很麻烦,原因有二:

  • 您不能传递整个命令行,而必须分别指定可执行文件和参数。

  • Windows PowerShell(最新和final版本是5.1)你不能通过一个 字符串或数组到(位置隐含的)-ArgumentList 参数(因此需要上面的两个单独调用)。

    • 此问题已在跨平台、按需安装 PowerShell (Core) 版本(版本 6 及更高版本)中修复。

[1] 如果您不介意额外开销,您可以(暂时)导入 Windows PowerShell PackageManagement 模块,甚至可以从 PowerShell(核心)导入,使用Windows PowerShell compatibility feature:
Import-Module -UseWindowsPowerShell PackageManagement.

[2] 如您的问题所示,它们存储在 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall(64 位应用程序)和 HKEY_LOCAL_MACHINE\Wow6432Node \Software\Microsoft\Windows\CurrentVersion\Uninstall(32 位应用程序)注册表项中。

或者例如:

get-package *chrome* | uninstall-package