5

I am aware ErrorAction argument, also $ErrorActionPreference,$Error and $.

Context

The issue I would like to solve is when running an external command (eg choco or git) it gives error which should be warning or not even warning, at least in my task context.

Because of their exit code PowerShell considers that result as error in any sense, for example writes out red to output, etc, which is not desirable in my task's context.

I can suppress those commands error output with -ErrorAction or 2> $null, but I have a bad feeling about completely vanishing any errors this way of the particular command.

I would like only ignore that known "not a problem in this context for me" type error.

Question

Is there any way to handle a command's error ignore some specific, but treat normally all other error conditions?

henrycarteruk
  • 12,708
  • 2
  • 36
  • 40
g.pickardou
  • 32,346
  • 36
  • 123
  • 268
  • 2
    You could use [try/catch](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-6) to only catch (and log/ignore) specific errors and allow all others to be dealt with elsewhere. – boxdog Feb 06 '19 at 09:00
  • 1
    @boxdog: `try` / `catch` does not apply to calling external programs. – mklement0 Feb 06 '19 at 15:17
  • @boxdog: I tried, but concluded it does not work in this case. – g.pickardou Feb 06 '19 at 15:38
  • Yeah, as mklement0 says, it won't work with external commands. I misread your question, but looks like you might have a solution from JamesC. – boxdog Feb 06 '19 at 15:51

2 Answers2

5
  • In a regular console window, in PowerShell v3 and above, stderr lines print just like stdout lines.

    • This is desirable, because stderr is used by many programs not just to report genuine errors, but anything that is not data, such as status messages.

    • Stderr lines (unless redirected - see below) print straight through the console (whereas stdout lines are sent to PowerShell's success output stream, where they can be collected in a variable, sent through the pipeline, or redirected with > / >>).

  • Regrettably, the PowerShell ISE, even in v5.1, does print stderr lines in red, in the same format as PowerShell errors.

    • Visual Studio Code with the PowerShell extension doesn't have this problem, and is worth migrating to in general, given that all future development effort will focus there, and given that it also works with the cross-platform PowerShell Core edition.
  • Well-behaved console applications solely use their exit code to signal success (exit code 0) vs. failure (any nonzero exit code). PowerShell saves the exit code of the most recently invoked console application in its automatic $LASTEXITCODE variable.

  • As an aside:

    • Common parameters -ErrorAction and -ErrorVariable cannot be used with external programs.
    • Similarly, preference variable $ErrorActionPreference has no effect (except accidentally, due to this bug, as of PowerShell v5.1 / PowerShell Core 6.2.0-preview.4).
    • try / catch cannot be used to detect and handle an external program's failure.
    • For a comprehensive overview of PowerShell's complex error handling rules, see this GitHub docs issue.

Note: I'm using the following external commands in the examples below, which produce 1 line of stdout and 1 line of stderr output (note that the redirection >&2 is handled by cmd, not PowerShell, because it is inside the '...'; it is used to produce stderr output):

cmd /c 'echo stdout & echo stderr >&2' 

A variant that makes the command signal failure, via a nonzero exit code (exit 1):

cmd /c 'echo stdout & echo stderr >&2 & exit 1' 

Therefore, normally the following should suffice:

$stdOut = cmd /c 'echo stdout & echo stderr >&2' # std*err* will print to console
if ($LASTEXITCODE -ne 0) { Throw "cmd failed." } # handle error

This captures stdout output in variable $stdout and passes stderr output through to the console.


If you want to collect stderr output for later and only show them in case of error:

$stdErr = @() # initialize array for collecting stderr lines.

# Capture stdout output while collecting stderr output in $stdErr.
$stdOut = cmd /c 'echo stdout & echo stderr >&2 & exit 1' 2>&1  | Where-Object { 
  $fromStdErr = $_ -is [System.Management.Automation.ErrorRecord]
  if ($fromStdErr) { $stdErr += $_.ToString() }
  -not $fromStdErr # only output line if it came from stdout
}

# Throw an error, with the collected stderr lines included.
if ($LASTEXITCODE -ne 0) { 
  Throw "cmd failed with the following message(s): $stdErr"
}
  • Note the 2>&1 redirection, which instructs PowerShell to send stderr lines (stream 2, the error stream) through the pipeline (stream 1, the success stream) as well.

  • In the Where-Object block, $_ -is [System.Management.Automation.ErrorRecord] is used to identify stderr lines, because PowerShell wraps such lines in instances of that type.

Alternatively, you could collect stderr output in a temporary file (2>/path/to/tmpfile), read its contents, and delete it.

This GitHub issue proposes introducing the option of collecting redirected stderr output in a variable, analogous to how you can ask cmdlets to collect errors in a variable via common parameter
-ErrorVariable.

There are two caveats:

  • Inherently, by only printing stderr lines later, the specific context relative to the stdout output may be lost.

  • Due to a bug as of Windows PowerShell v5.1 / PowerShell Core 6.2.0, $ErrorActionPreference = Stop mustn't be in effect, because the 2>&1 redirection then triggers a script-terminating error as soon as the first stderr line is received.


If you want to selectively act on stderr lines as they're being received:

Note:

  • Inherently, as you're processing the lines, you won't yet know whether the program will report failure or success in the end.

  • The $ErrorActionPreference = 'Stop' caveat applies here too.

The following example filters out stderr line stderr1 and prints line stderr2 to the console in red (text only, not like a PowerShell error).

$stdOut = cmd /c 'echo stdout & echo stderr1 >&2 & echo stderr2 >&2' 2>&1 |
  ForEach-Object { 
    if ($_ -is [System.Management.Automation.ErrorRecord]) { # stderr line
      $stdErrLine = $_.ToString()
      switch -regex ($stdErrLine) {
        'stderr1' { break } # ignore, if line contains 'stderr1'
        default   { Write-Host -ForegroundColor Red $stdErrLine }
      }
    } else { # stdout line
      $_ # pass through
    }
  }

# Handle error.
if ($LASTEXITCODE -ne 0) { Throw "cmd failed." }
mklement0
  • 382,024
  • 64
  • 607
  • 775
-1

I've come across this situation with git before, and worked around it by using $LASTEXITCODE.

Not sure if it's applicable to your situation, or to choco as I've not had issues with it.

# using git in powershell console sends everything to error stream
# this causes red text when it's not an error which is annoying
# 2>&1 redirects all to stdout, then use exit code to 'direct' output

$git_output = Invoke-Expression "& [git pull etc] 2>&1"

# handle output using exitcode
if ($LASTEXITCODE -ne 0) { throw $git_output }
else { Write-Output $git_output }
henrycarteruk
  • 12,708
  • 2
  • 36
  • 40
  • 1
    You don't need `Invoke-Expression` (which should generally be avoided) to perform the desired redirection; e.g., `$out = cmd /c 'echo out & echo err >&2' 2>&1`. In the regular console, in v3 and above, passed-through stderr lines are _not_ printed like PowerShell errors - that only happens in the _ISE_. However, by echoing the captured merged streams as-is (`Write-Output $git_output`), any stderr lines (which may be present even if `$LASTEXITCODE` is `0`) _are_ then printed like PowerShell errors - even in the regular console. – mklement0 Feb 06 '19 at 15:16