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).
Being able to capture stderr output separately in a variable is desirable, which isn't supported as of PowerShell 7.2.x, however. Adding future support, along the lines of 2>variable:errs
, is the subject of GitHub issue #4332.
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 problem.
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.