commandline.info

PowerShell, MSI und PSADT

Praktische Snippets fuer Deployment Engineers: stille Installation, COM-Auswertung, Detection und PSADT-Ablaufmuster.

MSI Installation robust skripten

param(
    [Parameter(Mandatory)]
    [string]$MsiPath
)

$logPath = "C:\Temp\$([IO.Path]::GetFileNameWithoutExtension($MsiPath))-install.log"
$args = "/i `"$MsiPath`" /qn /norestart ALLUSERS=1 /L*v `"$logPath`""

$process = Start-Process -FilePath 'msiexec.exe' -ArgumentList $args -Wait -PassThru
switch ($process.ExitCode) {
    0    { 'OK' }
    3010 { 'OK - reboot required' }
    1618 { throw 'Another installation is running.' }
    default { throw "MSI failed with code $($process.ExitCode). Log: $logPath" }
}

MSI Eigenschaften ueber COM auslesen

function Get-MsiProperty {
    param(
        [Parameter(Mandatory)][string]$Path,
        [Parameter(Mandatory)][string]$Property
    )

    $installer = New-Object -ComObject WindowsInstaller.Installer
    $database  = $installer.GetType().InvokeMember('OpenDatabase','InvokeMethod',$null,$installer,@($Path,0))
    $query = "SELECT `Value` FROM `Property` WHERE `Property`='$Property'"
    $view = $database.GetType().InvokeMember('OpenView','InvokeMethod',$null,$database,@($query))
    $null = $view.GetType().InvokeMember('Execute','InvokeMethod',$null,$view,$null)
    $record = $view.GetType().InvokeMember('Fetch','InvokeMethod',$null,$view,$null)
    if (-not $record) { return $null }
    return $record.GetType().InvokeMember('StringData','GetProperty',$null,$record,1)
}

$path = 'C:\Packages\App\app.msi'
[pscustomobject]@{
    ProductName    = Get-MsiProperty -Path $path -Property 'ProductName'
    ProductVersion = Get-MsiProperty -Path $path -Property 'ProductVersion'
    ProductCode    = Get-MsiProperty -Path $path -Property 'ProductCode'
}

PSADT Praxispatterns

1) Install + User Experience

## Deploy-Application.ps1 (Auszug)
Show-InstallationWelcome -CloseApps 'teams,outlook' -AllowDefer -DeferTimes 2
Show-InstallationProgress
Execute-MSI -Action Install -Path 'Files\app.msi' -Parameters 'REBOOT=ReallySuppress ALLUSERS=1'
Exit-Script -ExitCode $mainExitCode

2) Uninstall via ProductCode

$app = Get-InstalledApplication -Name 'Contoso App'
if ($app) {
    Execute-MSI -Action Uninstall -Path $app.ProductCode -Parameters '/qn /norestart'
}
Exit-Script -ExitCode 0

3) Repair bei korruptem Setup

if (Get-InstalledApplication -Name 'Contoso App') {
    Write-Log -Message 'Starting repair.' -Severity 1
    Execute-MSI -Action Repair -Path '{11111111-2222-3333-4444-555555555555}' -Parameters '/qn'
}
Exit-Script -ExitCode $mainExitCode

4) Pre-Check vor Installation

$minGb = 2
$freeGb = [math]::Round((Get-PSDrive -Name C).Free / 1GB, 2)
if ($freeGb -lt $minGb) {
    Show-InstallationPrompt -Message "Nicht genug Speicher: $freeGb GB frei." -Icon Stop
    Exit-Script -ExitCode 60001
}
Execute-MSI -Action Install -Path 'Files\app.msi' -Parameters '/qn'

Empfohlene Paketstruktur (PSADT)

.
|-- Deploy-Application.exe
|-- Deploy-Application.ps1
|-- AppDeployToolkit
|-- AppDeployToolkitExtensions.ps1
`-- Files
    `-- app.msi