Skip to content

Instantly share code, notes, and snippets.

@MyITGuy
Last active March 27, 2024 02:22
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MyITGuy/153fc0f553d840631269720a56be5136 to your computer and use it in GitHub Desktop.
Save MyITGuy/153fc0f553d840631269720a56be5136 to your computer and use it in GitHub Desktop.
PowerShell: Get-MsiProducts / Get Windows Installer Products
function Get-MsiProducts {
function Get-MsiUpgradeCode {
[CmdletBinding()]
param (
[System.Guid]$ProductCode
,
[System.Guid]$UpgradeCode
)
function ConvertFrom-CompressedGuid {
<#
.SYNOPSIS
Converts a compressed globally unique identifier (GUID) string into a GUID string.
.DESCRIPTION
Takes a compressed GUID string and breaks it into 6 parts. It then loops through the first five parts and reversing the order. It loops through the sixth part and reversing the order of every 2 characters. It then joins the parts back together and returns a GUID.
.EXAMPLE
ConvertFrom-CompressedGuid -CompressedGuid '2820F6C7DCD308A459CABB92E828C144'
The output of this example would be: {7C6F0282-3DCD-4A80-95AC-BB298E821C44}
.PARAMETER CompressedGuid
A compressed globally unique identifier (GUID) string.
#>
[CmdletBinding()]
[OutputType([System.String])]
param (
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)]
[ValidatePattern('^[0-9a-fA-F]{32}$')]
[ValidateScript( { [System.Guid]::Parse($_) -is [System.Guid] })]
[System.String]$CompressedGuid
)
process {
Write-Verbose "CompressedGuid: $($CompressedGuid)"
$GuidString = ([System.Guid]$CompressedGuid).ToString('N')
Write-Verbose "GuidString: $($GuidString)"
$Indexes = [ordered]@{
0 = 8
8 = 4
12 = 4
16 = 2
18 = 2
20 = 12
}
$Guid = ''
foreach ($index in $Indexes.GetEnumerator()) {
$Substring = $GuidString.Substring($index.Key, $index.Value)
Write-Verbose "Substring: $($Substring)"
switch ($index.Key) {
20 {
$parts = $Substring -split '(.{2})' | Where-Object { $_ }
foreach ($part In $parts) {
$part = $part -split '(.{1})'
Write-Verbose "Part: $($part)"
[System.Array]::Reverse($part)
Write-Verbose "Reversed: $($part)"
$Guid += $part -join ''
}
}
default {
$part = $Substring.ToCharArray()
Write-Verbose "Part: $($part)"
[System.Array]::Reverse($part)
Write-Verbose "Reversed: $($part)"
$Guid += $part -join ''
}
}
}
[System.Guid]::Parse($Guid).ToString('B').ToUpper()
}
}
function ConvertTo-CompressedGuid {
<#
.SYNOPSIS
Converts a GUID string into a compressed globally unique identifier (GUID) string.
.DESCRIPTION
Takes a GUID string and breaks it into 6 parts. It then loops through the first five parts and reversing the order. It loops through the sixth part and reversing the order of every 2 characters. It then joins the parts back together and returns a compressed GUID string.
.EXAMPLE
ConvertTo-CompressedGuid -Guid '{7C6F0282-3DCD-4A80-95AC-BB298E821C44}'
The output of this example would be: 2820F6C7DCD308A459CABB92E828C144
.PARAMETER Guid
A globally unique identifier (GUID).
#>
[CmdletBinding()]
[OutputType([System.String])]
param (
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)]
[ValidateScript( { [System.Guid]::Parse($_) -is [System.Guid] })]
[System.Guid]$Guid
)
process {
Write-Verbose "Guid: $($Guid)"
$GuidString = $Guid.ToString('N')
Write-Verbose "GuidString: $($GuidString)"
$Indexes = [ordered]@{
0 = 8
8 = 4
12 = 4
16 = 2
18 = 2
20 = 12
}
$CompressedGuid = ''
foreach ($index in $Indexes.GetEnumerator()) {
$Substring = $GuidString.Substring($index.Key, $index.Value)
Write-Verbose "Substring: $($Substring)"
switch ($index.Key) {
20 {
$parts = $Substring -split '(.{2})' | Where-Object { $_ }
foreach ($part In $parts) {
$part = $part -split '(.{1})'
Write-Verbose "Part: $($part)"
[System.Array]::Reverse($part)
Write-Verbose "Reversed: $($part)"
$CompressedGuid += $part -join ''
}
}
default {
$part = $Substring.ToCharArray()
Write-Verbose "Part: $($part)"
[System.Array]::Reverse($part)
Write-Verbose "Reversed: $($part)"
$CompressedGuid += $part -join ''
}
}
}
[System.Guid]::Parse($CompressedGuid).ToString('N').ToUpper()
}
}
filter ByProductCode {
$Object = $_
Write-Verbose "ProductCode: $($ProductCode)"
if ($ProductCode) {
$Object | Where-Object { [System.Guid]($_.ProductCode) -eq [System.Guid]($ProductCode) }
break
}
$Object
}
$Path = "Registry::HKEY_CLASSES_ROOT\Installer\UpgradeCodes\*"
if ($UpgradeCode) {
$CompressedUpgradeCode = ConvertTo-CompressedGuid -Guid $UpgradeCode -Verbose:$false
Write-Verbose "CompressedUpgradeCode: $($CompressedUpgradeCode)"
$Path = "Registry::HKEY_CLASSES_ROOT\Installer\UpgradeCodes\$($CompressedUpgradeCode)"
}
Get-Item -Path $Path -ErrorAction SilentlyContinue | ForEach-Object {
$UpgradeCodeFromCompressedGuid = ConvertFrom-CompressedGuid -CompressedGuid $_.PSChildName -Verbose:$false
foreach ($ProductCodeCompressedGuid in ($_.GetValueNames())) {
$Properties = [ordered]@{
ProductCode = ConvertFrom-CompressedGuid -CompressedGuid $ProductCodeCompressedGuid -Verbose:$false
UpgradeCode = [System.Guid]::Parse($UpgradeCodeFromCompressedGuid).ToString('B').ToUpper()
}
[PSCustomObject]$Properties | ByProductCode
}
}
}
$MsiUpgradeCodes = Get-MsiUpgradeCode
$Installer = New-Object -ComObject WindowsInstaller.Installer
$Type = $Installer.GetType()
$Products = $Type.InvokeMember('Products', [System.Reflection.BindingFlags]::GetProperty, $null, $Installer, $null)
foreach ($Product In $Products) {
$hash = @{}
$hash.ProductCode = $Product
$Attributes = @('Language', 'ProductName', 'PackageCode', 'Transforms', 'AssignmentType', 'PackageName', 'InstalledProductName', 'VersionString', 'RegCompany', 'RegOwner', 'ProductID', 'ProductIcon', 'InstallLocation', 'InstallSource', 'InstallDate', 'Publisher', 'LocalPackage', 'HelpLink', 'HelpTelephone', 'URLInfoAbout', 'URLUpdateInfo')
foreach ($Attribute In $Attributes) {
$hash."$($Attribute)" = $null
}
foreach ($Attribute In $Attributes) {
try {
$hash."$($Attribute)" = $Type.InvokeMember('ProductInfo', [System.Reflection.BindingFlags]::GetProperty, $null, $Installer, @($Product, $Attribute))
} catch [System.Exception] {
#$error[0]|format-list –force
}
}
# UpgradeCode
$hash.UpgradeCode = $MsiUpgradeCodes | Where-Object ProductCode -eq ($hash.ProductCode) | Select-Object -ExpandProperty UpgradeCode
New-Object -TypeName PSObject -Property $hash
}
}
#region Windows Installer
function Get-MsiProducts {
[CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName="None")]
PARAM()
$Installer = New-Object -ComObject WindowsInstaller.Installer
$Type = $Installer.GetType()
$Products = $Type.InvokeMember('Products', [System.Reflection.BindingFlags]::GetProperty, $null, $Installer, $null)
$MsiProducts = foreach ($Product In $Products) {
try {
$MsiProduct = New-Object -TypeName PSObject -Property @{
ProductCode = $Product
}
$MsiProperties = @('Language', 'ProductName', 'PackageCode', 'Transforms', 'AssignmentType', 'PackageName', 'InstalledProductName', 'VersionString', 'RegCompany', 'RegOwner', 'ProductID', 'ProductIcon', 'InstallLocation', 'InstallSource', 'InstallDate', 'Publisher', 'LocalPackage', 'HelpLink', 'HelpTelephone', 'URLInfoAbout', 'URLUpdateInfo')
foreach ($MsiProperty In $MsiProperties) {
$MsiProduct | Add-Member -MemberType NoteProperty -Name $MsiProperty -Value $Type.InvokeMember('ProductInfo', [System.Reflection.BindingFlags]::GetProperty, $null, $Installer, @($Product, $MsiProperty))
}
$MsiProduct | Add-Member -MemberType ScriptProperty -Name 'ProductVersion' -Value {$this.VersionString}
$MsiProduct | Add-Member -MemberType ScriptProperty -Name 'Manufacturer' -Value {$this.Publisher}
$MsiProduct
} catch [System.Exception] {
#$error[0]|format-list –force
}
}
$MsiProducts | Sort ProductName
}
function Get-WindowsInstallerTableData {
[CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName="None")]
PARAM (
[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,HelpMessage="MSI Database Filename",ValueFromPipeline=$true)]
[Alias("Database","Msi")]
[ValidateScript({Test-Path $_ -PathType 'Leaf'})]
[System.IO.FileInfo]
$MsiDbPath
,
[Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,HelpMessage="MST Database Filename",ValueFromPipeline=$true)]
[Alias("Transform","Mst")]
[System.IO.FileInfo[]]
$MstDbPath
,
[Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,HelpMessage="SQL Query",ValueFromPipeline=$true)]
[Alias("Query")]
[String]
$Table
)
begin {
# Add Required Type Libraries
Add-Type -Path "$(Split-Path $Script:MyInvocation.MyCommand.Path)\Microsoft.Deployment.WindowsInstaller.dll";
}
process {
# Open an MSI Database
$Database = New-Object Microsoft.Deployment.WindowsInstaller.Database $MsiDbPath;
# ApplyTransforms
foreach ($MstDbPathEx In $MstDbPath) {
if (Test-Path -Path $MstDbPathEx -PathType Leaf) {
$Database.ApplyTransform($MstDbPathEx);
}
}
#Create a View object
# SELECT `Message` FROM `Error` WHERE `Error` = 1715
# [Microsoft.Deployment.WindowsInstaller.View]
$_ColumnsView = $Database.OpenView("SELECT * FROM ``_Columns`` WHERE ``Table`` = '$($Table)'");
if ($_ColumnsView) {
# Execute the View object
$_ColumnsView.Execute()
# Place the objects in a PSObject
$_Columns = @()
$_ColumnsRow = $_ColumnsView.Fetch()
while($_ColumnsRow -ne $null) {
$hash = @{
'Table' = $_ColumnsRow.GetString(1)
'Number' = $_ColumnsRow.GetString(2)
'Name' = $_ColumnsRow.GetString(3)
'Type' = $_ColumnsRow.GetString(4)
}
$_Columns += New-Object -TypeName PSObject -Property $hash
$_ColumnsRow = $_ColumnsView.Fetch()
}
$FieldNames = $_Columns | Select -ExpandProperty Name
}
if ($FieldNames) {
# [Microsoft.Deployment.WindowsInstaller.View]
$TableView = $Database.OpenView("SELECT * FROM ``$($Table)``");
# Execute the View object
$TableView.Execute()
# Place the objects in a PSObject
$Rows = @()
# Fetch the first record
$Row = $TableView.Fetch()
while($Row -ne $null) {
$hash = @{}
foreach ($FieldName In $FieldNames) {
$hash += @{
$FieldName = $Row.Item($FieldName)
}
}
$Rows += New-Object -TypeName PSObject -Property $hash
# Fetch the next record
$Row = $TableView.Fetch()
}
$Rows
}
}
end {
#Close the Database & View
if ($_ColumnsView) {$_ColumnsView.Close();}
if ($TableView) {$TableView.Close();}
if ($Database) {$Database.Dispose();}
}
}
#endregion
@CarstenG2
Copy link

GUID-string into GUID:

$bytes = [System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::Parse('2820F6C7DCD308A459CABB92E828C144').Value
$bytes2 = foreach($byte in $bytes){($byte -shr 4) + (($byte -band 15) -shl 4)}
[guid]::new([byte[]]$bytes2).Guid

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment