44

How can I write to standard error from PowerShell, or trap errors such that:

  • An error message is displayed as an error (truly writing to standard error so that TeamCity and Octopus see it as an error)
  • No stack trace garbage muddles my beautiful, concise error message

All these years I've survived by throwing errors or writing via Write-Error, but I'm tired and old, and in my scripts I just want to see one concise error message. I've been trying every combination of trap, throw, Write-Error, and -ErrorAction, to no avail:

try {
  throw "error" # Sample code for a stack overflow. In the theater
  # of your mind, imagine there is code here that does something real and useful
} catch {
  Write-Error "An error occurred attempting to 'do something.' Have you tried rebooting?"
}

Here's the user experience I want to see:

C:\> & .\Do-Something.ps1
An error occurred attempting to 'do something.' Have you tried rebooting?

C:\> ▏

Instead I get:

C:\> & .\Do-Something.ps1
An error occurred attempting to 'do something.' Have you tried rebooting?
At line:1 char:1
+ Do-RealWork
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Do-RealWork

C:\> ▏
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Peter Seale
  • 4,835
  • 4
  • 37
  • 45
  • 1
    FWIW `$error[0].Exception.Message` contains the error message but I'm not sure this will be useful to you. – sodawillow Jun 27 '16 at 23:29

6 Answers6

27

Preface re what doesn't work:

  • Setting the $ErrorView preference variable $ErrorView variable to 'CategoryView' causes PowerShell to output concise, single-line error representations instead, but this representation may not always include enough information, because the error message is typically not included; on the plus side, the text passed to Throw "..." is reflected, but, by contrast, Write-Error output contains no specific information while 'CategoryView' is in effect.

  • While PowerShell (Core) 7+ now defaults to more concise error formatting (in the simplest case printing just the error message) via its new $ErrorView default, ConciseView, this output is only situationally single-line.
    Notably, it is still multiline if the error occurs in a script (.*ps1 file) or in a function dot-sourced from a script, (including profile scripts), in which case the error message is preceded by additional lines reporting the script's file path, source-code line, and line number.


Provided that your PowerShell code is run from a console (uses a console host), use [Console]::Error.WriteLine(), which unconditionally writes to the outside world's stderr (standard error stream):

[Console]::Error.WriteLine("An error occurred ... Have you tried rebooting?")

Note:

  • This won't work from non-console hosts such as the PowerShell ISE.

  • [Console]::Error.WriteLine() output doesn't print in red in the console [1].


Sadly, there is no single solution that works both from within PowerShell (across hosts) and from outside of it:

  • [Console]::Error.WriteLine(), while writing properly to stderr for the outside world, cannot have its output captured or suppressed inside PowerShell, and only works with the PowerShell console host.

  • Similarly, $host.ui.WriteErrorLine(), even though works with all hosts, it is a UI method that works outside PowerShell's stream system as well and therefore its output too cannot be captured or suppressed in PowerShell.
    More importantly, it doesn't write to the outside world's stderr (it behaves like Write-Error in this respect, see below).

  • Inside PowerShell, only Write-Error writes to PowerShell's error stream, so its output can be captured / suppressed.
    However, unfortunately, Write-Error (apart from being noisy) does not write to the outside world's stderr, unless, bizarrely, stderr is explicitly being redirected - see this answer of mine for details.


[1] Peter (the OP himself) offers a workaround for that:

[Console]::ForegroundColor = 'red'
[Console]::Error.WriteLine("An error occurred ... Have you tried rebooting?")
[Console]::ResetColor()

suneg's helpful answer provides a function wrapper for it.

Fortunately, PowerShell automatically omits the color codes when it detects that the output is being redirected (to a file).

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 3
    great answer. Without adding new answer, I'd like to recommend yet another way, instead of using `[Console]::Error.WriteLine`. Except that it doesn't work in ISE, it doesn't add to $Error variable. If I ever use Write-Error, it's because I want to process the error at later time using $Error variable, I would then hide the output completely: `Write-Error "hmm" -ErrorAction SilentlyContinue`, and optionally to also show the error text at that point in red, I'll use `Write-Host "hmm" -ForegroundColor Red`, beware though, Write-Host text doesn't pipe and also redirect to file is not possible – papo May 13 '18 at 23:33
  • Thanks, @papo. In PSv5+ you can redirect `Write-Host` output with `6>` – mklement0 May 14 '18 at 02:33
14

Building on the idea in a previous answer, you can override the built-in Write-Error cmdlet temporarily with a custom function.

# Override the built-in cmdlet with a custom version
function Write-Error($message) {
    [Console]::ForegroundColor = 'red'
    [Console]::Error.WriteLine($message)
    [Console]::ResetColor()
}

# Pretty-print "Something is wrong" on stderr (in red).
Write-Error "Something is wrong"

# Setting things back to normal 
Remove-Item function:Write-Error

# Print the standard bloated Powershell errors
Write-Error "Back to normal errors"

With this you are utilizing the fact that Powershell Functions takes precedence over cmdlets.

https://technet.microsoft.com/en-us/library/hh848304.aspx

This is the most elegant approach I've been able to come up with to both show beautiful and concise error messages, as well as letting TeamCity detect problems easily.

Community
  • 1
  • 1
suneg
  • 301
  • 3
  • 5
4

I needed to solve this problem myself recently so I put together a Write-ErrorMessage function as detailed here: https://intellitect.com/powershell-write-error-without-writing-stack-trace/

Specifically, I leveraged the combination

