Private/Get-Win32ToolkitMsiProperty.ps1

function Get-Win32ToolkitMsiProperty {
    <#
    .SYNOPSIS
        Reads a single property from an MSI's Property table (host-side, via the WindowsInstaller COM API).
    .DESCRIPTION
        Used to recover the version-stable UpgradeCode (and ProductName / ProductCode / ProductVersion) of an
        MSI so the update requirement rule can gate on "any version of this MSI installed" (see
        Get-Win32ToolkitRequirementRule). Returns '' if the file is missing, the property is absent, or the
        COM read fails — callers treat '' as "no value". The COM object is always released.
 
        The Property name is restricted by ValidateSet, so the interpolated SQL query cannot be influenced by
        untrusted input.
    .PARAMETER Path
        Full path to the .msi file.
    .PARAMETER Property
        Which MSI property to read.
    .EXAMPLE
        Get-Win32ToolkitMsiProperty -Path 'C:\...\App.msi' -Property UpgradeCode
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory)]
        [ValidateSet('UpgradeCode', 'ProductCode', 'ProductName', 'ProductVersion')]
        [string]$Property
    )

    if (-not (Test-Path -LiteralPath $Path)) { return '' }

    $installer = $null
    try {
        $installer = New-Object -ComObject WindowsInstaller.Installer
        $db   = $installer.GetType().InvokeMember('OpenDatabase', 'InvokeMethod', $null, $installer, @($Path, 0))
        $view = $db.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $db, @("SELECT Value FROM Property WHERE Property='$Property'"))
        $view.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $view, $null) | Out-Null
        $record = $view.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $view, $null)
        if ($record) {
            return [string]$record.GetType().InvokeMember('StringData', 'GetProperty', $null, $record, @(1))
        }
        return ''
    }
    catch {
        Write-Verbose "Get-Win32ToolkitMsiProperty($Property) failed for '$Path': $($_.Exception.Message)"
        return ''
    }
    finally {
        if ($installer) { [void][System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($installer) }
    }
}