Private/Invoke-PpdsProcess.ps1
|
function Invoke-PpdsProcess { <# .SYNOPSIS Runs the CLI with separate stdout/stderr streams and an exact argument list. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$FilePath, [Parameter(Mandatory)] [string[]]$Arguments, [switch]$Interactive, [scriptblock]$StandardErrorHandler ) $startInfo = [System.Diagnostics.ProcessStartInfo]::new() $startInfo.FileName = $FilePath $location = Get-Location if ($location.Provider.Name -ne 'FileSystem') { throw 'PPDS CLI requires a filesystem working directory.' } # PowerShell's current location can differ from the host process directory. $startInfo.WorkingDirectory = $location.ProviderPath $startInfo.UseShellExecute = $false $startInfo.CreateNoWindow = -not $Interactive # Also supports a packaged .NET tool payload for offline contract tests. if ([System.IO.Path]::GetExtension($FilePath) -eq '.dll') { $startInfo.FileName = 'dotnet' $startInfo.ArgumentList.Add($FilePath) } foreach ($argument in $Arguments) { $startInfo.ArgumentList.Add($argument) } $startInfo.RedirectStandardOutput = -not $Interactive $startInfo.RedirectStandardError = -not $Interactive $process = [System.Diagnostics.Process]::new() $process.StartInfo = $startInfo $started = $false try { $started = $process.Start() if (-not $started) { throw 'Could not start PPDS CLI.' } if ($Interactive) { while (-not $process.HasExited) { Start-Sleep -Milliseconds 20 } return [PSCustomObject]@{ ExitCode = $process.ExitCode; StdOut = ''; StdErr = '' } } # Drain both pipes concurrently to avoid blocking on a full stderr buffer. # Never wait synchronously on process I/O: PowerShell must be able to stop # this pipeline and reach the finally block while the CLI is still running. $stdoutTask = $process.StandardOutput.ReadToEndAsync() if ($StandardErrorHandler) { $stderr = [System.Text.StringBuilder]::new() $stderrTask = $process.StandardError.ReadLineAsync() } else { $stderrTask = $process.StandardError.ReadToEndAsync() } $stderrComplete = $false while ($true) { if ($StandardErrorHandler) { while (-not $stderrComplete -and $stderrTask.IsCompleted) { $line = $stderrTask.GetAwaiter().GetResult() if ($null -eq $line) { $stderrComplete = $true } else { [void]$stderr.AppendLine($line) & $StandardErrorHandler $line $stderrTask = $process.StandardError.ReadLineAsync() } } } else { $stderrComplete = $stderrTask.IsCompleted } if ($process.HasExited -and $stdoutTask.IsCompleted -and $stderrComplete) { break } Start-Sleep -Milliseconds 20 } return [PSCustomObject]@{ ExitCode = $process.ExitCode StdOut = $stdoutTask.GetAwaiter().GetResult() StdErr = if ($StandardErrorHandler) { $stderr.ToString() } else { $stderrTask.GetAwaiter().GetResult() } } } finally { if ($started -and -not $process.HasExited) { $process.Kill($true) } $process.Dispose() } } |