Catching FULL exception message

155,606

Solution 1

Errors and exceptions in PowerShell are structured objects. The error message you see printed on the console is actually a formatted message with information from several elements of the error/exception object. You can (re-)construct it yourself like this:

$formatstring = "{0} : {1}`n{2}`n" +
                "    + CategoryInfo          : {3}`n" +
                "    + FullyQualifiedErrorId : {4}`n"
$fields = $_.InvocationInfo.MyCommand.Name,
          $_.ErrorDetails.Message,
          $_.InvocationInfo.PositionMessage,
          $_.CategoryInfo.ToString(),
          $_.FullyQualifiedErrorId

$formatstring -f $fields

If you just want the error message displayed in your catch block you can simply echo the current object variable (which holds the error at that point):

try {
  ...
} catch {
  $_
}

If you need colored output use Write-Host with a formatted string as described above:

try {
  ...
} catch {
  ...
  Write-Host -Foreground Red -Background Black ($formatstring -f $fields)
}

With that said, usually you don't want to just display the error message as-is in an exception handler (otherwise the -ErrorAction Stop would be pointless). The structured error/exception objects provide you with additional information that you can use for better error control. For instance you have $_.Exception.HResult with the actual error number. $_.ScriptStackTrace and $_.Exception.StackTrace, so you can display stacktraces when debugging. $_.Exception.InnerException gives you access to nested exceptions that often contain additional information about the error (top level PowerShell errors can be somewhat generic). You can unroll these nested exceptions with something like this:

$e = $_.Exception
$msg = $e.Message
while ($e.InnerException) {
  $e = $e.InnerException
  $msg += "`n" + $e.Message
}
$msg

In your case the information you want to extract seems to be in $_.ErrorDetails.Message. It's not quite clear to me if you have an object or a JSON string there, but you should be able to get information about the types and values of the members of $_.ErrorDetails by running

$_.ErrorDetails | Get-Member
$_.ErrorDetails | Format-List *

If $_.ErrorDetails.Message is an object you should be able to obtain the message string like this:

$_.ErrorDetails.Message.message

otherwise you need to convert the JSON string to an object first:

$_.ErrorDetails.Message | ConvertFrom-Json | Select-Object -Expand message

Depending what kind of error you're handling, exceptions of particular types might also include more specific information about the problem at hand. In your case for instance you have a WebException which in addition to the error message ($_.Exception.Message) contains the actual response from the server:

PS C:\> $e.Exception | Get-Member

   TypeName: System.Net.WebException

