Windows: add Run-Executable-With-Timeout and mitigate Notepad++ hang

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 <exe> 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 <elias.toivola@qt.io>
Reviewed-by: Simo Fält <simo.falt@qt.io>
This commit is contained in:
Joerg Bornemann
2026-05-06 09:19:07 +02:00
parent 42370c3e0b
commit 02df2a107d
2 changed files with 37 additions and 7 deletions

View File

@@ -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)

View File

@@ -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"