Custom errors

The most common ways of reporting errors in PowerShell are through the Write-Error Cmdlet or the Throw statement. Write-Error writes non-terminating errors while the throw statement writes terminating errors which halt execution but are not very descriptive. Both methods write Management.Automation.ErrorRecord objects to the error stream. Write-Error lets you customize the ErrorRecord it reports through its parameters; this allows you to provide tailored specifics about an error, assisting the user to avoid or resolve the reason that caused the problem. In contrast, the Throw statement only lets us provide a custom message, which in some cases could be enough.

Custom ErrorRecord objects are not very common, but if you ever want to provide better details about an error that is not very clear, you can create and report your own. For instance, I wrote a function that appends data to an existing CSV file; before any piped input is processed, the function will report one of three custom ErrorRecord objects as a terminating error if the destination -or target- file:

  1. does not exist
  2. is empty, or
  3. has a different character encoding than the specified -or default- encoding

After the piped input is processed but before the processed data is written to disk, the function compares the original header fields against the processed data header fields, if any inconsistency is found the function reports the fourth custom ErrorRecord and stops execution without appending the processed data.

I could have used a Throw statement to report the problem and stop execution, but decided to make my advanced function behave more professionally with custom ErrorRecord objects.

PowerShell 2.0 introduced advanced functions which are very similar to Cmdlets. Through advanced functions, we haves access to most members of the PSCmdlet Class. The object through which we have access to these members is the $PSCmdlet object, which is only available in advanced functions. The $PSCmdlet object lets us report a terminating error through its ThrowTerminatingError method which takes one argument, an ErrorRecord. To construct an ErrorRecord we need four arguments:

  • [System.Exception]$exception
    • The Exception that will be associated with the ErrorRecord
  • [System.String]$errorId
    • A scripter-defined identifier of the error.
    • This identifier must be a non-localized string for a specific error type.
  • [Management.Automation.ErrorCategory]$errorCategory
    • An ErrorCategory enumeration that defines the category of the error.
  • [System.Object]$targetObject
    • The object that was being processed when the error took place.

Notice that the first argument is a System.Exception, this is the object from which all Exception objects derive from. There are three ways to construct most Exception objects, one that takes no arguments, just the full name of the exception; another that takes one argument:

  • [System.String]$message
    • Describes the Exception to the user.

…and the third one that takes two arguments:

  • [System.String]$message
    • Describes the Exception to the user.
  • [System.Exception]$innerException
    • The Exception instance that caused the Exception association with the ErrorRecord.

Did you notice that the last argument is also a System.Exception? Some Exception objects have either customized constructors or no constructors at all; those Exception objects are the exception, if you know what I mean, so will stick with the regulars.

This code snippet shows how you would report a terminating error in an advanced function:

…first the Exception, next the ErrorRecord and finally report the ErrorRecord.

To make the custom ErrorRecord creation process a bit simpler, I wrote the New-ErrorRecord function…

…takes all the arguments used to build both an ErrorRecord and an Exception, six in all. The first four are required and the last two are optional. The function guards against nonexistent Exception objects but allows the absence of the ‘System.’ prefix from its Exception argument, adhering to PowerShell’s tolerance. You can omit or include the ‘System.’ prefix from the full name when creating an Exception object.

The function shields against nonexistent Exception objects by relying on a list of available Exception objects that is retrieved through another function, Get-AvailableExceptionsList. The Exception argument -which is of Type String, not Exception- is compared against the list to verify its availability. The other precaution in the New-ErrorRecord function is for those exceptional Exception objects that make the available list but have customized constructors, that is, different constructors from regular Exception objects. In either case, New-ErrorRecord reports a terminating error.

The Get-AvailableExceptionsList function…

…retrieves all Type objects -from the assemblies in the current domain- whose names contain the word Exception but also excludes those whose names match part of some of the exceptional Exception objects’ names. Then, if the Exception has at least one constructor, the function outputs its full name. The function can be called independently to provide a list to select the adequate Exception object name before you create a custom ErrorRecord. This function must be defined before calling the New-ErrorRecord function.

After defining both functions, the code snippet we previously used to report a terminating error in an advanced function becomes:

…a bit simpler.

Remember, ErrorRecord objects are not just for terminating errors. To report a customized non-terminating ErrorRecord, create it with the function New-ErrorRecord and pass it to Write-Error through its ErrorRecord parameter:

This is the least you need to know about custom ErrorRecord objects, and how to report them as terminating or non-terminating errors. I know -through personal experience- this topic can be unapproachable, I threw away the ten-foot pole and explored it closer; we are good friends now, but there is still more to discover. At least I feel that this information can help you create custom ErrorRecord objects to better inform the user.


About the author: Robert Robelo

Related Posts