Invoke-Expression, not all output returned to variable Invoke-Expression, not all output returned to variable powershell powershell

Invoke-Expression, not all output returned to variable


As I mentioned in "PowerShell Capture Git Output", with Git 2.16 (Q1 2018), you can try and set first:

set GIT_REDIRECT_STDERR=2>&1

Then in your Powershell script, you should get both stdout and stderr outputs,

See also dahlbyk/posh-git issue 109 for a more Powershell-like example:

$env:GIT_REDIRECT_STDERR = '2>&1'


VonC's answer works well with git, specifically, but it's worth discussing a generic solution:


Note: Invoke-Expression should generally be avoided and there is no reason to use it for invocation of external programs: just invoke them directly and assign to a variable:

$capturedStdout = git ... # capture git's stdout output as an array of lines

As has been noted, git outputs status information to stderr, whereas data goes to stdout; a PowerShell variable assignment only captures stdout output.[1]

To capture a combination of stdout and stderr, interleaved, as it would print to the terminal, you can use redirection 2>&1, as in other shells, to merge the error stream / stderr (2) into (>&) the data output stream (stdout equivalent, 1 - see about_Redirection):

$combinedOutput = git fetch --all 2>&1 

Caveat: In PowerShell versions up to v7.1, if $ErrorActionPreference = 'Stop' happens to be in effect, the use of 2> unexpectedly triggers a terminating error; this problematic behavior is discussed in GitHub issue #4002.

There are non-obvious differences to the behavior of other shells, however:

  • The output will be an array of lines, not a single, multi-line string,

    • Note: As of PowerShell 7.2 - external-program output is invariably interpreted as text (strings) - there is no support for raw binary output; see this answer.
  • Lines that originated from stdout are represented as strings, as expected, but lines originating from stderr are actually [System.Management.Automation.ErrorRecord] instances, though they print like strings and on conversion to strings do reproduce the original line, such as when sending the result to an external program.
    This answer shows how to separate the captured lines by stream of origin (assuming stdout and stderr were merged).

The array-based result can be advantageous for parsing; e.g., to find a line that contains the word unpacking:

PS> $combinedOutput -match 'unpacking'Unpacking objects: 100% (4/4), done.

Note: If there's a chance that only one line was output, use @($combinedOutput) -match 'unpacking'

If you prefer to receive a single, multi-line string instead:

$combinedOutput = (git fetch --all 2>&1) -join "`n"  # \n (LF); or: [Environment]::NewLine 

If you don't mind a trailing newline as part of the string, you can more simply use Out-String:[2]

$combinedOutput = git fetch --all 2>&1 | Out-String

Caveat: In Windows PowerShell this won't work as expected if stderr lines are present, as they are rendered like PowerShell error records (this problem has been fixed in PowerShell (Core) 6+); run cmd /c 'echo data & echo err >&2' 2>&1 | Out-String to see the problem. Use the -join "`n" solution to avoid the problme.


Note:

  • As usual, irrespective of what redirections you use, determining whether an external-program call succeeded or failed should be based only on its exit code, reflected in PowerShell's automatic $LASTEXITCODE variable: By convention (which most, but not all programs observe), 0 indicates success and and any nonzero value failure (a notable exception is robocopy which uses several nonzero exit codes to communicate additional information in the success case).

[1] For comprehensive information on capturing output from external programs in PowerShell, see this answer.

[2] This problematic Out-String behavior is discussed in GitHub issue #14444.