如何使用卸载路径卸载 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 中的 $apps){$UninstallPath = Get-ItemProperty $Regpath |其中 {$_.displayname -like "*$apps*"} |Select-Object -Property UninstallString$UninstallPath.UninstallString#Invoke-Expression UninstallPath.UninstallString#start-process msiexec.exe"-arg "X $UnistallPath/qb";- 等待}这将返回以下结果:
<代码>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";- 等待
但是这会产生同样的问题.
注意:
- 这个答案解决了所提出的问题.
- js2010 的有用答案 显示了一种更方便的替代方案,可以通过
PackageManagement
模块的获取-Package
和Uninstall-Package
cmdlet.但请注意,这些 cmdlet 仅支持 Windows PowerShell 版本中已安装的应用程序 - 相比之下,PowerShell (Core) 自 v7.1 起缺少相关的包提供者,[1],并且(对我而言)他们是否会被添加还不清楚.
问题:
存储在
UninstallString
/QuietUninstallString
注册表值[2] 中的卸载命令行专为 no-shell/from-cmd.exe
调用.因此,如果您将它们传递给
Invoke-Expression
,即如果它们包含没有特殊含义的未加引号字符外部 shell/到cmd.exe
,但在 PowerShell 中是 元字符,这适用于{
和}
案例.
解决方案:
您有两个选择:
(a) 只需将卸载字符串按原样传递给
cmd/c
- 请注意,基于
msiexec
的卸载命令将异步执行,因为msiexec.exe
不是控制台 应用程序.
- 请注意,基于
(b) 将卸载字符串拆分为可执行文件和参数列表,这允许您通过
Start-Process
- 这允许您添加使用
-Wait
开关以确保在脚本继续之前安装完成.
- 这允许您添加使用
注意:以下命令假定卸载字符串包含在变量 $UninstallString
中(相当于代码中的 $UninstallPath.UninstallString
):
(a) 的实施:
# 只需将卸载字符串(命令行)传递给 cmd.exe# 通过`cmd/c`.cmd/c $卸载字符串$exitCode = $LASTEXITCODE
自动然后可以查询 $LASTEXITCODE
变量 以获取命令行的退出代码,但请注意,使用 异步 执行诸如 msiexec
这只会告诉你可执行文件是否启动成功;要了解最终结果,您需要同步执行 - 见下文.
(b) 的实施:
# 将命令行拆分为可执行文件和参数列表.# 考虑到可执行文件名称可能被双引号引起来的事实.如果 ($UninstallString[0] -eq '"') {$unused, $exe, $argList = $UninstallString -split '"', 3}别的 {$exe, $argList = $UninstallString -split ' ', 2}# 使用 Start-Process 和 -Wait 等待命令完成.# -PassThru 返回一个代表启动进程的对象,# 然后可以查询其 .ExitCode 属性.$ps = if ($argList) {Start-Process -Wait -PassThru $exe $argList} 别的 {Start-Process -Wait -PassThru $exe}$exitCode = $ps.ExitCode
您还可以添加 -NoNewWindow
以防止 console 基于程序的卸载命令行在新的控制台窗口中运行,但请注意,这是捕获其标准输出的唯一方法/stderr 输出通过 Start-Process
是将它们重定向到 files,使用 -RedirectStandardOutput
/-RedirectStandardError
参数.
特定于版本/未来的改进:
基于 Start-Process
的方法很麻烦,原因有两个:
您不能传递整个命令行,而必须分别指定可执行文件和参数.
- GitHub 提案 #14347 旨在增加对传递整个命令行的支持.
在 Windows PowerShell(最新和 最终 版本为 5.1)中,您不能将 空 字符串或数组传递给 (位置隐含)
-ArgumentList
参数(因此需要上述两个单独的调用).- 此问题已在跨平台、按需安装中修复PowerShell (Core) 版本(版本 6 及更高版本).
[1] 如果您不介意额外的开销,您甚至可以(暂时)使用 PackageManagement 模块"https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Windows_PowerShell_Compatibility" rel="nofollow noreferrer">Windows PowerShell 兼容性功能:Import-Module -UseWindowsPowerShell PackageManagement
.
[2] 如您的问题所示,它们存储在 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
(64 位应用程序)和 HKEY_LOCAL_MACHINE\Wow6432Node \Software\Microsoft\Windows\CurrentVersion\Uninstall
(32 位应用程序)注册表项.
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'sGet-Package
andUninstall-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 / tocmd.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 a
msiexec
-based uninstallation command will then execute asynchronously, becausemsiexec.exe
isn't a console application.
- Note that a
(b) Split the uninstallation string into executable and argument list, which allows you to call the command via
Start-Process
- This allows you to add use the
-Wait
switch to ensure that the installation completes before your script continues.
- This allows you to add use the
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`.
cmd /c $UninstallString
$exitCode = $LASTEXITCODE
The automatic $LASTEXITCODE
variable can then be queried for the command line's exit code, but note that with asynchronously executing executables such as msiexec
this will only tell you whether the executable was launched successfully; to learn the ultimate outcome, you need synchronous execution - see below.
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.
- GitHub proposal #14347 aims to add support for passing whole command lines.
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.
这篇关于如何使用卸载路径卸载 MSI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!