Install-FromManifest: WaitTimeoutSec for EXE entries (lib v2.5)

UDC_Setup.exe is a WiX Burn bootstrapper that installs the underlying
MSI cleanly with /quiet but the wrapper process never exits - waits on
a bundled child service that doesn't return control. Empirically:
DisplayVersion=1.0.34 + UDC.exe present in C:\Program Files\UDC after
~30s, but Process.WaitForExit blocks indefinitely (>5 min observed).

EXE handler now honors optional WaitTimeoutSec on the manifest entry.
After timeout, kills the wrapper, re-runs Test-Installed; if detection
passes, treats as success (rc 0); if fail, surfaces as -2 (distinct
from -1 Process.Start failure). Default unset = old behavior (block
forever via WaitForExit) so existing entries unaffected.

Pairs with UDC manifest entry update on the v2 share:
  InstallArgs:    "West Jefferson" 9999  ->  /quiet /norestart
  + WaitTimeoutSec: 120

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-04 14:27:23 -04:00
parent f2123f268e
commit 8baae43e08

View File

@@ -38,6 +38,12 @@ $ErrorActionPreference = 'Continue'
# logged; manifests tagged with a newer MINOR are fine.
#
# Changelog:
# 2.5 - Type=EXE handler honors optional WaitTimeoutSec on the manifest
# entry. WiX Burn bootstrappers (UDC_Setup.exe) install the MSI
# successfully but the wrapper process never exits (waits on a
# bundled child service). With WaitTimeoutSec set, kill the
# wrapper after timeout, re-check Test-Installed - if pass,
# treat as success (rc 0); if fail, surface as -2.
# 2.4 - Type=EXE handler stages network-share EXEs to a local temp dir
# before invoking Process.Start. SYSTEM-context Process.Start
# fails with "Access is denied" on \\share or mapped-drive EXE
@@ -52,7 +58,7 @@ $ErrorActionPreference = 'Continue'
# 2.0 - initial Stage 2a: PS1/BAT/File/Registry/INF action types,
# Always/MarkerFile/ValueMatches/pnputil detection, PCTypes filter
$LIB_MANIFEST_MAJOR = 2
$LIB_MANIFEST_MINOR = 4
$LIB_MANIFEST_MINOR = 5
$logDir = Split-Path -Parent $LogFile
if (-not (Test-Path $logDir)) {
@@ -293,10 +299,36 @@ function Invoke-InstallerAction {
if ($App.InstallArgs) { $psi.Arguments = $App.InstallArgs }
Write-InstallLog " exe: $runPath"
if ($App.InstallArgs) { Write-InstallLog " args: $($App.InstallArgs)" }
# Optional WaitTimeoutSec on the manifest entry: some EXE wrappers
# complete the install but don't exit themselves (UDC_Setup.exe is a
# WiX Burn bootstrapper that hangs post-install waiting on a child
# service that never returns control). When set, kill the wrapper
# after the timeout AND re-check detection - if installed, treat as
# success (rc 0); else surface as failure. Default unset = old
# behavior (block forever via WaitForExit).
$waitTimeoutMs = if ($App.WaitTimeoutSec) { [int]$App.WaitTimeoutSec * 1000 } else { -1 }
try {
$proc = [System.Diagnostics.Process]::Start($psi)
$proc.WaitForExit()
$exitCode = $proc.ExitCode
if ($waitTimeoutMs -gt 0) {
$exited = $proc.WaitForExit($waitTimeoutMs)
if (-not $exited) {
Write-InstallLog " WaitTimeoutSec=$($App.WaitTimeoutSec) reached - killing wrapper, will re-check detection" 'WARN'
try { $proc.Kill(); $proc.WaitForExit(5000) | Out-Null } catch {}
Start-Sleep -Seconds 2
if (Test-Installed -App $App) {
Write-InstallLog " detection passes post-kill - treating as success"
$exitCode = 0
} else {
Write-InstallLog " detection still missing post-kill - failure" 'ERROR'
$exitCode = -2
}
} else {
$exitCode = $proc.ExitCode
}
} else {
$proc.WaitForExit()
$exitCode = $proc.ExitCode
}
} catch {
Write-InstallLog " Process.Start failed: $_" 'ERROR'
$exitCode = -1