Name             MemberType Definition
----             ---------- ----------
Equals           Method     bool Equals(System.Object obj), bool _Exception.E...
GetBaseException Method     System.Exception GetBaseException(), System.Excep...
GetHashCode      Method     int GetHashCode(), int _Exception.GetHashCode()
GetObjectData    Method     void GetObjectData(System.Runtime.Serialization.S...
GetType          Method     type GetType(), type _Exception.GetType()
ToString         Method     string ToString(), string _Exception.ToString()
Data             Property   System.Collections.IDictionary Data {get;}
HelpLink         Property   string HelpLink {get;set;}
HResult          Property   int HResult {get;}
InnerException   Property   System.Exception InnerException {get;}
Message          Property   string Message {get;}
Response         Property   System.Net.WebResponse Response {get;}
Source           Property   string Source {get;set;}
StackTrace       Property   string StackTrace {get;}
Status           Property   System.Net.WebExceptionStatus Status {get;}
TargetSite       Property   System.Reflection.MethodBase TargetSite {get;}

which provides you with information like this:

PS C:\> $e.Exception.Response

IsMutuallyAuthenticated : False
Cookies                 : {}
Headers                 : {Keep-Alive, Connection, Content-Length, Content-T...}
SupportsHeaders         : True
ContentLength           : 198
ContentEncoding         :
ContentType             : text/html; charset=iso-8859-1
CharacterSet            : iso-8859-1
Server                  : Apache/2.4.10
LastModified            : 17.07.2016 14:39:29
StatusCode              : NotFound
StatusDescription       : Not Found
ProtocolVersion         : 1.1
ResponseUri             : http://www.example.com/
Method                  : POST
IsFromCache             : False

Since not all exceptions have the exact same set of properties you may want to use specific handlers for particular exceptions:

try {
  ...
} catch [System.ArgumentException] {
  # handle argument exceptions
} catch [System.Net.WebException] {
  # handle web exceptions
} catch {
  # handle all other exceptions
}

If you have operations that need to be done regardless of whether an error occured or not (cleanup tasks like closing a socket or a database connection) you can put them in a finally block after the exception handling:

try {
  ...
} catch {
  ...
} finally {
  # cleanup operations go here
}

Solution 2

I found it!

Simply print out $Error[0] for the last error message.

Solution 3

The following worked well for me

try {
    asdf
} catch {
    $string_err = $_ | Out-String
}

write-host $string_err

The result of this is the following as a string instead of an ErrorRecord object

asdf : The term 'asdf' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Users\TASaif\Desktop\tmp\catch_exceptions.ps1:2 char:5
+     asdf
+     ~~~~
    + CategoryInfo          : ObjectNotFound: (asdf:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Solution 4

I keep coming back to these questions trying to figure out where exactly the data I'm interested in is buried in what is truly a monolithic ErrorRecord structure. Almost all answers give piecemeal instructions on how to pull certain bits of data.

But I've found it immensely helpful to dump the entire object with ConvertTo-Json so that I can visually see LITERALLY EVERYTHING in a comprehensible layout.

    try {
        Invoke-WebRequest...
    }
    catch {
        Write-Host ($_ | ConvertTo-Json)
    }

Use ConvertTo-Json's -Depth parameter to expand deeper values, but use extreme caution going past the default depth of 2 :P

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertto-json

Solution 5

Option 1: Simple but effective, good enough for most purposes

try {1/0} catch { $_ | Format-List * -Force | Out-String }

Results in:

PSMessageDetails      :
Exception             : System.Management.Automation.RuntimeException: Attempted to divide by zero. ---> System.DivideByZeroException: Attempted to divide by zero.
                           --- End of inner exception stack trace ---
                           at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
                           at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
TargetObject          :
CategoryInfo          : NotSpecified: (:) [], RuntimeException
FullyQualifiedErrorId : RuntimeException
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}

Option 2: Also print the invocation info

try {1/0} catch { $_ | Format-List * -Force | Out-String ; $_.InvocationInfo | Format-List * -Force | Out-String }

Results in:

PSMessageDetails      :
Exception             : System.Management.Automation.RuntimeException: Attempted to divide by zero. ---> System.DivideByZeroException: Attempted to divide by zero.
                           --- End of inner exception stack trace ---
                           at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
                           at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
TargetObject          :
CategoryInfo          : NotSpecified: (:) [], RuntimeException
FullyQualifiedErrorId : RuntimeException
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}





MyCommand             :
BoundParameters       : {}
UnboundArguments      : {}
ScriptLineNumber      : 1
OffsetInLine          : 6
HistoryId             : -1
ScriptName            :
Line                  : try {1/0} catch { $_ | Format-List * -Force | Out-String ; $_.InvocationInfo | Format-List * -Force | Out-String }
PositionMessage       : At line:1 char:6
                        + try {1/0} catch { $_ | Format-List * -Force | Out-String ; $_.Invocat ...
                        +      ~~~
PSScriptRoot          :
PSCommandPath         :
InvocationName        :
PipelineLength        : 0
PipelinePosition      : 0
ExpectingInput        : False
CommandOrigin         : Internal
DisplayScriptPosition :

Option 3: Both of the above plus all the inner exceptions

try {1/0} catch { $Exception = $_; $Exception | Format-List * -Force | Out-String ; $Exception.InvocationInfo | Format-List * -Force | Out-String ; for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException)) { Write-Host ("$i" * 80) ; $Exception | Format-List * -Force | Out-String } }

PSMessageDetails      :
Exception             : System.Management.Automation.RuntimeException: Attempted to divide by zero. ---> System.DivideByZeroException: Attempted to divide by zero.
                           --- End of inner exception stack trace ---
                           at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
                           at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
TargetObject          :
CategoryInfo          : NotSpecified: (:) [], RuntimeException
FullyQualifiedErrorId : RuntimeException
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}





MyCommand             :
BoundParameters       : {}
UnboundArguments      : {}
ScriptLineNumber      : 1
OffsetInLine          : 6
HistoryId             : -1
ScriptName            :
Line                  : try {1/0} catch { $Exception = $_; $Exception | Format-List * -Force | Out-String ; $Exception.InvocationInfo | Format-List * -Force | Out-String ; for ($i = 0; $Exception;
                        $i++, ($Exception = $Exception.InnerException)) { Write-Host ("$i" * 80) ; $Exception | Format-List * -Force | Out-String  }  }
