Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$LastExitCode=0, but $?=False in PowerShell. Redirecting stderr to stdout gives NativeCommandError

Why does PowerShell show the surprising behaviour in the second example below?

First, an example of sane behaviour:

PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?" Hello from standard error $LastExitCode=0 and $?=True 

No surprises. I print a message to standard error (using cmd's echo). I inspect the variables $? and $LastExitCode. They equal to True and 0 respectively, as expected.

However, if I ask PowerShell to redirect standard error to standard output over the first command, I get a NativeCommandError:

PS C:\> & cmd /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?" cmd.exe : Hello from standard error At line:1 char:4 + cmd <<<<  /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"     + CategoryInfo          : NotSpecified: (Hello from standard error :String) [], RemoteException     + FullyQualifiedErrorId : NativeCommandError  $LastExitCode=0 and $?=False 

My first question, why the NativeCommandError?

Secondly, why is $? False when cmd ran successfully and $LastExitCode is 0? PowerShell's documentation about automatic variables doesn't explicitly define $?. I always supposed it is True if and only if $LastExitCode is 0, but my example contradicts that.


Here's how I came across this behaviour in the real-world (simplified). It really is FUBAR. I was calling one PowerShell script from another. The inner script:

cmd /c "echo Hello from standard error 1>&2" if (! $?) {     echo "Job failed. Sending email.."     exit 1 } # Do something else 

Running this simply as .\job.ps1, it works fine, and no email is sent. However, I was calling it from another PowerShell script, logging to a file .\job.ps1 2>&1 > log.txt. In this case, an email is sent! What you do outside the script with the error stream affects the internal behaviour of the script. Observing a phenomenon changes the outcome. This feels like quantum physics rather than scripting!

[Interestingly: .\job.ps1 2>&1 may or not blow up depending on where you run it]

like image 532
Colonel Panic Avatar asked May 19 '12 14:05

Colonel Panic


1 Answers

(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 
like image 123
Droj Avatar answered Oct 05 '22 02:10

Droj