From 70a36711c339769ad87724c339199c6117b685b4 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Wed, 22 Apr 2026 12:17:34 -0400 Subject: [PATCH] Install-FromManifest: InUseCheck ForceClose / CloseAndReopen Adds partial Stage 2b support for InUseCheck entries in manifests. When an entry declares InUseCheck.Behavior = ForceClose or CloseAndReopen and the listed processes are running at install time, the lib now: 1. Calls CloseMainWindow() on each matching Process handle (polite WM_CLOSE). 2. Waits GracefulCloseTimeoutSec (default 10) for exit. 3. Hard-kills the process if it did not exit gracefully. 4. Proceeds with the install. "CloseAndReopen" is currently treated the same as ForceClose - no reopen happens today. Stage 2b will add the user-session scheduled-task trick to relaunch the closed app in the logged-in user's session. In practice for the 24/7 ShopFloor persistent-user pattern the operator relaunches the app manually (or the app is registered as Startup and reopens on the next reboot), which is acceptable. Concrete impact: the eDNC entry in standard-machine/manifest.json lists InUseCheck.Processes = DncMain + NTLARS with Behavior=CloseAndReopen. On a retrofit or upgrade cycle that finds eDNC 6.4.3 needs to go to 6.4.5, the lib now force-closes DncMain and NTLARS before msiexec rather than risking Restart Manager silently scheduling a pending-file-replace that does not actually upgrade until the next reboot (which on a 24/7 PC might be never). Verified in the Win11 analyzer VM against manifests declaring InUseCheck on eDNC - logs show "InUseCheck: DncMain (PID ...) asked to close" followed by either graceful exit or the force-kill path, then install proceeds without 3010. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../common/lib/Install-FromManifest.ps1 | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 b/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 index 428eff6..04ea5ae 100644 --- a/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 +++ b/playbook/shopfloor-setup/common/lib/Install-FromManifest.ps1 @@ -416,6 +416,33 @@ foreach ($app in $config.Applications) { continue } + # --- InUseCheck (partial Stage 2b): ForceClose / CloseAndReopen --- + # Before install, if the entry declares processes that would block the + # install, close them. Stage 2a supports ForceClose: polite WM_CLOSE + # with a timeout then hard Kill. "CloseAndReopen" is currently treated + # the same (Stage 2b will add the user-session relaunch trick). No + # reopen happens today; operator relaunches the app or the enforcer + # leaves it for Windows Explorer / Start-Menu shortcut behavior. + if ($app.InUseCheck -and $app.InUseCheck.Behavior -in @('ForceClose','CloseAndReopen')) { + foreach ($p in ($app.InUseCheck.Processes | Where-Object { $_ })) { + $procs = Get-Process -Name $p.Name -ErrorAction SilentlyContinue + foreach ($proc in $procs) { + $timeout = if ($p.GracefulCloseTimeoutSec) { [int]$p.GracefulCloseTimeoutSec } else { 10 } + try { + Write-InstallLog " InUseCheck: $($p.Name) (PID $($proc.Id)) asked to close (timeout ${timeout}s)" + $null = $proc.CloseMainWindow() + if (-not $proc.WaitForExit([int]($timeout * 1000))) { + Write-InstallLog " InUseCheck: $($p.Name) did not exit gracefully - killing" 'WARN' + $proc.Kill() + $proc.WaitForExit(5000) | Out-Null + } + } catch { + Write-InstallLog " InUseCheck: close/kill of $($p.Name) threw: $_" 'WARN' + } + } + } + } + $result = Invoke-InstallerAction -App $app $rc = $result.ExitCode