$LastExitCode=0, but $?=False in PowerShell. Redirecting stderr to stdout gives NativeCommandError
(I am using PowerShell v2.)
The '$?
' variable is documented in about_Automatic_Variables
:
$? Contains the execution status of the last operation
This is referring to the most recent PowerShell operation, as opposed to the last external command, which is what you get in $LastExitCode
.
In your example, $LastExitCode
is 0, because the last external command was cmd
, which was successful in echoing some text. But the 2>&1
causes messages to stderr
to be converted to error records in the output stream, which tells PowerShell that there was an error during the last operation, causing $?
to be False
.
To illustrate this a bit more, consider this:
> java -jar foo; $?; $LastExitCode Unable to access jarfile foo False 1
$LastExitCode
is 1, because that was the exit code of java.exe. $?
is False, because the very last thing the shell did failed.
But if all I do is switch them around:
> java -jar foo; $LastExitCode; $? Unable to access jarfile foo 1 True
... then $?
is True, because the last thing the shell did was print $LastExitCode
to the host, which was successful.
Finally:
> &{ java -jar foo }; $?; $LastExitCode Unable to access jarfile foo True 1
...which seems a bit counter-intuitive, but $?
is True now, because the execution of the script block was successful, even if the command run inside of it was not.
Returning to the 2>&1
redirect.... that causes an error record to go in the output stream, which is what gives that long-winded blob about the NativeCommandError
. The shell is dumping the whole error record.
This can be especially annoying when all you want to do is pipe stderr
and stdout
together so they can be combined in a log file or something. Who wants PowerShell butting in to their log file??? If I do ant build 2>&1 >build.log
, then any errors that go to stderr
have PowerShell's nosey $0.02 tacked on, instead of getting clean error messages in my log file.
But, the output stream is not a text stream! Redirects are just another syntax for the object pipeline. The error records are objects, so all you have to do is convert the objects on that stream to strings before redirecting:
From:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 cmd.exe : Hello from standard error At line:1 char:4 + cmd &2" 2>&1 + CategoryInfo : NotSpecified: (Hello from standard error :String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError
To:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } Hello from standard error
...and with a redirect to a file:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } | tee out.txt Hello from standard error
...or just:
> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } >out.txt
This bug is an unforeseen consequence of PowerShell's prescriptive design for error handling, so most likely it will never be fixed. If your script plays only with other PowerShell scripts, you're safe. However if your script interacts with applications from the big wide world, this bug may bite.
PS> nslookup microsoft.com 2>&1 ; echo $?
False
Gotcha! Still, after some painful scratching, you'll never forget the lesson.
Use ($LastExitCode -eq 0)
instead of $?
(Note: This is mostly speculation; I rarely use many native commands in PowerShell and others probably know more about PowerShell internals than me)
I guess you found a discrepancy in the PowerShell console host.
- If PowerShell picks up stuff on the standard error stream it will assume an error and throw a
NativeCommandError
. - PowerShell can only pick this up if it monitors the standard error stream.
- PowerShell ISE has to monitor it, because it is no console application and thus a native console application has no console to write to. This is why in the PowerShell ISE this fails regardless of the
2>&1
redirection operator. - The console host will monitor the standard error stream if you use the
2>&1
redirection operator because output on the standard error stream has to be redirected and thus read.
My guess here is that the console PowerShell host is lazy and just hands native console commands the console if it doesn't need to do any processing on their output.
I would really believe this to be a bug, because PowerShell behaves differently depending on the host application.