Aaron Parker was talking about the uninstall guid in his session “Hands off my Golden Image Redux” at Citrix Synergy.

I remembered that I had written a small PowerShell script to read out the uninstall GUID from an MSI file. This way you do not need to actually install the software to determine the uninstall GUID.

How does that work?

There is a logical relation between the MSI Product Code property and the install guid. In this example I’ve taken install_flash_player_11.8.800.174_active_x.msi as an example.

The Uninstall key is HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\A2E504D3D31C0D5409F28F3FDD565AD9

The interesting part of it is the GUID:

A2E504D3D31C0D5409F28F3FDD565AD9

If we look into the MSI properties with (Super)Orca we see:

screenshot

If we compare those GUIDS:

Uninstal    {A2E504D3-D31C-0D54-09F2-8F3FDD565AD9}

Product Code{3D405E2A-C13D-45D0-902F-F8F3DD65A59D}

We can see that we need to apply the following logic:

· First 8 bytes must be swapped right to left

· Next 4 bytes (skipping the hyphen) also swapped right to left

· Next 4 bytes (skipping the hyphen) also swapped right to left

· Next 4 bytes (skipping the hyphen) also swapped right to left

· Last 12 bytes must be byte swapped per byte (F8 -> 8F, F3 -> 3F etc).

Knowing that we can make life easier with PowerShell:

[posh]function Get-Property ($Object, $PropertyName, [object[]]$ArgumentList)

{

return $Object.GetType().InvokeMember($PropertyName, ‘Public, Instance, GetProperty’, $null, $Object, $ArgumentList)

}

function Invoke-Method ($Object, $MethodName, $ArgumentList)

{

return $Object.GetType().InvokeMember($MethodName, ‘Public, Instance, InvokeMethod’, $null, $Object, $ArgumentList)

}

function GetMsiProductCode([string]$path)

{

$msiOpenDatabaseModeReadOnly = 0

$Installer = New-Object -ComObject WindowsInstaller.Installer

$Database = Invoke-Method $Installer OpenDatabase @($path, $msiOpenDatabaseModeReadOnly)

$View = Invoke-Method $Database OpenView @(“SELECT Value FROM Property WHERE Property=’ProductCode'”)

Invoke-Method $View Execute | Out-Null

$Record = Invoke-Method $View Fetch

if ($Record)

{

Write-Output (Get-Property $Record StringData 1)

}

}

cls

$path = “c:\Users\rweijnen\Desktop\install_flash_player_11.8.800.174_active_x.msi”

$item = “” | Select-Object Path, ProductCode, UninstallGuid, UninstallRegistry

$item.Path = $path

$item.ProductCode = (GetMsiProductCode $item.Path)

$DestGuid = ([regex]::Matches($item.ProductCode.Substring(1,8),’.’,’RightToLeft’) | ForEach {$_.value}) -join ”

$DestGuid += ([regex]::Matches($item.ProductCode.Substring(10,4),’.’,’RightToLeft’) | ForEach {$_.value}) -join ”

$DestGuid += ([regex]::Matches($item.ProductCode.Substring(15,4),’.’,’RightToLeft’) | ForEach {$_.value}) -join ”

$DestGuid += ([regex]::Matches($item.ProductCode.Substring(20,2),’.’,’RightToLeft’) | ForEach {$_.value}) -join ”

$DestGuid += ([regex]::Matches($item.ProductCode.Substring(22,2),’.’,’RightToLeft’) | ForEach {$_.value}) -join ”

for ($i=25 ; $i -lt 37 ; $i = $i + 2)

{

$DestGuid += ([regex]::Matches($item.ProductCode.Substring($i,2),’.’,’RightToLeft’) | ForEach {$_.value}) -join ”

}

$item.UninstallGuid = “{” + ([Guid]$DestGuid).ToString().ToUpper() + “}”

$item.UninstallRegistry = “HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\{0}” -f $DestGuid

$item | Format-List

Sample output:

Path              : c:\Users\rweijnen\Desktop\install_flash_player_11.8.800.174_active_x.msi

ProductCode       : {3D405E2A-C13D-45D0-902F-F8F3DD65A59D}

UninstallGuid     : {A2E504D3-D31C-0D54-09F2-8F3FDD565AD9}

UninstallRegistry : HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\A2E504D3D31C0D5409F28F3FDD565AD9

[/posh]