pf-WinInstaller.ps1

function Get-Uninstall {
    # paths: x86 and x64 registry keys are different
    if ([IntPtr]::Size -eq 4) {
        $path = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
    }
    else {
        $path = @(
            'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
            'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
        )
    }

    Get-ItemProperty $path
}

function Install-Msi ($file, $arguments, [Switch]$quiet, [Switch]$Repair) {
    $file = Resolve-Path $file | Get-Path
    Write-Host "Installing '$file'"
    $fileFolder = Split-Path $file -Parent
    
    $filename = [System.Io.Path]::GetFileName($file);
    New-Folder_EnsureExists $logFolder
    $logFile = "$logFolder\$filename.log"
    $logFileInQuotes = $logFile | Update-String_Enclose '"' -conditional
    $fileInQuotes = $file | Update-String_Enclose '"' -conditional

    Write-Host "Log in $logFileInQuotes"

    $flag = if ($Repair) { '/fa' } else { '/i' }
    $msiargs = @($flag, $fileInQuotes, '/l*v', $logFileInQuotes)
    if ( $quiet ) {
        $msiargs = @("/qn") + $msiargs 
    }
    $msiargs = $msiargs + $arguments
    
    $msiBat = "$file.bat"
    try {
        $msiargsTxt = $msiargs -join ' '
        # Batch file generated just to quickly reproduce errors
        "msiexec $msiargsTxt " | Out-File  -Encoding ascii -FilePath $msiBat

        Apply-Item_Conditional -path $file -action {
            Stop-Powershell_Other
            Invoke-InLocation -path $fileFolder -script {
                try {
                    Get-Process | Where-Object name -eq 'msiexec' | Stop-Process -Force -Verbose

                    $ps = Start-Process -Verb runAs -Wait -FilePath msiexec -ArgumentList $msiArgs -WorkingDirectory $fileFolder -PassThru
                    if ( $ps.ExitCode -ne 0) {
                        throw "Failed : $file"
                    }
                }
                finally {
                    Invoke-Dispose([ref]$ps)
                } 
            }
        }
        Remove-Item $msiBat
    }
    catch {
        $lastError = $_
        Write-Verbose $lastError
        $shortLogError = Get-MsiError -logFile $logFile
        $errMessage = ( @("MSI INSTALL '$file' failed", $arguments ) + $shortLogError ) -join "`n"
        throw $errMessage
    }
}

function Update-FromCompiledMsi {
    $msi = Get-ChildItem -path $src -Filter *.msi -Recurse | Sort-Object LastWriteTime | 
        Where-Object DirectoryName -NE $src | Get-Path 
    $InstallersOverride = $src
    New-Folder_EnsureExists $InstallersOverride
    $msi | Copy-Item -Destination $InstallersOverride -Force -Verbose
}