Public/Get-MSIProperty.ps1
|
function Get-MSIProperty { <# .SYNOPSIS Reads the Property table of a Windows Installer (.msi) database. .DESCRIPTION The Get-MSIProperty function opens an MSI database read-only with the WindowsInstaller.Installer COM object and returns every row of its Property table (for example ProductName, ProductVersion, ProductCode and Manufacturer) as the properties of a single object. This function needs Windows, because it uses the Windows Installer COM object. The alias Get-MSIProperties is kept for compatibility with earlier versions. .PARAMETER Path The path to the .msi file. Accepts pipeline input, including FileInfo objects from Get-ChildItem. .INPUTS System.String System.IO.FileInfo .OUTPUTS System.Management.Automation.PSCustomObject .EXAMPLE Get-MSIProperty -Path .\setup.msi | Select-Object ProductName, ProductVersion, ProductCode Returns the product name, version and product code of setup.msi. .EXAMPLE Get-ChildItem -Path C:\Installers -Filter *.msi | Get-MSIProperty Returns the properties of every MSI file in C:\Installers. .NOTES Author: Nigel Tatschner #> [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = 'MSI database file name')] [ValidateScript({ if (-not (Test-Path -Path $_ -PathType Leaf)) { throw "Could not find $_" } $true })] [Alias('Filename', 'MSIDbName', 'Database', 'Msi', 'FullName')] [string]$Path ) process { $TelemetryArgs = @{ ModuleName = $MyInvocation.MyCommand.Module.Name ModuleVersion = [string]$MyInvocation.MyCommand.Module.Version CommandName = $MyInvocation.MyCommand.Name ExecutionID = [guid]::NewGuid().ToString() } Invoke-TelemetryCollection @TelemetryArgs -Stage Start -ClearTimer $WindowsInstaller = $null $Database = $null $View = $null try { $FullPath = (Get-Item -Path $Path -ErrorAction Stop).FullName $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer -ErrorAction Stop # Open mode 0 = msiOpenDatabaseModeReadOnly $Database = $WindowsInstaller.GetType().InvokeMember('OpenDatabase', 'InvokeMethod', $null, $WindowsInstaller, @($FullPath, 0)) $View = $Database.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $Database, @('SELECT Property, Value FROM Property')) $null = $View.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $View, $null) $Results = [ordered]@{} $Row = $View.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $View, $null) while ($null -ne $Row) { $Name = $Row.GetType().InvokeMember('StringData', 'GetProperty', $null, $Row, 1) $Value = $Row.GetType().InvokeMember('StringData', 'GetProperty', $null, $Row, 2) $Results[$Name] = $Value $Row = $View.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $View, $null) } $null = $View.GetType().InvokeMember('Close', 'InvokeMethod', $null, $View, $null) [PSCustomObject]$Results Invoke-TelemetryCollection @TelemetryArgs -Stage End } catch { Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $_ Write-Error -Message "Failed to read the MSI database '$Path': $($_.Exception.Message)" -Exception $_.Exception } finally { # Release the COM objects so the MSI file is not left locked foreach ($ComObject in @($View, $Database, $WindowsInstaller)) { if ($null -ne $ComObject) { $null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($ComObject) } } } } } |