From 8baae43e08524131fbca2ae889fedc6d17fea9b8 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Mon, 4 May 2026 14:27:23 -0400 Subject: [PATCH] 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) --- .../common/lib/Install-FromManifest.ps1 | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 b/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 index 91b616d..d262587 100644 --- a/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 +++ b/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 @@ -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