How to Use GetMSIVersion to Check Installer Packages Managing software deployments requires precise control over application versions. System administrators and developers frequently need to verify the exact version of a Windows Installer (.msi) package before distributing it across a network. Manual verification by right-clicking the file and checking properties is inefficient for automation. The GetMSIVersion function provides a reliable, programmatic way to extract this metadata using PowerShell.
This guide demonstrates how to use Windows Installer COM objects within PowerShell to build a custom GetMSIVersion tool to inspect your installer packages. Why Extract MSI Versions Programmatically?
Relying on file properties or deployment logs can introduce errors during mass rollouts. Inspecting the MSI database directly offers several distinct advantages:
Deployment Validation: Ensures the correct software version is staged in your deployment shares.
Upgrade Logic: Helps scripts determine if a package is newer than the currently installed version.
Automation Friendly: Allows integration into Continuous Integration/Continuous Deployment (CI/CD) pipelines or Microsoft Endpoint Configuration Manager (SCCM) scripts. Building the GetMSIVersion Function
The Windows operating system does not include a native, standalone Get-MsiVersion cmdlet. Instead, you can leverage the WindowsInstaller.Installer COM object inside a PowerShell function to read the MSI database directly. Here is a standard implementation of the function: powershell
function Get-MsiVersion { [CmdletBinding()] param ( [Parameter(Mandatory = \(true, ValueFromPipeline = \)true, ValueFromPipelineByPropertyName = \(true)] [ValidateScript({ Test-Path \)_ -PathType Leaf })] [string]\(Path ) process { try { # Resolve the absolute path of the MSI file \)AbsolutePath = (Resolve-Path \(Path).Path # Instantiate the Windows Installer COM object \)Installer = New-Object -ComObject WindowsInstaller.Installer # Open the MSI database in read-only mode (0) \(Database = \)Installer.GetType().InvokeMember(“OpenDatabase”, “InvokeMethod”, \(null, \)Installer, @(\(AbsolutePath, 0)) # Query the Property table for the ProductVersion \)View = \(Database.GetType().InvokeMember("OpenView", "InvokeMethod", \)null, \(Database, "SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'") \)View.GetType().InvokeMember(“Execute”, “InvokeMethod”, \(null, \)View, \(null) # Fetch the record \)Record = \(View.GetType().InvokeMember("Fetch", "InvokeMethod", \)null, \(View, \)null) if (\(Record) { # Extract the value from the first column of the record \)Version = \(Record.GetType().InvokeMember("StringData", "GetProperty", \)null, \(Record, 1) # Output the result as a custom object [PSCustomObject]@{ Name = Split-Path \)AbsolutePath -Leaf Path = \(AbsolutePath Version = \)Version } } else { Write-Warning “Could not find ProductVersion property in \(AbsolutePath" } } catch { Write-Error "Failed to read MSI version for \)Path. Error: \(_" } finally { # Explicitly release COM objects from memory if (\)Record) { [System.Runtime.InteropServices.Marshal]::ReleaseComObject(\(Record) | Out-Null } if (\)View) { [System.Runtime.InteropServices.Marshal]::ReleaseComObject(\(View) | Out-Null } if (\)Database) { [System.Runtime.InteropServices.Marshal]::ReleaseComObject(\(Database) | Out-Null } if (\)Installer) { [System.Runtime.InteropServices.Marshal]::ReleaseComObject(\(Installer) | Out-Null } [GC]::Collect() [GC]::WaitForPendingFinalizers() } } } </code> Use code with caution. How the Script Works</p> <p>The function executes a few critical steps to safely pull data from the installer file:</p> <p><strong>Path Resolution:</strong> It converts relative paths into absolute paths so the COM object can locate the file accurately.</p> <p><strong>Database Query:</strong> It opens the internal SQL-like database of the MSI file in a read-only state, protecting the package from corruption.</p> <p><strong>Property Extraction:</strong> It executes a target query (<code>SELECT Value FROM Property WHERE Property='ProductVersion'</code>) to isolate the exact version string assigned by the software developer.</p> <p><strong>Memory Management:</strong> COM objects do not clear automatically in PowerShell. The <code>finally</code> block explicitly releases these objects to prevent the MSI file from remaining locked in the file system. Practical Usage Examples</p> <p>Once you load the function into your PowerShell session, you can apply it to various administrative workflows. <strong>Checking a Single Local MSI File</strong> powershell <code>Get-MsiVersion -Path "C:\Software\SlackDeployment.msi" </code> Use code with caution.</p> <p><strong>Auditing an Entire Directory of Installers</strong>If you manage a repository of deployment files, combine the function with <code>Get-ChildItem</code> to generate a complete version report: powershell</p> <p><code>Get-ChildItem "D:\DeploymentShare\Packages\" -Filter.msi | Get-MsiVersion | Format-Table -AutoSize </code> Use code with caution.</p> <p><strong>Filtering for Specific Versions</strong>You can filter your installer library to identify outdated packages that require updating: powershell</p> <p><code>Get-ChildItem "D:\DeploymentShare\Packages\" -Filter *.msi | Get-MsiVersion | Where-Object { \)_.Version -lt “5.0.0” } Use code with caution.
Using this programmatic approach to check installer packages guarantees consistency, eliminates manual inspection errors, and fits perfectly into any enterprise automation framework. If you want to expand this script, let me know:
Do you need to extract other properties like ProductCode or UpgradeCode?
Leave a Reply