I'd like to run an external process and capture it's command output to a variable in PowerShell. I'm currently using this:
$params = "/verify $pc /domain:hosp.uhhg.org"
start-process "netdom.exe" $params -WindowStyle Hidden -Wait
I've confirmed the command is executing but I need to capture the output into a variable. This means I can't use the -RedirectOutput because this only redirects to a file.
This thing worked for me:
If you want to redirect the error output as well, you have to do:
Or, if the program name has spaces in it:
Or try this. It will capture output into variable $scriptOutput:
I got the following to work:
$result gives you the needful
Have you tried:
$OutputVariable = (Shell command) | Out-String
Note: The command in the question uses
Start-Process
, which prevents direct capturing of the target program's output. Generally, do not useStart-Process
to execute console applications synchronously - just invoke them directly, as in any shell. Doing so keeps the application connected to the calling console's standard streams, allowing its output to be captured by simple assignment$output = netdom ...
, as detailed below.Fundamentally, capturing output from external utilities works the same as with PowerShell-native commands (you may want a refresher on how to execute external tools):
Note that
$cmdOutput
receives an array of objects if<command>
produces more than 1 output object, which in the case of an external program means a string array containing the program's output lines.If you want
$cmdOutput
to always receive a single - potentially multi-line - string, use$cmdOutput = <command> | Out-String
To capture output in a variable and print to the screen:
Or, if
<command>
is a cmdlet or advanced function, you can use common parameter-OutVariable
/-ov
:Note that with
-OutVariable
, unlike in the other scenarios,$cmdOutput
is always a collection, even if only one object is output. Specifically, an instance of the array-like[System.Collections.ArrayList]
type is returned.See this GitHub issue for a discussion of this discrepancy.
To capture the output from multiple commands, use either a subexpression (
$(...)
) or call a script block ({ ... }
) with&
or.
:Note that the general need to prefix with
&
(the call operator) an individual command whose name/path is quoted - e.g.,$cmdOutput = & 'netdom.exe' ...
- is not related to external programs per se (it equally applies to PowerShell scripts), but is a syntax requirement: PowerShell parses a statement that starts with a quoted string in expression mode by default, whereas argument mode is needed to invoke commands (cmdlets, external programs, functions, aliases), which is what&
ensures.The key difference between
$(...)
and& { ... }
/. { ... }
is that the former collects all input in memory before returning it as a whole, whereas the latter stream the output, suitable for one-by-one pipeline processing.Redirections also work the same, fundamentally (but see caveats below):
However, for external commands the following is more likely to work as expected:
Considerations specific to external programs:
External programs, because they operate outside PowerShell's type system, only ever return strings via their success stream (stdout).
If the output contains more than 1 line, PowerShell by default splits it into an array of strings. More accurately, the output lines are stored in an array of type
[System.Object[]]
whose elements are strings ([System.String]
).If you want the output to be a single, potentially multi-line string, pipe to
Out-String
:$cmdOutput = <command> | Out-String
Redirecting stderr to stdout with
2>&1
, so as to also capture it as part of the success stream, comes with caveats:To make
2>&1
merge stdout and stderr at the source, letcmd.exe
handle the redirection, using the following idioms:$cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
$cmdOutput = cmd /c <command> '2>&1' | Out-String # single string
cmd /c
invokescmd.exe
with command<command>
and exits after<command>
has finished.2>&1
, which ensures that the redirection is passed tocmd.exe
rather than being interpreted by PowerShell.Note that involving
cmd.exe
means that its rules for escaping characters and expanding environment variables come into play, by default in addition to PowerShell's own requirements; in PS v3+ you can use special parameter--%
(the so-called stop-parsing symbol) to turn off interpretation of the remaining parameters by PowerShell, except forcmd.exe
-style environment-variable references such as%PATH%
.Note that since you're merging stdout and stderr at the source with this approach, you won't be able to distinguish between stdout-originated and stderr-originated lines in PowerShell; if you do need this distinction, use PowerShell's own
2>&1
redirection - see below.Use PowerShell's
2>&1
redirection to know which lines came from what stream:Stderr output is captured as error records (
[System.Management.Automation.ErrorRecord]
), not strings, so the output array may contain a mix of strings (each string representing a stdout line) and error records (each record representing a stderr line). Note that, as requested by2>&1
, both the strings and the error records are received through PowerShell's success output stream).In the console, the error records print in red, and the 1st one by default produces multi-line display, in the same format that a cmdlet's non-terminating error would display; subsequent error records print in red as well, but only print their error message, on a single line.
When outputting to the console, the strings typically come first in the output array, followed by the error records (at least among a batch of stdout/stderr lines output "at the same time"), but, fortunately, when you capture the output, it is properly interleaved, using the same output order you would get without
2>&1
; in other words: when outputting to the console, the captured output does NOT reflect the order in which stdout and stderr lines were generated by the external command.If you capture the entire output in a single string with
Out-String
, PowerShell will add extra lines, because the string representation of an error record contains extra information such as location (At line:...
) and category (+ CategoryInfo ...
); curiously, this only applies to the first error record..ToString()
method to each output object instead of piping toOut-String
:$cmdOutput = <command> 2>&1 | % { $_.ToString() }
;in PS v3+ you can simplify to:
$cmdOutput = <command> 2>&1 | % ToString
(As a bonus, if the output isn't captured, this produces properly interleaved output even when printing to the console.)
Alternatively, filter the error records out and send them to PowerShell's error stream with
Write-Error
(as a bonus, if the output isn't captured, this produces properly interleaved output even when printing to the console):