From 02df2a107d33358ceb1efe21ed60f2db375a71d8 Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Wed, 6 May 2026 09:19:07 +0200 Subject: [PATCH] Windows: add Run-Executable-With-Timeout and mitigate Notepad++ hang MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Run-Executable-With-Timeout, a wrapper around Run-Executable that takes a timeout in seconds as its first argument. Run-Executable gains an internal -TimeoutSeconds parameter (default 0 = no timeout, all existing callers unaffected) used by the new wrapper. Apply a generous timeout to the Notepad++ silent installer, which was observed hanging for ~14 hours and causing provisioning CANCELED failures on Windows 11 24H2 machines. Cache the process handle ($null = $p.Handle) immediately after Start-Process -PassThru. Without this, $p.ExitCode reads back as $null after the process exits, which made Run-Executable spuriously throw "Process exited with exit code " (blank) on successful runs. This race was observed in provisioning logs for certutil.exe in the certificate-updates step. Change-Id: Icc66b670a3d40d67d1b6251913a06dbffeffaec2 Reviewed-by: Elias Toivola Reviewed-by: Simo Fält --- coin/provisioning/common/windows/helpers.ps1 | 42 ++++++++++++++++--- .../common/windows/install-notepad++.ps1 | 2 +- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/coin/provisioning/common/windows/helpers.ps1 b/coin/provisioning/common/windows/helpers.ps1 index 207c1892..491993d9 100644 --- a/coin/provisioning/common/windows/helpers.ps1 +++ b/coin/provisioning/common/windows/helpers.ps1 @@ -20,11 +20,22 @@ function Verify-Checksum } } +function Run-Executable-With-Timeout +{ + Param ( + [int]$TimeoutSeconds=$(throw("You must specify a timeout.")), + [string]$Executable=$(throw("You must specify a program to run.")), + [string[]]$Arguments + ) + Run-Executable -Executable $Executable -Arguments $Arguments -TimeoutSeconds $TimeoutSeconds +} + function Run-Executable { Param ( [string]$Executable=$(throw("You must specify a program to run.")), - [string[]]$Arguments + [string[]]$Arguments, + [int]$TimeoutSeconds = 0 ) $stdoutFile = [System.IO.Path]::GetTempFileName() @@ -32,13 +43,32 @@ function Run-Executable if ([string]::IsNullOrEmpty($Arguments)) { Write-Host "Running `"$Executable`"" - $p = Start-Process -FilePath "$Executable" -Wait -PassThru ` - -RedirectStandardOutput $stdoutFile -RedirectStandardError $stderrFile } else { Write-Host "Running `"$Executable`" with arguments `"$Arguments`"" - $p = Start-Process -FilePath "$Executable" -ArgumentList $Arguments -PassThru ` - -RedirectStandardOutput $stdoutFile -RedirectStandardError $stderrFile - Wait-Process -InputObject $p + } + + $startArgs = @{ + FilePath = $Executable + PassThru = $true + RedirectStandardOutput = $stdoutFile + RedirectStandardError = $stderrFile + } + if (-not [string]::IsNullOrEmpty($Arguments)) { + $startArgs.ArgumentList = $Arguments + } + $p = Start-Process @startArgs + # Force PowerShell to cache the process handle. Without this, $p.ExitCode + # can read back as $null after the process exits. + # See https://github.com/PowerShell/PowerShell/issues/5421 + $null = $p.Handle + + if ($TimeoutSeconds -gt 0) { + if (-not $p.WaitForExit($TimeoutSeconds * 1000)) { + $p.Kill() + throw "Process $($Executable) timed out after $TimeoutSeconds seconds" + } + } else { + $p.WaitForExit() } $stdoutContent = [System.IO.File]::ReadAllText($stdoutFile) diff --git a/coin/provisioning/common/windows/install-notepad++.ps1 b/coin/provisioning/common/windows/install-notepad++.ps1 index 17e86d27..e05cab93 100644 --- a/coin/provisioning/common/windows/install-notepad++.ps1 +++ b/coin/provisioning/common/windows/install-notepad++.ps1 @@ -36,7 +36,7 @@ $nppPackage = "C:\Windows\Temp\npp-$version.exe" Download $url_official $url_cache $nppPackage Verify-Checksum $nppPackage $sha1 -Run-Executable "$nppPackage" "/S" +Run-Executable-With-Timeout 1800 "$nppPackage" "/S" Write-Host "Cleaning $nppPackage.." Remove "$nppPackage"