繁体   English   中英

Powershell:使用 Process 对象捕获标准输出和错误

[英]Powershell: Capturing standard out and error with Process object

我想从 PowerShell 启动一个 Java 程序并在控制台上打印结果。

我已按照此问题的说明进行操作: 使用 Start-Process 捕获标准输出和错误

但对我来说,这并没有像我预期的那样工作。 我做错了什么?

这是脚本:

$psi = New-object System.Diagnostics.ProcessStartInfo
$psi.CreateNoWindow = $true
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.FileName = 'java.exe'
$psi.Arguments = @("-jar","tools\compiler.jar","--compilation_level",   "ADVANCED_OPTIMIZATIONS", "--js", $BuildFile, "--js_output_file", $BuildMinFile)
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
$process.Start() | Out-Null
$process.WaitForExit()
$output = $process.StandardOutput.ReadToEnd()
$output

$output变量始终为空(当然控制台上不会打印任何内容)。

RedirectStandardError属性的文档建议最好将WaitForExit()调用放在ReadToEnd()调用之后。 以下对我来说是正确的:

$psi = New-object System.Diagnostics.ProcessStartInfo 
$psi.CreateNoWindow = $true 
$psi.UseShellExecute = $false 
$psi.RedirectStandardOutput = $true 
$psi.RedirectStandardError = $true 
$psi.FileName = 'ipconfig.exe' 
$psi.Arguments = @("/a") 
$process = New-Object System.Diagnostics.Process 
$process.StartInfo = $psi 
[void]$process.Start()
$output = $process.StandardOutput.ReadToEnd() 
$process.WaitForExit() 
$output

小的变化,以便您可以在需要时选择性地打印输出。 就好像您只是在寻找错误或警告消息一样,顺便说一下,基思用您的回复保存了我的培根……

$psi = New-object System.Diagnostics.ProcessStartInfo 
$psi.CreateNoWindow = $true 
$psi.UseShellExecute = $false 
$psi.RedirectStandardOutput = $true 
$psi.RedirectStandardError = $true 
$psi.FileName = 'robocopy' 
$psi.Arguments = @("$HomeDirectory $NewHomeDirectory /MIR /XF desktop.ini /XD VDI /R:0 /W:0 /s /v /np") 
$process = New-Object System.Diagnostics.Process 
$process.StartInfo = $psi 
[void]$process.Start()
do
{
   $process.StandardOutput.ReadLine()
}
while (!$process.HasExited)

这是对保罗答案的修改,希望它能解决截断的输出。 我使用失败进行了测试,但没有看到截断。

function Start-ProcessWithOutput
{
    param ([string]$Path,[string[]]$ArgumentList)
    $Output = New-Object -TypeName System.Text.StringBuilder
    $Error = New-Object -TypeName System.Text.StringBuilder
    $psi = New-object System.Diagnostics.ProcessStartInfo 
    $psi.CreateNoWindow = $true 
    $psi.UseShellExecute = $false 
    $psi.RedirectStandardOutput = $true 
    $psi.RedirectStandardError = $true 
    $psi.FileName = $Path
    if ($ArgumentList.Count -gt 0)
    {
        $psi.Arguments = $ArgumentList
    }
    $process = New-Object System.Diagnostics.Process 
    $process.StartInfo = $psi 
    [void]$process.Start()
    do
    {


       if (!$process.StandardOutput.EndOfStream)
       {
           [void]$Output.AppendLine($process.StandardOutput.ReadLine())
       }
       if (!$process.StandardError.EndOfStream)
       {
           [void]$Error.AppendLine($process.StandardError.ReadLine())
       }
       Start-Sleep -Milliseconds 10
    } while (!$process.HasExited)

    #read remainder
    while (!$process.StandardOutput.EndOfStream)
    {
        #write-verbose 'read remaining output'
        [void]$Output.AppendLine($process.StandardOutput.ReadLine())
    }
    while (!$process.StandardError.EndOfStream)
    {
        #write-verbose 'read remaining error'
        [void]$Error.AppendLine($process.StandardError.ReadLine())
    }

    return @{ExitCode = $process.ExitCode; Output = $Output.ToString(); Error = $Error.ToString(); ExitTime=$process.ExitTime}
}


