Get all properties from MSI installer files

windowsinstallericonThere are times when you need to specify custom values for MSI properties.  If the MSI is from a third party or you do not have the original installer code, it would be nice to be able to get a full listing of all properties exposed by an MSI.  This PowerShell function uses the COM-based WindowsInstaller API to extract properties from 1 to n MSIs.


Once you find the properties you want to provide custom values for, you can then execute msiexec.exe using the custom property values:


function Get-MsiInfo {
        [parameter(Mandatory=$True, ValueFromPipeline=$true)]
            Queries parameter information from one or more MSI files

            By default will return the ProductCode,ProductVersion,ProductName,Manufacturer,ProductLanguage,FullVersion.  If an empty string
            is provided for the Property parameter, then all properties are returned

            .PARAMETER Path
            MSI Path(s) provided either explicitly or from the pipeline

            .PARAMETER Property
            The names of the MSI properties to return.  Specify empty string to return all properties

            gci *.msi | Get-MsiInfo -Property 'ProductName','ProductVersion','Manufacturer'


            Gets specific properties for all MSIs in the current directory

            gci *.msi | Get-MsiInfo


            Get all properties for all MSIs in the current directory
    Begin {
        $winInstaller = New-Object -ComObject WindowsInstaller.Installer
    Process {
        try {
            $msiDb = $winInstaller.GetType().InvokeMember('OpenDatabase', 'InvokeMethod', $null, $winInstaller, @($Path.FullName, 0))
            if($Property) {
                $propQuery = 'WHERE ' + (($Property | ForEach-Object { "Property = '$($_)'"}) -join ' OR ')
            $query = ("SELECT Property,Value FROM Property {0}" -f ($propQuery))

            $view = $msiDb.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $msiDb, ($query))
            $null = $view.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $view, $null)

            $msiInfo = [PSCustomObject]@{'File' = $Path}
            do {
                $null = $view.GetType().InvokeMember('ColumnInfo', 'GetProperty', $null, $view, 0)
                $record = $view.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $view, $null)
                if(-not $record) { break; }
                $propName = $record.GetType().InvokeMember('StringData', 'GetProperty', $null, $record, 1) | select -First 1
                $value = $record.GetType().InvokeMember('StringData', 'GetProperty', $null, $record, 2) | select -First 1
                $msiInfo = $msiInfo | Add-Member -MemberType NoteProperty -Name $propName -Value $value -PassThru
            } while ($true)

            $null = $msiDb.GetType().InvokeMember('Commit', 'InvokeMethod', $null, $msiDb, $null)
            $null = $view.GetType().InvokeMember('Close', 'InvokeMethod', $null, $view, $null)           

        catch {
            Write-Warning -Message $_
            Write-Warning -Message $_.ScriptStackTrace

    End {
        try {
            $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($winInstaller)
        } catch {
            Write-Warning -Message 'Failed to release Windows Installer COM reference'
            Write-Warning -Message $_