Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Don't throw PowerShell exception on writes to stderr from external command

When an external command, such as git, writes to stderr, PowerShell generates a NativeCommandError exception. I would like to see the output normally along with stdout similar to how it might look on a standard UNIX/Linux system. There are a number of native commands that this script needs to run and I would prefer a solution that does not add too much clutter and maintenance to each command, if possible.

On Linux, a branch can be checked out as such:

$ git checkout devel
Branch 'devel' set up to track remote branch 'devel' from 'origin'.
Switched to a new branch 'devel'

The subtle part of that is that that last line was written to stderr for whatever reason. However, the exit status of git was zero indicating it was successful. Under PowerShell, I get this:

PS> git checkout devel
Branch 'devel' set up to track remote branch 'devel' from 'origin'.
git : Switched to a new branch 'devel'
At line:1 char:1
+ git checkout devel
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Switched to a new branch 'devel':String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

Checking $LASTEXITSTATUS, it shows as zero indicating that the checkout was successful. However, because git's behavior is to write to stderr certain messages, it triggers PowerShell to register an error. I can just hide the message and check for the exit status manually, but I don't want to hide actual error messages when they show. This command does avoid the exception:

PS> git checkout devel 2>$null
Branch 'devel' set up to track remote branch 'devel' from 'origin'.

And is what other answers on StackOverflow have suggested, but isn't what I need. I've tried redirecting stderr to stdout with hope that PowerShell would see all output on stdout and not trigger an exception, but it still does:

git checkout devel 2>&1
Branch 'devel' set up to track remote branch 'devel' from 'origin'.
git : Switched to a new branch 'devel'
At line:1 char:1
+ git checkout devel 2>&1
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Switched to a new branch 'devel':String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

I have also tried catching the exception, but the try..catch block does not seem to block it:

PS> Try { git checkout devel 2>&1 } catch { echo "There was an error, ignore" }
Branch 'devel' set up to track remote branch 'devel' from 'origin'.
git : Switched to a new branch 'devel'
At line:1 char:7
+ Try { git checkout devel 2>&1 } catch { echo "There was an error, ign ...
+       ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Switched to a new branch 'devel':String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

I only want a native command to cause a failure if it had a non-zero exit status, not if it only writes to stderr. How do I prevent native commands in a PowerShell script from throwing an exception on writes to stderr without completely blocking stderr?

like image 701
penguin359 Avatar asked Dec 17 '19 01:12

penguin359


Video Answer


1 Answers

tl;dr

The simplest workaround for the PowerShell ISE is the following (not needed in a regular console or in Visual Studio Code):

# Redirect stderr (2) to the success stream (1).
# Doing so wraps stderr lines in [System.Management.Automation.ErrorRecord]
# instances; calling .ToString() on them returns them as normal text.
# CAVEAT: BE SURE THAT $ErrorActionPreference = 'Stop' is NOT IN EFFECT.
git checkout devel 2>&1 | % ToString 

For other workarounds, including the ability to distinguish lines by stream of origin, see this answer.

However, you're better off switching to Visual Studio Code as your development environment, which solves the problem fundamentally - see below.

Two asides:

  • What you're seeing are non-terminating errors, not exceptions (while .NET exceptions underly PowerShell's error handling, they are always wrapped in [System.Management.Automation.ErrorRecord] instances and may or may not abort execution (terminating vs. non-terminating error)).

  • try / catch doesn't catch non-terminating errors, only terminating ones; while you could set $ErrorActionPreference = 'Stop' beforehand to promote non-terminating errors to terminating ones, execution of your program (in the ISE ) would be aborted on receiving the first stderr line.


You can avoid this problem if you limit your PowerShell use to:

  • a terminal (console), including the upcoming Windows Terminal

  • Visual Studio Code with the PowerShell extension; this cross-platform editor (IDE) is meant to supersede the obsolescent Windows PowerShell ISE.

These environments behaves as you would expect:

  • stderr (standard error) output is passed through to the display by default
  • stderr lines print just like regular lines.

Note:

  • In the context of remoting and background jobs, stderr output is still sent to PowerShell's error stream.

  • There are additional, host-independent problems with the use of 2> with external programs.

See this answer for a comprehensive overview of all problems.

like image 184
mklement0 Avatar answered Sep 18 '22 12:09

mklement0