Powershell: Capturing standard out and error with Process object

22,410

Solution 1

The docs on the RedirectStandardError property suggests that it is better to put the WaitForExit() call after the ReadToEnd() call. The following works correctly for me:

$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

Solution 2

Small variation so that you can selectively print the output if needed. As in if your looking just for error or warning messages and by the way Keith you saved my bacon with your response...

$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)

Solution 3

Here is a modification to paul's answer, hopefully it addresses the truncated output. i did a test using a failure and did not see truncation.

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

the 10ms sleep is to avoid spinning cpu when nothing to read.

Share:
22,410

Related videos on Youtube

Marcelo De Zen
Author by

Marcelo De Zen

Worked with many languages and technology stacks over the 20+ I've been designing and implementing "line of business" applications.

Updated on October 04, 2020

Comments

  • Marcelo De Zen
    Marcelo De Zen over 3 years

    I want to start a Java program from PowerShell and get the results printed on the console.

    I have followed the instructions of this question: Capturing standard out and error with Start-Process

    But for me, this is not working as I expected. What I'm doing wrong?

    This is the script:

    $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
    

    The $output variable is always empty (and nothing is printed on the console of course).

    • Keith Hill
      Keith Hill almost 12 years
      Considering PowerShell is a "shell", any reason why you just don't execute PS> java.exe -jar tools\compiler.jar --compilation_level ...?
    • Marcelo De Zen
      Marcelo De Zen almost 12 years
      It actually works :) But I still don't understand why the script above doesn't print the output to console!
    • Ralph Willgoss
      Ralph Willgoss almost 12 years
      @dev use Write-OutPut $output, to see what is returned via the console
    • Kiquenet
      Kiquenet over 9 years
      Any final solution with full source code sample application ? IMHO, better samples for minimize learning curve are real applications with full source code and good patterns.
  • Marcelo De Zen
    Marcelo De Zen almost 12 years
    Ah, I use the | Out-Null to prevent that a "true" is printed on console.
  • Keith Hill
    Keith Hill almost 12 years
    @devundef Ignore the comment about piping to Out-Null. It's OK here. The pipe to Out-Null trick can be used to hold off until a Windows exe you launch has closed i.e. Notepad.exe | Out-Null. This doesn't apply in your case.
  • Kiquenet
    Kiquenet over 9 years
    I'm pretty sure Windows also won't allow you to redirect standard input/output/error across the admin/non-admin security boundary. You'll have to find a different way to get output from the program running as admin - Reference: stackoverflow.com/a/8690661
  • Kiquenet
    Kiquenet over 9 years
    I'm pretty sure Windows also won't allow you to redirect standard input/output/error across the admin/non-admin security boundary. You'll have to find a different way to get output from the program running as admin - Reference: stackoverflow.com/a/8690661
  • Adarsha
    Adarsha about 8 years
    Your way of repeatedly reading output buffer is the key. Otherwise processes producing large text can fill the output buffer, and program will hand. Only change that is needed is to append the read line to a string to use later.
  • PhiGamma
    PhiGamma almost 8 years
    I have encountered trouble with this approach, when the process exits due to an error, say, and ReadLine() can't catch up, so the output is truncated.
  • rollsch
    rollsch over 4 years
    This a great workaround to the deadlock that occurs doing this the "proper" way.
  • ash
    ash about 2 years
    In my testing this ends up in a deadlock scenario. The 'EndOfStream' is the culprit: stackoverflow.com/questions/2767496/…