$p = Start-ProcessWithOutput "C:\Program Files\7-Zip\7z.exe" -ArgumentList "x","-y","-oE:\PowershellModules",$NewModules.FullName -verbose
$p.ExitCode
$p.Output
$p.Error

10ms 的睡眠是为了避免在没有东西读的时候旋转 cpu。

我使用贾斯汀的解决方案得到了 Ash 提到的死锁场景。 相应地修改脚本以订阅异步事件处理程序以获取完成相同事情但避免死锁条件的输出和错误文本。

似乎在不更改返回数据的情况下解决了我的测试中的死锁问题。

# Define global variables used in the Start-ProcessWithOutput function.
$global:processOutputStringGlobal = ""
$global:processErrorStringGlobal = ""

# Launch an executable and return the exitcode, output text, and error text of the process.
function Start-ProcessWithOutput
{
    # Function requires a path to an executable and an optional list of arguments
    param (
        [Parameter(Mandatory=$true)] [string]$ExecutablePath,
        [Parameter(Mandatory=$false)] [string[]]$ArgumentList
    )

    # Reset our global variables to an empty string in the event this process is called multiple times.
    $global:processOutputStringGlobal = ""
    $global:processErrorStringGlobal = ""

    # Create the Process Info object which contains details about the process.  We tell it to 
    # redirect standard output and error output which will be collected and stored in a variable.
    $ProcessStartInfoObject = New-object System.Diagnostics.ProcessStartInfo 
    $ProcessStartInfoObject.FileName = $ExecutablePath
    $ProcessStartInfoObject.CreateNoWindow = $true 
    $ProcessStartInfoObject.UseShellExecute = $false 
    $ProcessStartInfoObject.RedirectStandardOutput = $true 
    $ProcessStartInfoObject.RedirectStandardError = $true 
    
    # Add the arguments to the process info object if any were provided
    if ($ArgumentList.Count -gt 0)
    {
        $ProcessStartInfoObject.Arguments = $ArgumentList
    }

    # Create the object that will represent the process
    $Process = New-Object System.Diagnostics.Process 
    $Process.StartInfo = $ProcessStartInfoObject 

    # Define actions for the event handlers we will subscribe to in a moment.  These are checking whether
    # any data was sent by the event handler and updating the global variable if it is not null or empty.
    $ProcessOutputEventAction = { 
        if ($null -ne $EventArgs.Data -and $EventArgs.Data -ne ""){
            $global:processOutputStringGlobal += "$($EventArgs.Data)`r`n"
        }
    }
    $ProcessErrorEventAction = { 
        if ($null -ne $EventArgs.Data -and $EventArgs.Data -ne ""){
            $global:processErrorStringGlobal += "$($EventArgs.Data)`r`n"
        }
    }

    # We need to create an event handler for the Process object.  This will call the action defined above 
    # anytime that event is triggered.  We are looking for output and error data received by the process 
    # and appending the global variables with those values.
    Register-ObjectEvent -InputObject $Process -EventName "OutputDataReceived" -Action $ProcessOutputEventAction
    Register-ObjectEvent -InputObject $Process -EventName "ErrorDataReceived" -Action $ProcessErrorEventAction

    # Process starts here
    [void]$Process.Start()

    # This sets up an asyncronous task to read the console output from the process, which triggers the appropriate
    # event, which we setup handlers for just above.
    $Process.BeginErrorReadLine()
    $Process.BeginOutputReadLine()
    
    # Wait for the process to exit.  
    $Process.WaitForExit()

    # We need to wait just a moment so the async tasks that are reading the output of the process can catch
    # up.  Not having this sleep here can cause the return values to be empty or incomplete.  In my testing, 
    # it seemed like half a second was enough time to always get the data, but you may need to adjust accordingly.
    Start-Sleep -Milliseconds 500


    # Return an object that contains the exit code, output text, and error text.
    return @{
        ExitCode = $Process.ExitCode; 
        OutputString = $global:processOutputStringGlobal; 
        ErrorString = $global:processErrorStringGlobal; 
        ExitTime = $Process.ExitTime
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM