Note: The command in the question uses Start-Process, which prevents direct capturing of the target program’s output. Generally, do not use Start-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 programs works the same as with PowerShell-native commands (you may want a refresher on how to execute external programs; <command> is a placeholder for any valid command below):
# IMPORTANT:
# <command> is a *placeholder* for any valid command; e.g.:
# $cmdOutput = Get-Date
# $cmdOutput = attrib.exe +R readonly.txt
$cmdOutput = <command> # captures the command's success stream / stdout output
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[1] array containing the program’s output lines.
If you want to make sure that the result is always an array – even if only one object is output, type-constrain the variable as an array ([object[]]), or enclose the command in @(...), the array-subexpression operator:[2]
[array] $cmdOutput = <command>
$cmdOutput = @(<command>) # alternative
By contrast, if you want $cmdOutput to always receive a single – potentially multi-line – string, use Out-String, though note that a trailing newline is invariably added (GitHub issue #14444 discusses this problematic behavior):
# Note: Adds a trailing newline.
$cmdOutput = <command> | Out-String
With calls to external programs – which by definition only ever return strings in PowerShell[1] – you can avoid that by using the -join operator instead:
# NO trailing newline.
$cmdOutput = (<command>) -join "`n"
Note: For simplicity, the above uses "`n" to create Unix-style LF-only newlines, which PowerShell happily accepts on all platforms; if you need platform-appropriate newlines (CRLF on Windows, LF on Unix), use [Environment]::NewLine instead.
To capture output in a variable and print to the screen:
<command> | Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed
Or, if <command> is a cmdlet or advanced function, you can use common parameter
-OutVariable / -ov:
<command> -OutVariable cmdOutput # cmdlets and advanced functions only
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 .:
$cmdOutput = $(<command>; ...) # subexpression
$cmdOutput = & {<command>; ...} # script block with & - creates child scope for vars.
$cmdOutput = . {<command>; ...} # script block with . - no child scope
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):
$cmdOutput = <command> 2>&1 # redirect error stream (2) to success stream (1)
However, for external commands the following is more likely to work as expected:
$cmdOutput = cmd /c <command> '2>&1' # Let cmd.exe handle redirection - see below.
Considerations specific to external programs:
-
External programs, because they operate outside PowerShell’s type system, only ever return strings via their success stream (stdout); similarly, PowerShell only ever sends strings to external programs via the pipeline.[1]
- Character-encoding issues can therefore come into play:
-
On sending data via the pipeline to external programs, PowerShell uses the encoding stored in the
$OutVariablepreference variable; which in Windows PowerShell defaults to ASCII(!) and in PowerShell [Core] to UTF-8. -
On receiving data from an external program, PowerShell uses the encoding stored in
[Console]::OutputEncodingto decode the data, which in both PowerShell editions defaults to the system’s active OEM code page. -
See this answer for more information; this answer discusses the still-in-beta (as of this writing) Windows 10 feature that allows you to set UTF-8 as both the ANSI and the OEM code page system-wide.
-
- Character-encoding issues can therefore come into play:
-
If the output contains more than 1 line, PowerShell by default splits it into an array of strings. More accurately, the output lines are streamed one by one, and, when captured, 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, use the
-joinoperator (you can alternatively pipe toOut-String, but that invariably adds a trailing newline):
$cmdOutput = (<command>) -join [Environment]::NewLine -
Merging stderr into stdout with
2>&1, so as to also capture it as part of the success stream, comes with caveats:-
To do this at the source, let
cmd.exehandle the redirection, using the following idioms (works analogously withshon Unix-like platforms):
$cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
$cmdOutput = (cmd /c <command> '2>&1') -join "`r`n" # single string-
cmd /cinvokescmd.exewith command<command>and exits after<command>has finished. -
Note the single quotes around
2>&1, which ensures that the redirection is passed tocmd.exerather than being interpreted by PowerShell. -
Note that involving
cmd.exemeans 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>&1redirection – see below.
-
-
Use PowerShell’s
2>&1redirection 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). -
Note: The following only applies to Windows PowerShell – these problems have been corrected in PowerShell [Core] v6+, though the filtering technique by object type shown below (
$_ -is [System.Management.Automation.ErrorRecord]) can also be useful there. -
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.-
To work around this problem, apply the
.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):
-
-
-
$cmdOutput = <command> 2>&1 | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) {
Write-Error $_
} else {
$_
}
}
An aside re argument-passing, as of PowerShell 7.2.x:
-
Passing arguments to external programs is broken with respect to empty-string arguments and arguments that contain embedded
"characters. -
Additionally, the (nonstandard) quoting needs of executables such as
msiexec.exeand batch files aren’t accommodated.
For the former problem only, a fix may be coming (though the fix would be complete on Unix-like platforms), as discussed in this answer, which also details all the current problems and workarounds.
If installing a third-party module is an option, the ie function from the Native module (Install-Module Native) offers a comprehensive solution.
[1] As of PowerShell 7.1, PowerShell knows only strings when communicating with external programs. There is generally no concept of raw byte data in a PowerShell pipeline. If you want raw byte data returned from an external program, you must shell out to cmd.exe /c (Windows) or sh -c (Unix), save to a file there, then read that file in PowerShell. See this answer for more information.
[2] There are subtle differences between the two approaches (which you may combine), though they usually won’t matter: If the command has no output, the [array] type-constraint approach results in $null getting stored in the target variable, whereas it is an empty ([object[]) array in the case of @(...). Additionally, the [array] type constraint means that future (non-empty) assignments to the same variable are coerced to an array too.