<#PSScriptInfo .VERSION 2026.1.7.0 .GUID 3a7b9c4d-2e8f-4a1b-9d6c-5e3f7a8b9c2d .AUTHOR Michael Escamilla .COMPANYNAME .COPYRIGHT .TAGS .LICENSEURI .PROJECTURI https://github.com/MichaelEscamilla/GetMSIInformation .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES 1.0.0.0 - Initial release 2.0.0.0 - 10-4-2024 - Added file hash information, and context menu items for the installation and uninstallation of a Right-Click Option in Windows Explorer. And some other UI improvements. 2024.10.4.1 - Updated the version numbering, and a sepearator in the context menu. 2024-10.13.0 - Added an error message when the file is locked 2024-12.8.0 - Formatted Script for Publishing to PowerShell Gallery 2026-1.5.0 - Added Icon extraction and export functionality. Added context menu items to open the icon temp folder and right-click menu folder. 2026.1.5.1 - Fixed a bug when launching the script from the internet 2026.1.6.0 - Fixed a bug where the 'No Icon' label would not hide 2026.1.6.1 - Added the michaeltheadmin.com icon to the Form and Right-Click Menu 2026.1.7.0 - Simplified console output messages during script download from GitHub .PRIVATEDATA #> <# .SYNOPSIS This script provides a graphical user interface (GUI) for viewing and copying properties of MSI files. .DESCRIPTION The script creates a WPF-based GUI that allows users to drag and drop MSI files to view their properties such as Product Name, Manufacturer, Product Version, Product Code, and Upgrade Code. It also provides functionality to copy these properties to the clipboard and to clear the displayed information. Additionally, the script includes options to install and uninstall a context menu item for MSI files to retrieve their properties. .PARAMETER FilePath Optional parameter to specify the path of the MSI file to automatically load the information for. .NOTES #> param ( [Parameter(Mandatory = $false)] [string]$FilePath ) ############################################# ################# Variables ################# ############################################# # Script Name $Script:ScriptName = "GetMSIInformation.ps1" # Script Version [System.Version]$Script:ScriptVersion = "2026.1.7.0" # Right-Click Menu $Script:RightClickMenuName = "Get MSI Information" $Script:RightClickMenuFolderPath = "$env:LOCALAPPDATA\GetMSIInformation" # Icon Temp Folder Path $Script:IconTempFolderPath = "$env:TEMP\GetMSIInformation\Icons" # Get the Security Principal $Script:currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) # Get PowerShell Version $Script:ScriptPSVersion = $PSVersionTable.PSVersion # Get Pwsh Path $Script:PowerShellPath = (Get-Command pwsh.exe -ErrorAction SilentlyContinue) # michaeltheadmin.com Icon $Script:WindowIconBase64 = "AAABAAEAIBwAAAEAIACYDgAAFgAAACgAAAAgAAAAOAAAAAEAIAAAAAAAcA4AAMQOAADEDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaSFoNRkZGEUtLSxBLS0sQS0tLEEtLSxBLS0sQS0tLEEtLSxBLS0sQS0tLEEtLSxBLS0sQS0tLEEtLSxBLS0sQS0tLEEtLSxBLS0sQS0tLEEtLSxBLS0sQS0tLEEtLSxBLS0sQS0tLEFRGRhFaSEgNAAAAAAAAAABUVFQLUkxPrFNOUOlRTE7tUUxO7FFMTuxRTE7sUUxO7FFMTuxRTE7sUUxO7FFMTuxRTE7sUUxO7FFMTuxRTE7sUUxO7FFMTuxRTE7sUUxO7FFMTuxRTE7sUUxO7FFMTuxRTE7sUUxO7FFMTuxRTE7sUUxO7VROUOhSTU+qTExMCU5KTH5lYWP/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bm1t/25tbf9ubW3/bmxt/2RfYf9OSkx6TkhLtWtoaf94eHj/dnZ2/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3Z2dv94eHj/aWZn/05IS69OSUuxamdo/3h4eP9mZGX/Uk1P/05JS/9OSUv/VVFT/1VRUv9OSUv/T0pM/09KTP9PSkz/T0pM/09KTP9PSkz/T0pM/09KTP9PSkz/T0pM/09KTP9PSkz/TklL/1VRU/9VUVP/TklL/05JS/9STlD/ZmRl/3h4eP9oZWb/T0lMqk9JTLBqZ2j/eHl4/1dUVf90cHH/rqys/62rrP9rZ2j/b2tt/66srP+npaX/p6Wl/6elpf+npaX/p6Wl/6elpf+npaX/p6Wl/6elpf+npaX/p6Wl/6elpf+tq6z/a2do/29rbP+urKz/rays/3Bsbv9YVVb/eHl5/2hlZv9PSUyqT0lMsGpnaP94eXj/U1BR/5GPkP/09PT/8/Pz/399ff+Hg4X/9PT0/+jo6P/o6Oj/6Ojo/+jo6P/o6Oj/6Ojo/+jo6P/o6Oj/6Ojo/+jo6P/o6Oj/6Ojo//Pz8/9/fX3/hoOE//T09P/z9PP/i4iJ/1VRUv94eXn/aGVm/09JTKpPSUywamdo/3h5eP9VUlP/hIGC/9PS0v/S0dH/dXJz/3t3ef/T0tL/ycjI/8rJyv/Kycr/ycjI/8nIyP/Kycr/ysnK/8nIyP/JyMj/ysnK/8vKyf/Lycj/1NLR/3dzc/98eHj/1dPS/9TT0f9/fHz/V1NU/3h5ef9oZWb/T0lMqk9JTLBqZ2j/eHl4/19cXv9TTU//V1JU/1dSVP9TTlD/VE9R/1dSVP9XUlT/V1NV/1dTVf9XUlT/V1JU/1dTVf9XU1X/V1JU/1dSVP9XU1X/WFNU/1hSVP9ZU1T/VU9Q/1VPUP9ZU1T/WVNU/1JNTv9gXV7/eHl5/2hlZv9PSUyqT0lMsGpnaP94eXj/V1NV/3x4ef/Hx8j/v76//8HAwf/BwMD/v76//8jHx/9ybnD/dnN0/8jIyP/Ix8f/cm9w/3Vxc//Ix8j/yMfI/4B2cP85VYL/LXrf/y933P8xed7/MXne/y933P8tet7/Q1p//2NaU/94eXn/aGVm/09JTKpPSUywamdo/3h5eP9UUFL/jImK//L09P/n6Oj/5+jo/+fo6P/n6Oj/8/Pz/4B9f/+Gg4T/9PT0//L08/+Afn//hICC//L09P/y9PT/k4h//zRblv8jjf//J4n//yeJ//8nif//J4n//yON//9AYJL/Y1lQ/3h5ef9oZWb/T0lMqk9JTLBqZ2j/eHl4/1hUVv95dHP/v7u3/7izr/+3tLP/tLO0/7GwsP+5t7j/cGxu/3Rwcf+5uLj/ubi4/3Bsbv9ybnD/ubi4/7m4uP98c27/PVV9/y5zzf8xcMr/NHTO/zR0zv8xcMr/LnPN/0RXef9jWlX/eHl5/2hlZv9PSUyqT0lMsGpnaP94eXj/YFxd/01LUv9ITVz/SE5d/0pJUP9TTU7/XFdZ/1tXWf9VUVL/VlFT/1tXWf9bV1n/VVFT/1ZRU/9cV1n/XFdZ/1VQU/9ZU1L/ZFtY/2RbV/9ZUEz/WlFN/2RbWP9kW1j/VlBQ/19cXv94eXn/aGVm/09JTKpPSUywamdo/3h5eP9jWFH/QF+Q/ymE+f8phPn/OFiJ/4yCe//e3t7/3d3d/3p2eP9/fH3/3t7e/93e3f96d3j/fXl7/93e3v/d3t7/fXp7/4B9fv/f3t7/3t3d/3p2d/9/fH3/397e/97e3f+DgIH/VlJT/3h5ef9oZWb/T0lMqk9JTLBqZ2j/eHl4/2NYT/8+Ypr/JI3//ySN//81WpL/lYuC//Pz8//y8vL/gHx+/4aDhP/z8/P/8vPy/4B9fv+DgIL/8vPz//Lz8/+DgYL/h4SF//Pz8//y8vL/f3x9/4aDhP/z8/P/8vPy/4qIiP9VUVL/eHl5/2hlZv9PSUyqT0lMsGpnaP94eXj/YFhV/0ZTbv87aaj/O2mo/0NRbP9waGX/m5iZ/5uYmf9mYWP/aGRm/5uYmf+bmJn/ZmJj/2djZf+bmJn/m5iZ/2djZf9pZWb/m5iZ/5uYmf9lYWL/amZo/6Cdnv+fnZ7/a2Zo/1lVV/94eXn/aGVm/09JTKpOSUuxamdo/3d4eP9nZmj/XFVT/2BUTP9gVEz/X1hV/1dUVv9QTE7/UExO/1lVV/9ZVVb/UExO/1BMTv9ZVVf/WVVW/1BMTv9QTE7/WVVW/1hVVv9QTE7/UExO/1lVV/9ZVVb/UE1O/1FNTv9WUlT/aWdo/3d4eP9oZWb/TkhLq09JTLNraGn/eHl5/3d3d/93eHj/d3h3/3d4d/93eHf/d3h3/3d4d/93eHf/d3h3/3d4d/93eHf/d3h3/3d4d/93eHf/d3h3/3d4d/93eHf/d3h3/3d4d/93eHf/d3h3/3d4d/93eHf/d3h3/3d4eP93d3f/eHl4/2hmZ/9OSEuuUUpMbWRfYf9ta2z/bWts/21rbP9ta2z/bWts/21rbP9ta2z/bWts/21rbP9ta2z/bWts/21rbP9ta2z/amhp/2poaf9ta2z/bWts/21rbP9ta2z/bWts/21rbP9ta2z/bWts/21rbP9ta2z/bWts/21rbP9samv/Yl1f/1BLS2hRUVECUUtNjFNNT89QS0zTUEpM01BKTNNQSkzTUEpM01BKTNNQSkzTUEpM01BKTNNQSkzTUEpM01BLTdFQS03yUEtN8FBLTdFQSkzTUEpM01BKTNNQSkzTUEpM01BKTNNQSkzTUEpM01BKTNNQSkzTUEtM01NNT89RTE6IfHx8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFNMT7lTTVCmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU01Qs1JNT7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABTTU1RWFNV/1NOUIhUTE9pVFBSaExMTBYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABRTE9mVlFS11NOUOJWUVLdUEtQNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////wAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//n////5////+B////wf///////////8=" ############################################# ################# Functions ################# ############################################# #region Functions function Test-FileLock { param ( [Parameter(Mandatory = $true)] [string]$Path ) try { $FileStream = [System.IO.File]::Open("$($Path)", 'Open', 'Write') $FileStream.Close() $FileStream.Dispose() return $false } catch { return $true } } function Get-MsiProperties { param ( [Parameter(Mandatory = $true)] [IO.FileInfo[]]$Path ) Write-Host "Getting MSI Properties for: [$Path]" # Check if the MSI file path exists if (-not (Test-Path $Path)) { throw "The file $Path does not exist." } # Create a new Windows Installer COM object $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer # Open the MSI database in read-only mode $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($Path.FullName, 0)) # Open a view on the Property table $MSIPropertyView = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, @("SELECT * FROM Property")) # Execute the view query $MSIPropertyView.GetType().InvokeMember("Execute", "InvokeMethod", $null, $MSIPropertyView, $null) | Out-Null # Fetch the first record from the result set $MSIRecord = $MSIPropertyView.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $MSIPropertyView, $null) # Initialize an empty System Object to store properties [System.Object]$Properties = @{} # Loop through all records in the result set while ($null -ne $MSIRecord) { # Get the property name from the first column $property = $MSIRecord.GetType().InvokeMember("StringData", "GetProperty", $null, $MSIRecord, @(1)) # Get the property value from the second column $Value = $MSIRecord.GetType().InvokeMember("StringData", "GetProperty", $null, $MSIRecord, @(2)) # Add the property name and value to the hashtable $Properties[$Property] = $Value # Fetch the next record from the result set $MSIRecord = $MSIPropertyView.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $MSIPropertyView, $null) } # Close the Property view $MSIPropertyView.GetType().InvokeMember("Close", "InvokeMethod", $null, $MSIPropertyView, $null) | Out-Null # Return the System Object of properties $Properties } function Enable-AllButtons { param ( [Parameter(Mandatory = $false)] [string]$Exclude ) # Get all button variables $Buttons = Get-Variable -Name "btn_*" -ValueOnly -ErrorAction SilentlyContinue foreach ($Button in $Buttons) { # Check that the button name does not contain the exclude variable if ($Button.Name -notlike "*$Exclude*") { # Enable Button $Button.IsEnabled = $true } } } function Disable-AllButtons { # Get all button variables $Buttons = Get-Variable -Name "btn_*" -ValueOnly -ErrorAction SilentlyContinue foreach ($Button in $Buttons) { # Disable Button $Button.IsEnabled = $false } } function Clear-Textboxes { # Get all textbox variables $Textboxes = Get-Variable -Name "txt_*" -ValueOnly -ErrorAction SilentlyContinue foreach ($Textbox in $Textboxes) { # Clear textbox $Textbox.Clear() } } # Stolen from: https://github.com/PatchMyPCTeam/CustomerTroubleshooting/blob/Release/PowerShell/Get-LocalContentHashes.ps1 function Get-EncodedHash { [CmdletBinding()] Param( [Parameter(Position = 0)] [System.Object]$HashValue ) $hashBytes = $hashValue.Hash -split '(?<=\G..)(?=.)' | ForEach-Object { [byte]::Parse($_, 'HexNumber') } Return [Convert]::ToBase64String($hashBytes) } function Get-FileHashInformation { param ( [Parameter(Mandatory = $true)] [IO.FileInfo[]]$Path ) Write-Host "Getting File Hash Information for: [$Path]" # Initialize the hash object $Hashes = @{} # Get File Hash - MD5 $FileHashMD5 = Get-FileHash -Path $Path -Algorithm MD5 $Hashes["MD5"] = $FileHashMD5 # Get File Hash - SHA1 $FileHashSHA1 = Get-FileHash -Path $Path -Algorithm SHA1 $Hashes["SHA1"] = $FileHashSHA1 # Get File Hash - SHA256 $FileHashSHA256 = Get-FileHash -Path $Path -Algorithm SHA256 $Hashes["SHA256"] = $FileHashSHA256 # Get File Hash - SHA1 - Encoded $FileHashEncoded = Get-EncodedHash -HashValue $FileHashSHA1 $Hashes["Digest"] = $FileHashEncoded # Return the hash object $Hashes } function Get-MsiIcon { param ( [Parameter(Mandatory = $true)] [IO.FileInfo[]]$Path, [Parameter(Mandatory = $false)] [string]$ExportFolder = "$($Script:IconTempFolderPath)" ) Write-Host "Getting MSI Icon for: [$Path]" # Ensure the export folder exists if (-not (Test-Path -Path $ExportFolder)) { New-Item -ItemType Directory -Path $ExportFolder -Force | Out-Null } # Create a new Windows Installer COM object $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer # Open the MSI database in read-only mode $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($Path.FullName, 0)) # Get all Icons in the Icon table try { # Open a view on the Icon table to get the icon binary data $IconView = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, @("SELECT Name FROM _Tables WHERE Name='Icon'")) # Execute the view query $IconView.GetType().InvokeMember("Execute", "InvokeMethod", $null, $IconView, $null) | Out-Null # Fetch Record $IconTable = $IconView.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $IconView, $null) # Close the Icon view $IconView.GetType().InvokeMember("Close", "InvokeMethod", $null, $IconView, $null) | Out-Null if ($IconTable) { # Open a view on the Icon table $IconData = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, @("SELECT Name,Data FROM Icon")) # Execute the view query $IconData.GetType().InvokeMember("Execute", "InvokeMethod", $null, $IconData, $null) | Out-Null # Start Building PSCustomObject [Collections.Generic.List[PSCustomObject]]$IconInformation = @() Do { # Fetch the next record $IconRecord = $IconData.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $IconData, $null) if ($IconRecord) { # Get the 'Name' Field $IconDataName = $IconData.GetType().InvokeMember("StringData", 'Public, Instance, GetProperty', $null, $IconRecord, 1) # Get the DataSize of the Binary Data $IconDataSize = $IconData.GetType().InvokeMember("DataSize", "GetProperty", $null, $IconRecord, 2) Write-Verbose "Found Icon: [$IconDataName] with Size: [$IconDataSize] bytes" # Read the Binary Data $IconBinaryData = $IconData.GetType().InvokeMember("ReadStream", "InvokeMethod", $null, $IconRecord, @(2, $IconDataSize, 2)) # Get Binary data as ANSI string - use Windows-1252 encoding $ByteArray = [System.Text.Encoding]::GetEncoding(1252).GetBytes($IconBinaryData) # Construct the path to save the ICO file $IconPathTemp = Join-Path -Path $ExportFolder -ChildPath "$($IconDataName)" # Check if the file already exists and delete it if necessary if (Test-Path -Path $IconPathTemp) { Remove-Item -Path $IconPathTemp -Force } # Write the byte array to an ICO file [System.IO.File]::WriteAllBytes($IconPathTemp, $ByteArray) # Create PSCustomObject for Icon Information $IconInfoObject = [PSCustomObject]@{ Name = $IconDataName Size = $IconDataSize Path = $IconPathTemp } # Add to Icon Information List $IconInformation.Add($IconInfoObject) } } While ($IconRecord) # Close the IconData view $IconData.GetType().InvokeMember("Close", "InvokeMethod", $null, $IconData, $null) | Out-Null } } catch { Write-Verbose "Error retrieving Icons: $_" } Return $IconInformation } function Set-TextboxInformation { param ( [Parameter(Mandatory = $true)] [System.Object]$MSIPropertiesInfo, [Parameter(Mandatory = $true)] [hashtable]$FileHashInfo ) # Set the MSI file properties textboxes $txt_ProductName.Text = $MSIPropertiesInfo.ProductName $txt_Manufacture.Text = $MSIPropertiesInfo.Manufacturer $txt_ProductVersion.Text = $MSIPropertiesInfo.ProductVersion $txt_ProductCode.Text = $MSIPropertiesInfo.ProductCode $txt_UpgradeCode.Text = $MSIPropertiesInfo.UpgradeCode # Set the File Hash Information textboxes $txt_MD5.Text = $FileHashInfo.MD5.Hash $txt_SHA1.Text = $FileHashInfo.SHA1.Hash $txt_SHA256.Text = $FileHashInfo.SHA256.Hash $txt_Digest.Text = $FileHashInfo.Digest } function Build-IconImageControls { param ( [Parameter(Mandatory = $true)] [PSCustomObject[]]$IconObjects ) # Start Building PSCustomObject [Collections.Generic.List[PSCustomObject]]$Script:IconImageControlsList = @() # Loop through each Icon $IconIndex = 0 foreach ($IconObject in $IconObjects) { if ((Test-Path ($IconObject.Path))) { # IconBitmap Export File Path $IconBitmapPath = Join-Path -Path "$($IconObject.Path | Split-Path -Parent)" -ChildPath "$([System.IO.Path]::GetFileNameWithoutExtension($IconObject.Name))_Export.ico" # Extract the Binary file to a Bitmap File at 256px if ($Script:ScriptPSVersion -lt [Version]"7.4") { # This is the only method available in .NET 4.x.x $IconExtract = [System.Drawing.Icon]::ExtractAssociatedIcon($IconObject.Path) } else { # This uses newer .NET methods from 8+ and results in higher quality images $IconExtract = [System.Drawing.Icon]::ExtractIcon($IconObject.Path, 0, 256) } # Delete any existing ICO file #Remove-Item -Path "$($IconBitmapPath)" -Force -ErrorAction SilentlyContinue | Out-Null # Save the Extracted icon to a bitmap file $IconExtract.ToBitmap().Save("$($IconBitmapPath)") # Create BitmapImage from the Bitmap file $Bitmap = New-Object System.Windows.Media.Imaging.BitmapImage $Bitmap.BeginInit() $Bitmap.StreamSource = [System.IO.MemoryStream]::new([System.IO.File]::ReadAllBytes("$($IconBitmapPath)")) $Bitmap.CacheOption = [System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad $Bitmap.EndInit() $Bitmap.Freeze() # Create a New Image Control in the Grid 'grid_Icon' $ImageControlIcon = New-Object System.Windows.Controls.Image $ImageControlIcon.SetValue([System.Windows.Controls.Control]::NameProperty, "img_Icon_$(([System.IO.Path]::GetFileNameWithoutExtension($IconObject.Name)) -replace '[^a-zA-Z0-9]', '_')") $ImageControlIcon.Source = $Bitmap $ImageControlIcon.SetValue([System.Windows.Controls.Grid]::RowProperty, 0) $ToolTip = New-Object System.Windows.Controls.ToolTip if ($Script:ScriptPSVersion -lt [Version]"7.4") { $ToolTip.Content = "Click to Export.`nUse PowerShell 7.4 or higher for better quality." $ToolTip.Background = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.Colors]::LightGoldenrodYellow) $ToolTip.Foreground = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.Colors]::Red) } else { $ToolTip.Content = "Click to Export" } $ImageControlIcon.ToolTip = $ToolTip $ImageControlIcon.SetValue([System.Windows.Controls.ToolTipService]::InitialShowDelayProperty, 100) $ImageControlIcon.HorizontalAlignment = "Center" $ImageControlIcon.VerticalAlignment = "Center" $ImageControlIcon.Visibility = "Collapsed" $ImageControlIcon.add_mouseleftbuttonup($Button_ExportToPNG_Handler) # Add the Image Control to the Grid 'grid_Icon' $grid_Icon.Children.Add($ImageControlIcon) # Create PSCustomObject for the Image Control Information $ImageControlIconInfo = [PSCustomObject]@{ Index = $IconIndex Name = $ImageControlIcon.Name IconName = "$([System.IO.Path]::GetFileNameWithoutExtension($IconObject.Name))" IconBinaryPath = "$($IconObject.Path)" IconBitmapPath = "$($IconBitmapPath)" Control = $ImageControlIcon } # Add to IconImageControlsList $Script:IconImageControlsList.Add($ImageControlIconInfo) } # Increment the Icon Index $IconIndex++ } } function Set-IconImageNavigation { [CmdletBinding(DefaultParameterSetName = 'New')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'New')] [Parameter(Mandatory = $true, ParameterSetName = 'Next')] [Parameter(Mandatory = $true, ParameterSetName = 'Previous')] [PSCustomObject[]]$IconObjects, [Parameter(Mandatory = $false, ParameterSetName = 'Next')] [switch]$Next, [Parameter(Mandatory = $false, ParameterSetName = 'Previous')] [switch]$Previous ) # IconImageControlsList Count $IconCount = $Script:IconImageControlsList.Count # Get the Current Icon Index if ($null -eq $Script:CurrentIconIndex) { $Script:CurrentIconIndex = 0 } $CurrentIndex = $Script:CurrentIconIndex if ($Next) { # Increment the Index $NewIndex = $CurrentIndex + 1 } elseif ($Previous) { # Decrement the Index $NewIndex = $CurrentIndex - 1 } else { # If neither Next nor Previous is specified, keep the current index $NewIndex = $CurrentIndex } # Set GlobalIndex $Script:CurrentIconIndex = $NewIndex # Hide the 'No Icon' Label $lbl_NoIcon.Visibility = "Collapsed" # Hide the current Icon ($Script:IconImageControlsList | Where-Object { $_.Index -eq $CurrentIndex }).Control.Visibility = "Hidden" # Show the new Icon ($Script:IconImageControlsList | Where-Object { $_.Index -eq $NewIndex }).Control.Visibility = "Visible" # Set the Navigation button states if ($IconCount -gt 1) { if ($NewIndex -eq 0) { $btn_IconPrevious.IsEnabled = $false $btn_IconNext.IsEnabled = $true } elseif ($NewIndex -eq ($IconCount - 1)) { $btn_IconPrevious.IsEnabled = $true $btn_IconNext.IsEnabled = $false } else { $btn_IconPrevious.IsEnabled = $true $btn_IconNext.IsEnabled = $true } } } function Set-IconImage { param ( [Parameter(Mandatory = $true)] [PSCustomObject[]]$IconObjects ) # Build the Icon Image Controls Build-IconImageControls -IconObjects $IconObjects # Set the Icon Image Navigation Set-IconImageNavigation -IconObjects $Script:IconImageControlsList } function Clear-IconImageControls { # Clear the Script Scoped Variables Remove-Variable -Name IconImageControlsList -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name CurrentIconIndex -Scope Script -ErrorAction SilentlyContinue # Reload all Icon Image Controls on the form into variables $formMSIProperties.FindName("grid_Icon").Children | Where-Object { $_.Name -like "img_Icon*" } | ForEach-Object { Set-Variable -Name $_.Name -Value $_ -Scope Script } # Get all Icon Image Controls that start with the name 'img_Icon' and Hide them $IconImageControls = Get-Variable -Name "img_Icon*" -Scope Script -ErrorAction SilentlyContinue if ($null -ne $IconImageControls) { foreach ($IconImageControl in $IconImageControls) { $IconImageControl.Value.Visibility = "Collapsed" } } # Show the No Icon Label Visibility $lbl_NoIcon.Visibility = "Visible" # Disable the Icon Navigation buttons $btn_IconPrevious.IsEnabled = $false $btn_IconNext.IsEnabled = $false } function Invoke-FormReset { # Clear the Icon Image Controls Clear-IconImageControls # Clear the Textboxes Clear-Textboxes # Clear and Reset the listbox font style $lsbox_FilePath.Items.Clear() $lsbox_FilePath.ClearValue([System.Windows.Controls.Control]::BackgroundProperty) $lsbox_FilePath.ClearValue([System.Windows.Controls.Control]::ForegroundProperty) $lsbox_FilePath.ClearValue([System.Windows.Controls.Control]::FontWeightProperty) $lsbox_FilePath.ClearValue([System.Windows.Controls.Control]::FontSizeProperty) # Disable all buttons Disable-AllButtons } function Invoke-GetMSIInformation { param ( [Parameter(Mandatory = $true)] [IO.FileInfo[]]$MSIPath ) # Reset the form Invoke-FormReset # Get the MSI file properties $FileMSIInfo = Get-MsiProperties -Path $MSIPath # Get the File Hash Information $HashInfo = Get-FileHashInformation -Path $MSIPath # Populate the textboxes Set-TextboxInformation -MSIPropertiesInfo $FileMSIInfo -FileHashInfo $HashInfo # Extract and display the Icons $IconObjects = Get-MsiIcon -Path $MSIPath if ($null -ne $IconObjects) { Set-IconImage -IconObjects $IconObjects } # Enable the Copy buttons Enable-AllButtons -Exclude "Icon" # Clear the listbox and add the filename $lsbox_FilePath.Items.Clear() $lsbox_FilePath.Items.Add($MSIPath[0]) # Remove lock on current file [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } function Invoke-LaunchAsPwsh { # Are we running as PowerShell 7.4 or higher if ($Script:ScriptPSVersion -ge [Version]"7.4") { Write-Host "Running PowerShell 7.4 or higher" } else { # Check that PowerShell is installed if ($Script:PowerShellPath) { # Check that it is version 7.4 or higher if ($Script:PowerShellPath.Version -lt [Version]"7.4") { Write-Host "Installed PowerShell (pwsh.exe) version [$($Script:PowerShellPath.Version)] is lower than 7.4. Continuing with existing PowerShell version [$($Script:ScriptPSVersion)]..." } else { # Start PowerShell and run the script Write-Host "Relaunching script in PowerShell (pwsh.exe)..." if ($PSCommandPath -ne "") { Start-Process -FilePath $Script:PowerShellPath -ArgumentList "-NoProfile -ExecutionPolicy Bypass -WindowStyle Minimized -File `"$PSCommandPath`" -FilePath `"$FilePath`"" } else { Start-Process -FilePath $Script:PowerShellPath -ArgumentList "-NoProfile -ExecutionPolicy Bypass -WindowStyle Minimized -Command `"Invoke-Expression (Invoke-RestMethod 'https://raw.githubusercontent.com/MichaelEscamilla/GetMSIInformation/main/GetMSIInformation.ps1')`"" } Exit } } else { Write-Host "PowerShell (pwsh.exe) is not installed on this system." } } } #endregion Functions ############################################# ################# Main Script ################ ############################################# # Load Assemblies Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName System.Windows.Forms # Build the GUI [xml]$XAMLformMSIProperties = @"