Vaporup.Tools.psm1

function Get-MsiMetadata {
    <#
    .SYNOPSIS
        Extracts metadata and product properties from MSI files in a specified folder.
    .DESCRIPTION
        Scans a directory for .msi files and reads internal database properties
        including Product Name, Version, Manufacturer, Product Code (GUID), and Upgrade Code.
    .PARAMETER Path
        The directory containing the MSI files. Defaults to the current directory if omitted.
    .EXAMPLE
        Get-MsiMetadata -Path "C:\Installers"
    .EXAMPLE
        Get-MsiMetadata
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateScript({ Test-Path $_ -PathType Container })]
        [string]$Path = $PWD
    )

    begin {
        # Initialize the Windows Installer COM object once
        try {
            $installer = New-Object -ComObject WindowsInstaller.Installer
        }
        catch {
            Write-Error "Failed to create Windows Installer COM object: $_"
            return
        }
        
        $propertiesToExtract = @('ProductName', 'ProductVersion', 'Manufacturer', 'ProductCode', 'UpgradeCode')
    }

    process {
        $resolvedPath = Resolve-Path -Path $Path

        Get-ChildItem -Path $resolvedPath -Filter "*.msi" | ForEach-Object {
            $msiFile = $_
            
            $msiData = [ordered]@{
                FileName = $msiFile.Name
                FullPath = $msiFile.FullName
            }

            $database = $null
            $view = $null

            try {
                # Open the database for read-only access (Mode 0)
                $database = $installer.OpenDatabase($msiFile.FullName, 0)
                
                foreach ($prop in $propertiesToExtract) {
                    $view = $database.OpenView("SELECT Value FROM Property WHERE Property = '$prop'")
                    $view.Execute()
                    $record = $view.Fetch()
                    
                    if ($record) {
                        $msiData[$prop] = $record.StringData(1)
                    } else {
                        $msiData[$prop] = "N/A"
                    }
                    
                    if ($view) { 
                        [System.Runtime.InteropServices.Marshal]::ReleaseComObject($view) | Out-Null
                        $view = $null
                    }
                }
                
                [PSCustomObject]$msiData
            }
            catch {
                Write-Warning "Could not read metadata from $($msiFile.Name): $_"
            }
            finally {
                # Clean up COM references per file
                if ($view) { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($view) | Out-Null }
                if ($database) { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($database) | Out-Null }
            }
        }
    }

    end {
        if ($installer) { 
            [System.Runtime.InteropServices.Marshal]::ReleaseComObject($installer) | Out-Null 
        }
    }
}