Powershell: Uninstall application by UpgradeCode
Asked Answered
T

3

0

When I upgrade / downgrade my application via a Powershell script, I want to first force the uninstallation of the currently installed version before running the new installer.

How can I do that with Powershell, using the UpgradeCode of the application?

Doing it by application name would be less robust.

Tangled answered 2/3, 2018 at 18:0 Comment(8)
Can you provide what you've already tried?Dentition
Any chance that you are (or will be) doing this through SCCM?Hermaphroditus
I thought correctly designed MSI packages do this automatically.Septavalent
You're cute....Hermaphroditus
Just call msiexec.exe /x UPGRADECODEHermaphroditus
@EBGreen: That's news to me to uninstall via UpgradeCode like that. I tried it, and it does bring up a prompt to uninstall a product - even if there is no product to uninstall - and clicking yes doesn't do anything (regardless if there are relevant products to uninstall or not). I added a script in my answer to uninstall by UpgradeCode using the MSI API (COM automation). It should be translatable to Powershell.Decasyllable
Added an important disclaimer for my previous VBScript sample (prevent unexpected and sudden reboot without warning). And some extra information on alternative methods to use for uninstall.Decasyllable
@SteinÅsmul if you get to the GUI then the uninstall is running. If nothing is actually uninstalling then that is an issue with the uninstall not MSIExec.Hermaphroditus
B
4

Since you mention upgrade code, it must mean that you are talking about an MSI file (Windows Installer). As stated by others such an uninstall is normally performed auto-magically by a properly authored MSI package - it is referred to as a major upgrade - which is essentially an uninstall of the existing version of a product and then the install of the newest version.

The Upgrade Table of the MSI being installed will specify what existing packages on the box will be uninstalled before the new version is installed. In theory you can uninstall any number of existing installations. You can even uninstall a competitive product if you are mad as a hatter. Frankly, and astonishingly, I have never tried to uninstall multiple products during one major upgrade - it is rarely called for. In most cases you uninstall a single, existing product and then install your latest version.

  1. You can modify the Upgrade table using a transform to change how the major upgrade behaves - in other words to make it start or stop uninstalling a specific pre-existing installation.

  2. You can also enumerate all related products that share the same upgrade code by calling this MSI API function (COM - VBScript used as sample):

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

Then you can uninstall the products by passing the product code(s) to the msiexec.exe command line (see below for how to do this via MSI API COM automation instead):

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

Quick Parameter Explanation (since I recommend this option):

 /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)

If you don't want to uninstall via msiexec.exe, then you can find a myriad of ways to invoke an MSI uninstall here: Uninstalling an MSI file from the command line without using msiexec.

And you can find the product code of an installed MSI in several different ways: How can I find the product GUID of an installed MSI setup?


UPDATE: I guess I forgot the obvious, you can uninstall directly via MSI API automation. In the script below we get all products sharing the same upgrade code and then uninstall them in sequence.

Note that when run silently you should run with admin rights since the UAC may be suppressed and then the uninstall will usually fail (permission denied). Because of this the below script runs the uninstall interactively - allowing UAC prompting and elevation.

And if it isn't obvious: running this script will uninstall Orca! I use this product as a sample because it is quick to install again (hints on finding the installer quick if you need to towards bottom here - search for "orca"):

BIG DISCLAIMER:

The COM method installer.ConfigureProduct does not accept any arguments that allow us to pass in REBOOT=ReallySuppress. This means that a (very) badly authored package which triggers the ScheduleReboot action (or uses some more obscure magic to cause a reboot) - may reboot the system without warning if you run the below script with admin rights and in silent mode.

There is a newer call ConfigureProductEx which is available as a Win32 function, but it is not exposed via the COM automation interface. If you platform invoke you can use that call - there is a C++ example in section 14 here: Uninstalling an MSI file from the command line without using msiexec. Or you can use the DTF feature from the WiX toolkit (see section 6 in the same link as the C++ example).


UPDATE July 2018:

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

Perhaps the above snippet is the best uninstall approach? This should suppress any reboots. I don't have the time or the setup to test it right now (on a Linux box), but I wanted to add it before I forget.


Original uninstall script:

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

Some Links:

Bookkeeper answered 2/3, 2018 at 21:10 Comment(0)
S
1

Use WindowsInstaller.Installer to find the product code from the upgrade code, then run msiexec /x {productcode}.

Example using powershell to uninstall upgrade code {939BFD63-B6E1-44BC-BD9E-225DDC30CF51}:

PS C:\Users\user> $inst = New-Object -ComObject "WindowsInstaller.Installer"
PS C:\Users\user> $inst.RelatedProducts('{939BFD63-B6E1-44BC-BD9E-225DDC30CF51}')
{F587B97A-43C7-43D9-9243-DF76B0A54DE8}
PS C:\Users\user> msiexec /x "{F587B97A-43C7-43D9-9243-DF76B0A54DE8}"
PS C:\Users\user>
Stuckey answered 4/11, 2023 at 1:56 Comment(0)
P
0

Since the question specifically mentions powershell I'll just put this here too. There are other PS solutions around using WMI and/or Get-Package. This solution is based off of https://outnull.wordpress.com/2016/11/02/uninstalling-application-based-on-upgradecode/ but accepts various forms of upgrade code syntax, and it tries to avoid string manipulation when converting to/from the package/upgrade Guid and the registry representation.

$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
        }
    }
}
Politicize answered 30/1, 2023 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.