param( [Parameter(Mandatory = $true)][string]$Executable, [Parameter(Mandatory = $true)][string]$ArgumentsJsonPath, [Parameter(Mandatory = $true)][string]$CapturePath, [Parameter(Mandatory = $true)][string]$StdoutPath, [Parameter(Mandatory = $true)][string]$StderrPath, [Parameter(Mandatory = $true)][string]$StartedStatusPath, [Parameter(Mandatory = $true)][string]$ResultStatusPath ) $ErrorActionPreference = 'Stop' function Write-JsonAtomically { param( [Parameter(Mandatory = $true)][object]$Value, [Parameter(Mandatory = $true)][string]$Path ) $temporaryPath = "$Path.tmp" $json = $Value | ConvertTo-Json -Depth 8 $encoding = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($temporaryPath, $json + [Environment]::NewLine, $encoding) Move-Item -LiteralPath $temporaryPath -Destination $Path -Force } $decodedArguments = Get-Content -Raw -LiteralPath $ArgumentsJsonPath | ConvertFrom-Json if ($decodedArguments -isnot [System.Array]) { throw 'ArgumentsJsonPath must contain a JSON array.' } $childArguments = @( foreach ($argument in $decodedArguments) { [string]$argument } ) $startedAt = [DateTimeOffset]::UtcNow $finishedAt = $null $childProcess = $null $exitCode = $null $runnerError = $null $captureSize = 0 $sha256 = $null try { $startParameters = @{ FilePath = $Executable ArgumentList = $childArguments RedirectStandardOutput = $StdoutPath RedirectStandardError = $StderrPath PassThru = $true WindowStyle = 'Hidden' } $childProcess = Start-Process @startParameters # PowerShell 5 can lose ExitCode for redirected processes unless the # underlying process handle is opened before WaitForExit(). $null = $childProcess.Handle Write-JsonAtomically -Path $StartedStatusPath -Value ([ordered]@{ state = 'RUNNING' pid = $childProcess.Id started_at = $startedAt.ToString('o') capture_path = $CapturePath }) $childProcess.WaitForExit() $childProcess.Refresh() $exitCode = [int]$childProcess.ExitCode } catch { $runnerError = $_.Exception.ToString() } finally { $finishedAt = [DateTimeOffset]::UtcNow if (Test-Path -LiteralPath $CapturePath) { try { $captureSize = [int64](Get-Item -LiteralPath $CapturePath).Length $sha256 = ( Get-FileHash -LiteralPath $CapturePath -Algorithm SHA256 ).Hash.ToLowerInvariant() } catch { $additionalError = $_.Exception.ToString() if ([string]::IsNullOrWhiteSpace($runnerError)) { $runnerError = $additionalError } else { $runnerError = $runnerError + [Environment]::NewLine + $additionalError } } } Write-JsonAtomically -Path $ResultStatusPath -Value ([ordered]@{ pid = if ($null -ne $childProcess) { $childProcess.Id } else { $null } started_at = $startedAt.ToString('o') finished_at = $finishedAt.ToString('o') duration = ($finishedAt - $startedAt).TotalSeconds exit_code = $exitCode capture_path = $CapturePath capture_size = $captureSize sha256 = $sha256 runner_error = $runnerError }) } if ($null -eq $exitCode) { exit 255 } exit $exitCode