Powershell:通过UpgradeCode卸载应用程序

eufgjt7s  于 2023-02-16  发布在  Shell
关注(0)|答案(2)|浏览(242)

当我通过Powershell脚本升级/降级我的应用程序时,我希望在运行新的安装程序之前首先强制卸载当前安装的版本。
我如何使用Powershell,使用应用程序的UpgradeCode来实现这一点?
通过应用程序名称执行此操作可能不太健壮。

3pvhb19x

3pvhb19x1#

既然你提到了升级代码,那就意味着你在谈论一个MSI文件(Windows Installer)。正如其他人所说的那样,这样的卸载通常是由一个正确创作的MSI包自动执行的-它被称为major upgrade-它本质上是卸载现有版本的产品,然后安装最新版本。
正在安装的MSI的Upgrade Table将指定在安装新版本之前,盒子上的哪些现有软件包将被卸载。理论上,你可以卸载任何数量的现有安装。如果你疯了,你甚至可以卸载竞争产品。坦率地说,令人惊讶的是,我从来没有尝试过在一次重大升级过程中卸载多个产品-很少要求这样做。大多数情况下,你卸载一个现有的产品,然后安装你的最新版本。
1.您可以使用transform修改Upgrade表以更改主要升级的行为方式-换句话说,使其开始或停止卸载特定的预先存在的安装。
1.您还可以通过调用以下MSI API函数(COM - VBScript用作示例)来枚举共享相同升级代码的所有相关产品:

Set installer = CreateObject("WindowsInstaller.Installer")

' Enumerate all products related to "Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.4148"

' {AA783A14-A7A3-3D33-95F0-9A351D530011} is the upgrade code
Set upgrades = installer.RelatedProducts("{AA783A14-A7A3-3D33-95F0-9A351D530011}")

For Each u In upgrades
   MsgBox u, vbOKOnly, "Product Code: "
Next

然后,您可以通过将产品代码传递到msiexec.exe命令行来卸载产品(有关如何通过MSI API COM自动化来完成此操作,请参见下文):

msiexec.exe /x {11111111-1111-1111-1111-11111111111X} /L*V "C:\msilog.log" REBOOT=ReallySuppress

快速参数解释(因为我推荐此选项):

/X = run uninstall sequence
 /QN = run completely silently
 /L*V "C:\msilog.log"= verbose logging at path specified
 {11111111-1111-1111-1111-11111111111X} = product guid of app to uninstall
 REBOOT=ReallySuppress = prevent reboot without warning (badly authored MSI packages)

如果您不想通过msiexec.exe卸载,那么您可以在这里找到无数种调用MSI卸载的方法:不使用msiexec从命令行卸载MSI文件
您可以通过几种不同的方式找到已安装MSI的产品代码:如何找到已安装MSI安装程序的产品GUID?

更新:我想我忘记了一个显而易见的事实,你可以通过MSI API自动化直接卸载。在下面的脚本中,我们得到了所有共享相同升级代码的产品,然后按顺序卸载它们。

请注意,当静默运行时,您应该使用管理员权限运行,因为UAC可能会被抑制,然后卸载通常会失败(权限被拒绝)。因此,下面的脚本以交互方式运行卸载-允许UAC提示和提升。
如果还不明显:**运行此脚本将卸载Orca!**我使用此产品作为示例,因为它可以快速再次安装(如果您需要在此处底部快速查找安装程序的提示-搜索“orca”):

重大免责声明

COM方法installer.ConfigureProduct不接受允许我们传入REBOOT=ReallySuppress的任何参数。这意味着如果您以管理员权限并在静默模式下运行以下脚本,则触发ScheduleReboot操作(或使用一些更晦涩的魔法导致重新启动)的(非常)错误编写的软件包-可能会在没有警告的情况下重新启动系统 *。
有一个更新的调用ConfigureProductEx,它可以作为一个Win32函数使用,但是它
不**通过COM自动化接口公开。如果你使用platform invoke,你可以使用这个调用-在第14节有一个C示例:不使用msiexec从命令行卸载MSI文件,或者使用WiX工具包中的DTF特性(参见C示例链接中的第6节)。

