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:

C:\> msiexec.exe /i [PATH_TO_MY_MSI]  PROPERTY1=[MY_CUSTOM_VALUE1] PROPERTY2=[MY_CUSTOM_VALUE2]

function Get-MsiInfo {
    param(
        [parameter(Mandatory=$True, ValueFromPipeline=$true)]
        [IO.FileInfo[]]$Path,
        [AllowEmptyString()]
        [AllowNull()]
        [string[]]$Property
)
<#
            .SYNOPSIS
            Queries parameter information from one or more MSI files

            .DESCRIPTION
            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

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

            --------------------

            Gets specific properties for all MSIs in the current directory

            .EXAMPLE
            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)           

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

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

    }
}
Advertisements