How can I display a 'naked' error message in PowerShell without an accompanying stacktrace?

后端 未结 5 1911
星月不相逢
星月不相逢 2021-02-05 04:25

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 Tea
相关标签:
5条回答
  • 2021-02-05 04:55

    Setting the automatic $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.
    Adding a new error view to PowerShell that is single-line yet always contains all crucial information is being discussed for v6.

    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).

    0 讨论(0)
  • 2021-02-05 04:57

    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)
    
    0 讨论(0)
  • 2021-02-05 05:00

    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.

    0 讨论(0)
  • 2021-02-05 05:05

    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"
    
    0 讨论(0)
  • 2021-02-05 05:14

    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!"
    }
    
    0 讨论(0)
提交回复
热议问题