PositionMessage       : At line:1 char:6
                        + try {1/0} catch { $Exception = $_; $Exception | Format-List * -Force  ...
                        +      ~~~
PSScriptRoot          :
PSCommandPath         :
InvocationName        :
PipelineLength        : 0
PipelinePosition      : 0
ExpectingInput        : False
CommandOrigin         : Internal
DisplayScriptPosition :




00000000000000000000000000000000000000000000000000000000000000000000000000000000


PSMessageDetails      :
Exception             : System.Management.Automation.RuntimeException: Attempted to divide by zero. ---> System.DivideByZeroException: Attempted to divide by zero.
                           --- End of inner exception stack trace ---
                           at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
                           at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
TargetObject          :
CategoryInfo          : NotSpecified: (:) [], RuntimeException
FullyQualifiedErrorId : RuntimeException
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}
Share:
155,606

Related videos on Youtube

JustAGuy
Author by

JustAGuy

Updated on March 26, 2021

Comments

  • JustAGuy
    JustAGuy about 3 years

    Consider:

    Invoke-WebRequest $sumoApiURL -Headers @{"Content-Type"= "application/json"} -Credential $cred -WebSession $webRequestSession -Method post -Body $sumojson -ErrorAction Stop
    

    This throws the following exception:

    Enter image description here

    How can I catch it entirely or at least filter out the "A resource with the same name already exist."?

    Using $_.Exception.GetType().FullName yields

    System.Net.WebException

    and $_.Exception.Message gives

    The remote server returned an error: (400) Bad Request.

  • JustAGuy
    JustAGuy almost 8 years
    I already found the answer but this is by far more detailed. Cheers.
  • Doug J. Huras
    Doug J. Huras about 7 years
    Thanks for the thorough explanation. Very useful.
  • BrainSlugs83
    BrainSlugs83 almost 6 years
    Instead of calling Write-Host -Foreground Red ... you should just use the Write-Error cmdlet. -- that will keep the output consistent across all host applications.
  • Ansgar Wiechers
    Ansgar Wiechers almost 6 years
    @BrainSlugs83 Write-Error would throw a non-terminating error on top of displaying the message. That's something I wanted to avoid here.
  • John Zabroski
    John Zabroski almost 6 years
    Up vote if you think PowerShell's default formatting of errors was designed to irritate C# engineers and encourage them to throw PowerShell into a blackhole.
  • BrainSlugs83
    BrainSlugs83 over 5 years
    @AnsgarWiechers Write-Error doesn't throw anything, it just writes output to the standard error stream. -- The default behavior is to print it like it threw something, but that's up to each environment to implement. If you are running PowerShell from a custom C# host it's super helpful if scripts write their text-based outputs to the proper streams.
  • Ansgar Wiechers
    Ansgar Wiechers over 5 years
    @BrainSlugs83 $ErrorActionPreference = 'Stop'; try {Write-Error 'foo'} catch {$_.GetType().FullName} You were saying?
  • BrainSlugs83
    BrainSlugs83 over 5 years
    That's very strange. It still behaves differently than throw. -- throw works with try/catch even with $ErrorActionPreference is Continue, versus this only seems to work if it's set to Stop? -- I hadn't realized the words non-terminating were significant in your previous statement. -- Thanks for showing me this, I learned something new today. :-)
  • Ansgar Wiechers
    Ansgar Wiechers over 5 years
    @BrainSlugs83 Related. Also related.
  • BrainSlugs83
    BrainSlugs83 over 5 years
    Thanks, @AnsgarWiechers!
  • ZakiMa
    ZakiMa over 5 years
    This is the best approach - it catches both "throw <something>" and invocations.
  • Ansgar Wiechers
    Ansgar Wiechers over 4 years
    @AltimusPrime What do you mean "how"?
  • Altimus Prime
    Altimus Prime over 4 years
    I didn't know until now that you could execute multi-line code directly in PowerShell.
  • Robert K. Bell
    Robert K. Bell over 4 years
    ConvertTo-Json was failing me, with The type 'System.Collections.ListDictionaryInternal' is not supported for serialization or deserialization of a dictionary. Keys must be strings.; I got what I needed by using Out-String instead.
  • eniacAvenger
    eniacAvenger about 4 years
    Perfect, it gave me all the information I need, especially line number of the error.
  • Jeetcu
    Jeetcu almost 4 years
    Great... Much easier than anything :)
  • JonoB
    JonoB almost 4 years
    You might need to limit the depth of the JSON with the -Depth flag. I had examples of the JSON parsing taking gigabytes of RAM converting an ErrorRecord object in a Catch statement...
  • galaxis
    galaxis about 2 years
    For that output, all u need is "$_" (default Error object "ToString()") in the "catch". Assigning to a string variable like that would be handy tho, if say, u (also) wanted to write the msg to a log, change the color, etc.