Write-Error -Message $err -ErrorAction SilentlyContinue
$Host.UI.WriteErrorLine($errorMessage)
Chris
  • 3,400
  • 1
  • 27
  • 41
  • 2
    A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](//stackoverflow.com/help/deleted-answers) – Zoe Apr 17 '18 at 06:32
  • 1
    Just $Host.UI.WriteErrorLine(), instead of Write-Error did the trick for me in Sitecore PSE – Yuriy Nov 18 '19 at 15:32
1

The best way in my opinion to trap errors in PowerShell would be to use the following:

$Error[0].Exception.GetType().FullName

Here is an example of how to use this properly. Basically test what you are trying to do in PowerShell with different scenarios in which your script will fail.

Here is a typical PowerShell error message:

PS C:\> Stop-Process -Name 'FakeProcess'
Stop-Process : Cannot find a process with the name "FakeProcess". Verify the process name and call the cmdlet again.
At line:1 char:1
+ Stop-Process -Name 'FakeProcess'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (FakeProcess:String) [Stop-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.StopProcessCommand

Next you would get the exception of the error message:

PS C:\> $Error[0].Exception.GetType().FullName
Microsoft.PowerShell.Commands.ProcessCommandException

You would setup your code to catch the error message as follows:

Try
{
    #-ErrorAction Stop is needed to go to catch statement on error
    Get-Process -Name 'FakeProcess' -ErrorAction Stop
}
Catch [Microsoft.PowerShell.Commands.ProcessCommandException]
{
    Write-Host "ERROR: Process Does Not Exist. Please Check Process Name"
}

Output would look like the following instead of the Powershell standard error in above example:

ERROR: Process Does Not Exist. Please Check Process Name

Lastly, you can also use multiple catch blocks to handle multiple errors in your code. You can also include a "blanket" catch block to catch all errors you haven't handled. Example:

Try
{
    Get-Process -Name 'FakeProcess' -ErrorAction Stop
}

Catch [Microsoft.PowerShell.Commands.ProcessCommandException]
{
    Write-Host "ERROR: Process Does Not Exist. Please Check Process Name"
}

Catch [System.Exception]
{
    Write-Host "ERROR: Some Error Message Here!"
}

Catch
{
    Write-Host "ERROR: I am a blanket catch to handle all unspecified errors you aren't handling yet!"
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tyler Helder
  • 594
  • 3
  • 11
  • 1
    Writing error messages with `Write-Output` is ill-advised, because it writes to PowerShell's _success_ (output) stream. Aside from that, your post is more focused on exception handling per se, which is not what the OP's question is about (he clearly knows how to trap exceptions, as demonstrated in his question). – mklement0 Jun 28 '16 at 01:15
  • Would Write-Host be a better choice for this? – Tyler Helder Jun 28 '16 at 01:22
  • Honestly I'm hoping someone comes along and tells me there's a setting I can set on $host that will suppress stack traces. I really need real errors (written to stderr) so that errors are bubbled to places that look for error output (like TeamCity builds). Looking in a 20000 line build log in TeamCity for error messages is hard; that's why we need it written to stderr – Peter Seale Jun 28 '16 at 01:44
  • @TylerHelder: `Write-Host` is marginally better, because it doesn't pollute the output stream, but ultimately you want something that writes to the dedicated _error_ stream - both from PowerShell's own perspective and the outside world's. Sadly, even PowerShell's own (wordy) `Write-Error` doesn't satisfy these criteria. – mklement0 Jun 28 '16 at 01:54
  • 1
    @PeterSeale: Setting the `$ErrorView` preference variable to `'CategoryView'` outputs a more concise representation of the error, but, alas, that is typically not enough, because the actual error _message_ is missing. – mklement0 Jun 28 '16 at 01:57
0

Based on suneg's answer I wrote the following functions to allow you to effortlessly swap Write-Error with a custom function and back. I also added a check if user is invoking write-error from PowerShell ISE

# Override the built-in cmdlet with a custom version
function New-ErrorFunc {
        function Dyn($message){
            param($message,$ErrorAction)
            if($psISE){
                $Host.UI.WriteErrorLine($message)
            }
            else{
            [Console]::ForegroundColor = 'red'
            [Console]::Error.WriteLine($message)
            [Console]::ResetColor()
            }
           if($ErrorAction -eq 'Stop'){
           Break
           }
        }

    return ${function:Dyn}
}
function Set-ErrorFunc(){
    param([bool]$custom=$true)
    if($custom){
    $dynfex= New-ErrorFunc
    Invoke-Expression -Command "function script:Write-Error{ $dynfex }"
    }
    else {
        $custom= Get-Command Write-Error | Where-Object {$_.CommandType -eq 'Function'} 
        if($custom){ Remove-Item function:Write-Error }
   }
}

#User our Custom Error Function
Set-ErrorFunc 
# Pretty-print "Something is wrong" on stderr (in red).
Write-Error "Something is wrong"

# Setting things back to normal 
Set-ErrorFunc -custom $false
# Print the standard bloated Powershell errors
Write-Error "Back to normal errors"
vandre
  • 778
  • 6
  • 16
0

Powershell 7 provides new error view category 'ConciseView' which should suppress the 'noises'.

Powershell:

$ErrorView = 'ConciseView'
Get-ChildItem -path 'C:\NoRealDirectory'

Output:

Get-ChildItem: Cannot find path 'C:\NoRealDirectory' because it does not exist.

powershell-7.2#errorview

Jokkeri
  • 1,001
  • 1
  • 13
  • 35
  • 1
    Worth noting that `ConciseView` is the _default_ view in PowerShell 7+. In other words: You only need to set `$ErrorView` if you wan the old, noisy display back (`$ErrorView = 'NormalView'`). Note, however, that even with `ConciseView` you can get _multiline_ error messages, namely if the error occurs in a script (`.*ps1` file) or in a `function` dot-sourced from a script, (including profile scripts), in which case the error message is preceded by additional lines reporting the script's file path, source-code line, and line number. – mklement0 Jun 16 '23 at 14:30