Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I capture the output into a variable from an external process in PowerShell?

People also ask

How do I copy the output of a command in PowerShell?

Use QuickEdit to copy text—Although it's not obvious, the PowerShell command shell lets you select and quickly copy any text displayed in the command shell. Use the mouse to select the text to be copied, then press Enter or right-click on the selected text to copy it to the clipboard.


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, or wrap the command in @(), the array-subexpression operator):

[array] $cmdOutput = <command> # or: $cmdOutput = @(<command>)

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 $OutVariable preference 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]::OutputEncoding to 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.

  • 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, use the -join operator (you can alternatively pipe to Out-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.exe handle the redirection, using the following idioms (works analogously with sh on 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 /c invokes cmd.exe with command <command> and exits after <command> has finished.

      • Note the single quotes around 2>&1, which ensures that the redirection is passed to cmd.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 for cmd.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 by 2>&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 to Out-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.1:

  • 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.exe and 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.


Have you tried:

$OutputVariable = (Shell command) | Out-String


If you want to redirect the error output as well, you have to do:

$cmdOutput = command 2>&1

Or, if the program name has spaces in it:

$cmdOutput = & "command with spaces" 2>&1

Or try this. It will capture output into variable $scriptOutput:

& "netdom.exe" $params | Tee-Object -Variable scriptOutput | Out-Null

$scriptOutput

Another real-life example:

$result = & "$env:cust_tls_store\Tools\WDK\x64\devcon.exe" enable $strHwid 2>&1 | Out-String

Notice that this example includes a path (which begins with an environment variable). Notice that the quotes must surround the path and the EXE file, but not the parameters!

Note: Don't forget the & character in front of the command, but outside of the quotes.

The error output is also collected.

It took me a while to get this combination working, so I thought that I would share it.


I tried the answers, but in my case I did not get the raw output. Instead it was converted to a PowerShell exception.

The raw result I got with:

$rawOutput = (cmd /c <command> 2`>`&1)

This thing worked for me:

$scriptOutput = (cmd /s /c $FilePath $ArgumentList)