Skip to content

Instantly share code, notes, and snippets.

@MyITGuy
Last active October 4, 2024 15:12
Show Gist options
  • 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 {
[CmdletBinding()]
param()
function ConvertFrom-PackedGuid {
<#
.SYNOPSIS
Converts a packed globally unique identifier (GUID) string into a GUID string.
.DESCRIPTION
Takes a packed 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-PackedGuid -PackedGuid '2820F6C7DCD308A459CABB92E828C144'
The output of this example would be: {7C6F0282-3DCD-4A80-95AC-BB298E821C44}
.PARAMETER PackedGuid
A packed 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]$PackedGuid
)
process {
Write-Verbose "PackedGuid: $($PackedGuid)"
$GuidString = ([System.Guid]$PackedGuid).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-PackedGuid {
<#
.SYNOPSIS
Converts a GUID string into a packed 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 packed GUID string.
.EXAMPLE
ConvertTo-PackedGuid -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
}
$PackedGuid = ''
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)"
$PackedGuid += $part -join ''
}
}
default {
$part = $Substring.ToCharArray()
Write-Verbose "Part: $($part)"
[System.Array]::Reverse($part)
Write-Verbose "Reversed: $($part)"
$PackedGuid += $part -join ''
}
}
}
[System.Guid]::Parse($PackedGuid).ToString('N').ToUpper()
}
}
function Get-MsiUpgradeCode {
[CmdletBinding()]
param (
[System.Guid]$ProductCode
,
[System.Guid]$UpgradeCode
)
function ConvertFrom-PackedGuid {
<#
.SYNOPSIS
Converts a packed globally unique identifier (GUID) string into a GUID string.
.DESCRIPTION
Takes a packed 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-PackedGuid -PackedGuid '2820F6C7DCD308A459CABB92E828C144'
The output of this example would be: {7C6F0282-3DCD-4A80-95AC-BB298E821C44}
.PARAMETER PackedGuid
A packed 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]$PackedGuid
)
process {
Write-Verbose "PackedGuid: $($PackedGuid)"
$GuidString = ([System.Guid]$PackedGuid).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-PackedGuid {
<#
.SYNOPSIS
Converts a GUID string into a packed 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 packed GUID string.
.EXAMPLE
ConvertTo-PackedGuid -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
}
$PackedGuid = ''
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)"
$PackedGuid += $part -join ''
}
}
default {
$part = $Substring.ToCharArray()
Write-Verbose "Part: $($part)"
[System.Array]::Reverse($part)
Write-Verbose "Reversed: $($part)"
$PackedGuid += $part -join ''
}
}
}
[System.Guid]::Parse($PackedGuid).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) {
$PackedUpgradeCode = ConvertTo-PackedGuid -Guid $UpgradeCode -Verbose:$false
Write-Verbose "PackedUpgradeCode: $($PackedUpgradeCode)"
$Path = "Registry::HKEY_CLASSES_ROOT\Installer\UpgradeCodes\$($PackedUpgradeCode)"
}
Get-Item -Path $Path -ErrorAction SilentlyContinue | ForEach-Object {
$UpgradeCodeFromPackedGuid = ConvertFrom-PackedGuid -PackedGuid $_.PSChildName -Verbose:$false
foreach ($ProductCodePackedGuid in ($_.GetValueNames())) {
$Properties = [ordered]@{
ProductCode = ConvertFrom-PackedGuid -PackedGuid $ProductCodePackedGuid -Verbose:$false
UpgradeCode = [System.Guid]::Parse($UpgradeCodeFromPackedGuid).ToString('B').ToUpper()
}
[PSCustomObject]$Properties | ByProductCode
}
}
}
function Remove-ComObject {
[CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName="None")]
PARAM(
[Parameter(Mandatory = $true, Position = 0)]
[System.__ComObject]
$ComObject
)
BEGIN {
Write-Verbose "[$($MyInvocation.MyCommand)] Entering..."
}
PROCESS {
try {
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($ComObject) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Write-Verbose "[$($MyInvocation.MyCommand)] Successfully released ComObject"
} catch {
Write-Verbose "[$($MyInvocation.MyCommand)] Failed to release ComObject"
}
}
END {
Write-Verbose "[$($MyInvocation.MyCommand)] Exiting..."
}
}
Write-Verbose $MyInvocation.MyCommand
$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) {
# Attributes from Windows Installer database
[string[]]$Attributes = @('Language', 'ProductName', 'PackageCode', 'Transforms', 'AssignmentType', 'PackageName', 'InstalledProductName', 'VersionString', 'RegCompany', 'RegOwner', 'ProductID', 'ProductIcon', 'InstallLocation', 'InstallSource', 'InstallDate', 'Publisher', 'LocalPackage', 'HelpLink', 'HelpTelephone', 'URLInfoAbout', 'URLUpdateInfo')
$Attributes += 'ProductCode'
$Attributes += 'UpgradeCode'
$Attributes | Where-Object { $_ -match 'Code$' } | ForEach-Object { $Attributes += "$($_)Packed" }
$Attributes = $Attributes | Sort-Object
$hash = [ordered]@{}
foreach ($Attribute In $Attributes) {
switch ($Attribute) {
('ProductCode') {
$hash."${Attribute}" = $Product
}
('UpgradeCode') {
$hash."${Attribute}" = $MsiUpgradeCodes | Where-Object ProductCode -eq ($Product) | Select-Object -ExpandProperty UpgradeCode
}
default {
$hash."${Attribute}" = $null
try {
$InstallerProperty = $null
$InstallerProperty = $Type.InvokeMember('ProductInfo', [System.Reflection.BindingFlags]::GetProperty, $null, $Installer, @($Product, $Attribute))
$hash."${Attribute}" = $InstallerProperty
} catch {}
}
}
}
foreach ($Attribute In $Attributes) {
if ($Attribute -match 'code$') {
if ( [System.Guid]::TryParse(($hash."${Attribute}"), [ref][System.Guid]::NewGuid()) -eq $true ) {
$hash."${Attribute}Packed" = ConvertTo-PackedGuid -Guid ($hash."${Attribute}")
}
}
}
New-Object -TypeName PSObject -Property $hash
}
Remove-ComObject -ComObject $Installer
}
#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