I would like to redirect the output of a command in PowerShell, following these rules:
The command is stored to a variable
Output must be written to the console in real-time (i.e. "ping" results), including errors
Output must be stored to a variable, including errors (real-time is not mandatory here)
Here are my tests, assuming:
$command = "echo:"
to test errors redirection, and:
$command = "ping 127.0.0.1"
to test real-time output.
Output is written in real-time, errors are not redirected at all
Invoke-Expression $command 2>&1 | Tee-Object -Variable out_content
Output is written in real-time, errors are only redirected to the console
Invoke-Expression ($command 2>&1) | Tee-Object -Variable out_content
Invoke-Expression $command | Tee-Object -Variable out_content 2>&1
Output is not written in real-time, errors are correctly redirected to both
(Invoke-Expression $command) 2>&1 | Tee-Object -Variable out_content
Is it possible to get those rules working together?
Some general recommendations up front:
Invoke-Expression
should generally be avoided, because it can be a security risk and introduces quoting headaches; there are usually better and safer solutions available; best to form a habit of avoiding Invoke-Expression
, unless there is no other solution.
There is never a reason to use Invoke-Expression
to simply execute an external program with arguments, such as ping 127.0.0.1
; just invoke it directly - support for such direct invocations is a core feature of any shell, and PowerShell is no exception.
If you do need to store a command in a variable or pass it as an argument for later invocation, use script blocks ({ ... }
); e.g., instead of $command = 'ping 127.0.0.1'
, use $command = { ping 127.0.0.1 }
, and invoke that script block on demand with either &
, the call operator, or .
, the dot-sourcing operator.
When calling external programs, the two operators exhibit the same behavior; when calling PowerShell-native commands, &
executes the code in a child scope, whereas .
(typically) executes in the caller's current scope.
That Invoke-Expression $command 2>&1
doesn't work as expected looks like a bug (as of PowerShell (Core) 7.4.x) and has been reported in GitHub issue #10476.
As for a workaround for your problem:
PetSerAl, as countless times before, has provided a solution in a comment on the question:
& { Invoke-Expression $command } 2>&1 | Tee-Object -Variable out_content
{ ... }
is a script-block literal that contains the Invoke-Expression
call, and it is invoked with &
, the call operator, which enables applying stream-redirection expression 2>&1
to the &
call, which bypasses the bug.
If $command
contained a PowerShell-native command that you wanted to execute directly in the current scope, such as a function definition, you'd use .
instead of &
.
Alternative:
Include the 2>&1
redirection in the string to evaluate:
Invoke-Expression "$command 2>&1" | Tee-Object -Variable out_content