I need to capture the output of a external process into a variable (string), so I can do some processing on that.
The answer from here works nicely as long as the p
To complement manojlds' helpful answer with an overview:
To briefly explain redirection expression 2>&1
:
2>
redirects (>
) PowerShell's error output stream, whose number is 2
(and which maps onto stderr) into (&
) PowerShell's success output stream, whose number is 1
(and maps onto stdout).
Run Get-Help about_Redirection to learn more.
As a collection of output lines as strings, without the ability to tell which line came from what stream:
Using the platform-native shell to perform the merging at the source makes PowerShell only see stdout output, which, as usual, it collects in an array of strings.
In the following examples, the commands each create both stdout and stderr output.
All commands use PSv3+ syntax for convenience and brevity.
Windows example:
# Collect combined output across both streams as an array of
# strings, where each string represents and output line.
# Note the selective quoting around & and 2>&1 to make sure they
# are passed through to cmd.exe rather than PowerShell itself interpreting them.
$allOutput = cmd /c ver '&' dir \nosuch '2>&1'
Unix example (PowerShell Core):
# sh, the Unix default shell, expects the entire command as a *single* argument.
$allOutput = sh -c '{ date; ls /nosuch; } 2>&1'
Note:
You do need to call the platform-native shell explicitly (cmd /c
on Windows, sh -c
on Unix), which makes this approach less portable.
You will not be able to tell from the resulting array of lines which line came from what stream.
See below for how to make this distinction.
As a mix of string lines (from stdout) and [System.Management.Automation.ErrorRecord]
"lines" (from stderr):
By using PowerShell's 2>&1
redirection, you also get a single collection of lines representing the merged stdout and stderr streams, but the stderr lines aren't captured as strings, but as [System.Management.Automation.ErrorRecord]
instances.
Caveat: A bug as of Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.4 results in unexpected behavior when you redirect PowerShell's error stream with 2>
while $ErrorActionPreference = 'Stop'
is in effect - see this GitHub issue.
This gives you the flexibility to distinguish between stdout and stderr lines by examining the data type of each array element.
On the flip side, you may have to convert the stderr lines to strings.
In PSv6, the different data types are not obvious when you simply output the captured output, but you can tell by reflection:
# Let PowerShell merge the streams with 2>&1, which captures
# stdout lines as strings and stderr lines as [System.Management.Automation.ErrorRecord]
# instances.
$allOutput = cmd /c ver '&' dir \nosuch 2>&1
Inspect the result:
PS> $allOutput | % GetType | % Name
String
String
String
String
String
String
ErrorRecord
As you can see, the last array element is an error record, which represent the single File Not Found
stderr output line produced by the dir \nosuch
command.
Note: Up to PSv5.1, when you output the captured output to the console, the [System.Management.Automation.ErrorRecord]
instances actually rendered in the same format as PowerShell errors, perhaps making it appear as if an error had occurred then.
In PSv6, these error records print just their message, i.e., the contents of the original stderr line, and visually you can't tell that an error record rather than a string is being printed.
To convert all captured output to strings:
$allOutput = cmd /c ver '&' dir \nosuch 2>&1 | % ToString
To filter out the stderr lines (and converting them to strings in the process):
$allOutput = cmd /c ver '&' dir \nosuch 2>&1
$stderrOnly = $allOutput | ? { $_ -is [System.Management.Automation.ErrorRecord] } |
% ToString
Using a temporary file:
As of PSv5.1, the only direct way to capture stderr output in isolation is to use redirection 2>
with a filename target; i.e., to capture stderr output - as text - in a file:
$stderrFile = New-TemporaryFile # PSv5+; PSv4-: use [io.path]::GetTempFileName()
$stdoutOutput = cmd /c ver '&' dir \nosuch 2>$stderrFile
$stderrOutput = Get-Content $stdErrFile
Remove-Item $stderrFile
Clearly, this is cumbersome and also slower than in-memory operations.
Using the PSv4+ .Where()
collection operator:
The (little-known) PSv4+ .Where()
collection operator allows you to split a collection in two, based on whether the elements pass a Boolean test or not:
# Merge the streams first, so they go to the success stream, then
# split the objects in the merged stream by type.
$stdoutOutput, $stderrOutput = (cmd /c ver '&' dir \nosuch 2>&1).Where({
$_ -isnot [System.Management.Automation.ErrorRecord]
}, 'Split')
# Convert the collected [System.Management.Automation.ErrorRecord]
# instances to strings.
$stderrOutput = $stderrOutput | % ToString
Potential future alternative:
This GitHub issue proposes a new redirection syntax that would allow collecting stderr lines in a variable much more simply:
# As of v5.1: WISHFUL THINKING
$stdout = cmd /c ver '&' dir \nosuch 2>&stderr # collect stderr lines in var. $stderr
Try this:
$cmdOutput = svn info 2>&1