2018年7月更新

Set installer = CreateObject("WindowsInstaller.Installer")
installer.InstallProduct "product.msi", "REMOVE=ALL REBOOT=ReallySuppress"
Set installer = Nothing

也许上面的代码片段是最好的卸载方法?这应该会抑制任何重新启动。我没有时间或设置来测试它现在(在Linux机器上),但我想添加它之前,我忘记了。

原始卸载脚本

Const msiUILevelNone = 2
Const msiInstallStateAbsent = 2

Set installer = CreateObject("WindowsInstaller.Installer")
'installer.UILevel = msiUILevelNone ' Disabled to prevent silent uninstall. Now the UAC prompt will show

' Uninstall Orca, replace upgrade code with yours
Set products = installer.RelatedProducts("{CFF4D510-79B2-1CCD-0061-5741A0565A76}")

For Each product In products
   ' MsgBox "Product Code: " & product ' Show the product code found, if you want

   ' The following call when run silently with admin rights may reboot the system without warning!
   ' This is due to badly authored MSI packages - most packages will not trigger this problem.
   installer.ConfigureProduct product, 0,  msiInstallStateAbsent ' Uninstall product

   ' See text above for info on the newer ConfigureProductEx method.
Next

Set installer = Nothing

MsgBox "Finished" ' Just so we know the script ran if nothing found to uninstall

部分链接:

8i9zcol2

8i9zcol22#

因为这个问题特别提到了powershell,我也把它放在这里。还有其他的PS解决方案围绕着使用WMI和/或Get-Package。这个解决方案基于https://outnull.wordpress.com/2016/11/02/uninstalling-application-based-on-upgradecode/,但接受各种形式的升级代码语法,并且它试图避免在转换到/从包/升级Guid和注册表表示时的字符串操作。

$upgradecode = "{CFF4D510-79B2-1CCD-0061-5741A0565A76}"
$installer = Join-Path -Path $env:SystemRoot -ChildPath "system32\msiexec.exe" -Resolve

function Reverse-Nibbles {
    param ( [byte[]] $bytes )

    # reverse nibbles of each byte
    for($i = 0; $i -lt $bytes.Length; $i++ )
    {
        $bytes[$i] = (($bytes[$i] -band 0x0F0F) -shl 4) -bor (($bytes[$i] -band 0xF0F0) -shr 4)
    }

    Write-Output -NoEnumerate $bytes
}

function GuidToRegString {
    param ( [guid] $guid )
    $bigendian = (Reverse-Nibbles $guid.ToByteArray())
    return [System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::new($bigendian).ToString()
}

function RegStringToGuid {
    param ( [string] $guid )
    $littleendian = (Reverse-Nibbles ([System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::Parse($guid).Value))
    return [guid]::new($littleendian)
}

$upcode = GuidToRegString ([guid]::Parse($upgradecode))

if (Test-Path -Path "HKLM:\Software\Classes\Installer\UpgradeCodes\$upcode") {
    $products = RegStringToGuid (Get-Item -Path "HKLM:\Software\Classes\Installer\UpgradeCodes\$upcode").Property

    foreach ($prod in $products) {
        $pguid = [guid]::new($prod)
        $p = $pguid.ToString("B")

        if ((Test-Path -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$p") -or
            (Test-Path -Path "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$p"))
        {
            $logfile = Join-Path -Path $PSScriptRoot -ChildPath uninstall-$($pguid.ToString("D")).log
            $args = @( "/x", $p, "/l*v", """$logfile""", "/q", "COMPLETE_UNINSTALL=1", "REBOOT=REALLYSUPPRESS" )
            Write-Host "Uninstalling $p"
            $uninst = Start-Process -FilePath """$installer""" -ArgumentList $args -PassThru -Wait
            Write-Host $uninst.ExitCode
        }
    }
}

相关问题