How to capture process output asynchronously in powershell?

后端 未结 5 2058
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-05 11:10

I want to capture stdout and stderr from a process that I start in a Powershell script and display it asynchronously to the console. I\'ve found some documentation on doing

5条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-12-05 11:30

    Unfortunately asynchronous reading is not that easy if you want to do it properly. If you call WaitForExit() without timeout you could use something like this function I wrote (based on C# code):

    function Invoke-Executable {
        # Runs the specified executable and captures its exit code, stdout
        # and stderr.
        # Returns: custom object.
        param(
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [String]$sExeFile,
            [Parameter(Mandatory=$false)]
            [String[]]$cArgs,
            [Parameter(Mandatory=$false)]
            [String]$sVerb
        )
    
        # Setting process invocation parameters.
        $oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
        $oPsi.CreateNoWindow = $true
        $oPsi.UseShellExecute = $false
        $oPsi.RedirectStandardOutput = $true
        $oPsi.RedirectStandardError = $true
        $oPsi.FileName = $sExeFile
        if (! [String]::IsNullOrEmpty($cArgs)) {
            $oPsi.Arguments = $cArgs
        }
        if (! [String]::IsNullOrEmpty($sVerb)) {
            $oPsi.Verb = $sVerb
        }
    
        # Creating process object.
        $oProcess = New-Object -TypeName System.Diagnostics.Process
        $oProcess.StartInfo = $oPsi
    
        # Creating string builders to store stdout and stderr.
        $oStdOutBuilder = New-Object -TypeName System.Text.StringBuilder
        $oStdErrBuilder = New-Object -TypeName System.Text.StringBuilder
    
        # Adding event handers for stdout and stderr.
        $sScripBlock = {
            if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
                $Event.MessageData.AppendLine($EventArgs.Data)
            }
        }
        $oStdOutEvent = Register-ObjectEvent -InputObject $oProcess `
            -Action $sScripBlock -EventName 'OutputDataReceived' `
            -MessageData $oStdOutBuilder
        $oStdErrEvent = Register-ObjectEvent -InputObject $oProcess `
            -Action $sScripBlock -EventName 'ErrorDataReceived' `
            -MessageData $oStdErrBuilder
    
        # Starting process.
        [Void]$oProcess.Start()
        $oProcess.BeginOutputReadLine()
        $oProcess.BeginErrorReadLine()
        [Void]$oProcess.WaitForExit()
    
        # Unregistering events to retrieve process output.
        Unregister-Event -SourceIdentifier $oStdOutEvent.Name
        Unregister-Event -SourceIdentifier $oStdErrEvent.Name
    
        $oResult = New-Object -TypeName PSObject -Property ([Ordered]@{
            "ExeFile"  = $sExeFile;
            "Args"     = $cArgs -join " ";
            "ExitCode" = $oProcess.ExitCode;
            "StdOut"   = $oStdOutBuilder.ToString().Trim();
            "StdErr"   = $oStdErrBuilder.ToString().Trim()
        })
    
        return $oResult
    }
    

    It captures stdout, stderr and exit code. Example usage:

    $oResult = Invoke-Executable -sExeFile 'ping.exe' -cArgs @('8.8.8.8', '-a')
    $oResult | Format-List -Force 
    

    For more info and alternative implementations (in C#) read this blog post.

提交回复
热议问题