skills/long-running-job-monitor/scripts/Wait-DetachedResult.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Waits for the result of a short detached PowerShell job. .DESCRIPTION Run through the terminal tool in async mode only. A synchronous wait blocks the chat, and a detached waiter notifies nobody. Polls in 500 ms steps with a bounded timeout. Reads only; writes and deletes nothing. .PARAMETER ResultPath The ResultPath returned by Start-DetachedPowerShell.ps1, containing 0 or 1. .PARAMETER LogPath Optional job log whose last TailLines lines are printed after the result. .PARAMETER TimeoutSeconds Maximum wait from 1 to 600 seconds. Defaults to 120 seconds. .PARAMETER TailLines Number of log lines from 0 to 500. Defaults to 40; 0 disables log reading. .EXAMPLE .\Wait-DetachedResult.ps1 -ResultPath $launch.ResultPath -LogPath $logPath Run this command through the terminal tool in async mode only. .OUTPUTS System.String DETACHED-RESULT <0|1|timeout> after <seconds> s, followed by the log tail. .NOTES Exits with 0 or 1 for the job result, or 124 on timeout. #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ResultPath, [ValidateNotNullOrEmpty()] [string] $LogPath, [ValidateRange(1, 600)] [int] $TimeoutSeconds = 120, [ValidateRange(0, 500)] [int] $TailLines = 40 ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $ResultPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ResultPath) $clock = [Diagnostics.Stopwatch]::StartNew() $outcome = 'timeout' $exitCode = 124 while ($true) { if ([IO.File]::Exists($ResultPath)) { $stream = $null $reader = $null $text = $null try { $share = [IO.FileShare]::ReadWrite -bor [IO.FileShare]::Delete $stream = [IO.File]::Open($ResultPath, [IO.FileMode]::Open, [IO.FileAccess]::Read, $share) if ($stream.Length -gt 64) { throw "ResultPath '$ResultPath' must contain 0 or 1; the file exceeds 64 bytes." } $reader = [IO.StreamReader]::new($stream, [Text.Encoding]::UTF8, $true) $text = $reader.ReadToEnd().Trim() } catch [IO.FileNotFoundException] { Write-Verbose -Message 'ResultPath disappeared during the read; waiting for completion.' } catch [IO.IOException] { $nativeCode = $_.Exception.HResult -band 0xffff if ($nativeCode -notin 32, 33) { throw } Write-Verbose -Message 'ResultPath is still held by its writer; waiting for completion.' } finally { if ($reader) { $reader.Dispose() } elseif ($stream) { $stream.Dispose() } } if ($null -ne $text -and $text.Length -gt 0) { if ($text -notin '0', '1') { throw "ResultPath '$ResultPath' must contain 0 or 1; received '$text'." } $outcome = $text $exitCode = [int]$text break } } if ($clock.Elapsed.TotalSeconds -ge $TimeoutSeconds) { break } Start-Sleep -Milliseconds 500 } $seconds = $clock.Elapsed.TotalSeconds.ToString('0.0', [cultureinfo]::InvariantCulture) 'DETACHED-RESULT {0} after {1} s' -f $outcome, $seconds if ($LogPath -and $TailLines -gt 0) { try { Get-Content -LiteralPath $LogPath -Tail $TailLines -Encoding UTF8 -ErrorAction Stop } catch [System.Management.Automation.RuntimeException] { Write-Warning -Message "Could not read detached log '$LogPath': $($_.Exception.Message)" } } exit $exitCode |