Capture program stdout and stderr to separate variables

51,013

Solution 1

The easiest way to do this is to use a file for the stderr output, e.g.:

$output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
$err = get-content stderr.txt
if ($LastExitCode -ne 0) { ... handle error ... }

I would also use $LastExitCode to check for errors from native console EXE files.

Solution 2

One option is to combine the output of stdout and stderr into a single stream, then filter.

Data from stdout will be strings, while stderr produces System.Management.Automation.ErrorRecord objects.

$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }

Solution 3

You should be using Start-Process with -RedirectStandardError -RedirectStandardOutput options. This other post has a great example of how to do this (sampled from that post below):

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode

Solution 4

This is also an alternative that I have used to redirect stdout and stderr of a command line while still showing the output during PowerShell execution:

$command = "myexecutable.exe my command line params"

Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "STDOUT"
Write-Host $output
Write-Host "STDERR"
Write-Host $errors

It is just another possibility to supplement what was already given.

Keep in mind this may not always work depending upon how the script is invoked. I have had problems with -OutVariable and -ErrorVariable when invoked from a standard command line rather than a PowerShell command line like this:

PowerShell -File ".\FileName.ps1"

An alternative that seems to work under most circumstances is this:

$stdOutAndError = Invoke-Expression "$command 2>&1"

Unfortunately, you will lose output to the command line during execution of the script and would have to Write-Host $stdOutAndError after the command returns to make it "a part of the record" (like a part of a Jenkins batch file run). And unfortunately it doesn't separate stdout and stderr.

Solution 5

At first, this answer was published under another question.

The two questions are similar, but the attention here is higher, so I'll send another one here, which may provide solutions for more people.


Using Where-Object(The alias is symbol ?) is an obvious method, but it's a bit too cumbersome. It needs a lot of code.

In this way, it will not only take longer time, but also increase the probability of error.

In fact, there is a more concise method that separate different streams to different variable in PowerShell(it came to me by accident).

# First, declare a method that outputs both streams at the same time.
function thisFunc {
    [cmdletbinding()]
    param()
    Write-Output 'Output'
    Write-Verbose 'Verbose'
}
# The separation is done in a single statement.Our goal has been achieved.
$VerboseStream = (thisFunc -Verbose | Tee-Object -Variable 'String' | Out-Null) 4>&1

Then we verify the contents of these two variables

$VerboseStream.getType().FullName
$String.getType().FullName

The following information should appear on the console:

PS> System.Management.Automation.VerboseRecord
System.String

'4>&1' means to redirect the verboseStream to the success stream, which can then be saved to a variable, of course you can change this number to any number between 2 and 5.

If you feel and my method is not bad, please click the mouse to vote for me, thank you very much.

Share:
51,013

Related videos on Youtube

dusz
Author by

dusz

Updated on July 09, 2022

Comments

  • dusz
    dusz almost 2 years

    Is it possible to redirect stdout from an external program to a variable and stderr from external programs to another variable in one run?

    For example:

    $global:ERRORS = @();
    $global:PROGERR = @();
    
    function test() {
        # Can we redirect errors to $PROGERR here, leaving stdout for $OUTPUT?
        $OUTPUT = (& myprogram.exe 'argv[0]', 'argv[1]');
    
        if ( $OUTPUT | select-string -Pattern "foo" ) {
            # do stuff
        } else {
            $global:ERRORS += "test(): oh noes! 'foo' missing!";
        }
    }
    
    test;
    if ( @($global:ERRORS).length -gt 0 ) {
        Write-Host "Script specific error occurred";
        foreach ( $err in $global:ERRORS ) {
            $host.ui.WriteErrorLine("err: $err");
        }
    } else {
        Write-Host "Script ran fine!";
    }
    
    if ( @($global:PROGERR).length -gt 0 ) {
        # do stuff
    } else {
        Write-Host "External program ran fine!";
    }
    

    A dull example however I am wondering if that is possible?

    • Alexander Obersht
      Alexander Obersht almost 10 years
      You could use Start-Process to run myprogram.exe as described here. It captures STDOUT and STDERR separately.
  • johnnycrash
    johnnycrash over 9 years
    This can deadlock an app that doesn't write asynchronously to stderr and stdout.
  • sschuberth
    sschuberth over 8 years
    @johnnycrash Any suggestion how to fix that?
  • sschuberth
    sschuberth over 8 years
    Nevermind, as suggested here the WaitForExit() call should come after the ReadToEnd() calls.
  • Ohad Schneider
    Ohad Schneider about 8 years
    Or even better, replace the first line with & myprogram.exe 2>&1 | tee -Variable allOutput. That way you get the output printed for free, even keeping the order when stdout and stderr are interleaved (none of the other answers give that). This also doesn't go through any files which is a win in terms of performance and minimization of things that can fail.
  • Cameron Kerr
    Cameron Kerr over 7 years
    It is important to point out (at least for stderr), the output is not what the command produces, but rather it is the hosts report of what the command-produced, which would come as a shock to those coming from other backgrounds.
  • ComFreek
    ComFreek over 6 years
    Combining @OhadSchneider's approach with capturing the output in a variable without outputting it: [Void] (& myprog.exe 2>&1 | tee -Variable allOutput) and then $stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }.
  • Franklin Yu
    Franklin Yu about 6 years
    Since stderr.txt is probably used only once, New-TemporaryFile comes in handy