PowerShell Errors and Exceptions Handling

This entry is part 9 of 9 in the series Learn PowerShell

A Tale of Two Pipelines

PowerShell actually leverages two pipelines. If you’ve been following along in the Learn PowerShell series we’ve only engaged the primary pipeline so far. This is because examples provided up to this point were free from PowerShell errors. When PowerShell code runs successfully it engages the primary pipeline. This is sometimes referred to as the success pipeline or the output stream.

In the real world, your PowerShell code will encounter the unexpected. When your code encounters an error or exception it can also engage the error pipeline, or the error stream. You need to be able to account for and deal with PowerShell errors, and PowerShell exceptions. In this lesson, we’ll dive into how to handle PowerShell errors and exceptions.

Video

If you prefer video format over written documentation I discuss this topic in the following TechThoughts video:

PowerShell Error Types

In many programming languages an error results in the program halting, or terminating. The program won’t continue until the issue is corrected. You have likely experienced this at some point as a program crashed, and wouldn’t resume. PowerShell doesn’t typically behave this way and doesn’t have many terminating errors.

Remember that PowerShell is an operationally focused language that aims to make your life easier. If you wanted to get processes information with Get-Process, would you expect PowerShell to crash if it couldn’t query a process? Or would you expect it to log the error, and continue to do as much operational work as possible? Because of this operational nature, PowerShell errors and exceptions are typically non-terminating.

Non-terminating

# non-terminating error
1/0;Write-Host 'Hello, will I run after an error?'

The above example is a classic divide by zero error. Because dividing by zero is not possible, this should throw an exception. Note the ; symbol. In PowerShell this allows you to chain commands together. In this example that means PowerShell will first divide by zero, and then attempt to write a string to the console with Write-Host.

Launch a new PowerShell console and try the example. PowerShell runs both commands. Notice that you still get a divide by zero error, but that doesn’t stop PowerShell from moving on to the next command.

# non-terminating errors don't stop loops
$collection = @(
    'C:\Test\newcsv.csv',
    'c:\nope\nope.txt'
    'C:\Test\newcsv2.csv'
)
foreach ($item in $collection) {
    Get-Item $item
}

In this example we declare an array ($collection) and load it with three file paths. It is very possible that one of these doesn’t actually exist, or maybe we don’t have access to it. In the foreach loop, Get-Item may encounter an error accessing one of these as a result. Try this example on your computer by providing files that both do and don’t exist. Notice that PowerShell will continue processing the loop, regardless of how many errors are encountered.

Terminating

It is sometimes undesirable to have non-terminating errors. There are several use cases where you may want to force PowerShell to terminate:

  • The error warrants stopping all further action
  • You want to trap and evaluate the error – possibly performing various actions based on the error information
  • You want to trap the error and hide it from the user – providing a more user friendly message

There are several ways for turning a non-terminating PowerShell error into a terminating PowerShell error.

ErrorAction

ErrorAction is a common parameter that tells PowerShell what action to take when it encounters an error. The truth is, you’re always using the ErrorAction parameter. You’re just using the default setting when you don’t specify it, which is continue. Continue will display the error to the console and keep processing.

# Without ErrorAction
Get-Item -Path c:\nope\nope.txt;Write-Host 'Hello, will I run after an error?'
# With ErrorAction
Get-Item -Path c:\nope\nope.txt -ErrorAction Stop;Write-Host 'Hello, will I run after an error?'

Try the two examples above on your machine. The first Get-Item will error because it will be unable to find the file. Write-Host will continue to run. This is standard non-terminating behavior.

The second example leverages ErrorAction set to Stop. This instructs PowerShell to not continue when it encounters an error. Note that Write-Host does not run on the example with ErrorAction. That’s because Get-Item terminated when it was unable to find the file.

ErrorAction supports several settings:

  • Continue – Logs error to $Error, displays error to console, continues processing.
    • default (used even if ErrorAction isn’t specified)
  • Stop – Logs error to $Error, displays error to console, terminates.
  • SilentlyContinue – Logs error to $Error, does not display error, continues processing.
  • Ignore – Does not log error to $Error, does not display error, continues processing.

try/catch

Placing your code into a try catch doesn’t necessarily make it terminating. PowerShell will simply try the code inside the try section, and if an error is encountered, it will move to the catch. What the catch does is up to you. The use of throw in the catch is one way for making an error terminating.

# throw causes PowerShell to terminate
try {
    1/0;Write-Host 'Hello, will I run after an error?'
}
catch {
    throw
}

Try will catch many standard errors in this manner like divide by zero, or incorrect syntax.

When you have cmdlets in a try/catch and you want PowerShell to go to the catch, you still need to specify ErrorAction. Just because a cmdlet is in a try/catch does not mean PowerShell will go to the catch if the cmdlet encounters an error. Remember that PowerShell defaults to Continue. So even if an error is encountered, it will continue to process without going to the catch.

Try the following:

# this example will not go the catch and will run the Write-Host
try {
    Get-Item -Path c:\nope\nope.txt;Write-Host 'Hello, will I run after an error?'
}
catch {
    Write-Host 'You are now in the catch'
}

# this example will error and go directly to the catch
try {
    Get-Item -Path c:\nope\nope.txt -ErrorAction Stop;Write-Host 'Hello, will I run after an error?'
}
catch {
    Write-Host 'You are now in the catch'
}

Now evaluate the code block below. If you run this on your machine it will try to get process information for both processes. What could you change to make this terminate the first time it encounters an error?

$processNames = @(
    'NotAProcess',
    'Notepad'
)
foreach ($item in $processNames) {
    try {
        Get-Process -Nam $item
    }
    catch {
        Write-Host $item
        throw
    }
}

Keep in mind that you don’t have to terminate in the catch with throw. You could perform a variety of actions, such as logging the error somewhere. Using Write-Error you can even still display the error back to the user.

# Write-Error simply prints the error for the user
try {
    1/0;Write-Host 'Hello, will I run after an error?'
}
catch {
    Write-Error $_
}

You can also leverage finally with your try/catch. Regardless if the try is performed successfully, or if the catch is run, the finally step will always be performed.

# Finally will log results regardless of what happens
try {
    Get-Content -Path c:\nope\nope.txt -ErrorAction Stop
}
catch {
    Write-Error $_
}
finally {
    # log results to a logging file
}

PowerShell Errors – Exploring the error object

PowerShell deals in objects, and PowerShell errors are no exception. (A pun!) The error that is written out to the console is just a piece of the error object. When you trap the error in a try/catch you can evaluate the error’s various properties and take action accordingly.

#The website exists, but the page does not
try {
    $webResults = Invoke-WebRequest -Uri 'https://techthoughts.info/nope.htm'
}
catch {
    Write-Error $_
}

Try the example above. Because the nope.htm does not exist, Invoke-WebRequest will throw an exception . We will write the error to console using Write-Error $_. Just like for the success pipeline $_ represents the current object in the error pipeline as well.

Now, visiting a webpage that doesn’t exist technically is an exception, but not an uncommon one. Many users are familiar with miss-typing an address and getting a page not found error. If you ran the code above, it is doubtful that any of that is accurately reflected in your console. The error displayed with Write-Error isn’t fully articulating the issue.

Depending on the nature of your code, this likely is not a good reason to terminate and stop functionality. Instead, you might advise the user to check the web address and try again. To do that, you’ll need to evaluate the error object and take the appropriate action.

#The website exists, but the page does not
try {
    $webResults = Invoke-WebRequest -Uri 'https://techthoughts.info/nope.htm'
}
catch {
    $theError = $_
    if ($theError.Exception -like "*404*") {
        Write-Warning 'Web page not found. Check the address and try again.'
        #Retry code
    }
    else {
        throw
    }
}

The error object has many different sub-properties. Here, we evaluate the Exception sub-property to determine if the exception message contains a 404. If it does, we can advise the user and retry. Otherwise, we’ve encountered a different type of error and it may be appropriate to terminate.

#The website does not exist
try {
    $webResults = Invoke-WebRequest -Uri 'https://techthoughtssssssss.info/'
}
catch {
    $theError = $_
    $theError.Exception
}

In this example, the website doesn’t exist at all. Thus, the sub-property Exception will be different. Run this on your computer to see how it differs from the previous example.

To see all the sub-properties that are contained in the error object you will need to pipe it to Format-List with the force parameter.

#The website exists, but the page does not
try {
    $webResults = Invoke-WebRequest -Uri 'https://techthoughts.info/nope.htm'
}
catch {
    $theError = $_
    # see all the sub-properties
    $theError | Format-List * -Force
}

$Error Variable

$Error is a reserved variable that contains a collection of all errors in the current session. If you have been trying the examples in this lesson your current PowerShell window should contain quite a few. Simply type $Error and click enter.

You may be wondering what the point of this variable is. Remember that errors in PowerShell are rich data objects. As you’re developing you will likely be generating some errors as you try different solutions. If you forget to capture one, you can access the error in the $Error history. It’s a simple array so you can access the error and all it’s sub-properties.

$Error[5] | Format-List * -Force

#a few other $Error commands to try
$Error
1/0;$Error
Get-Process -Name 'NotAProcess'
$Error
$Error.Clear()

Closing Example

#this example will help display some helpful message to the user
#this example will only work in PowerShell 6.1+
$uri = Read-Host 'Enter the URL'
try {
    $webResults = Invoke-WebRequest -Uri $uri -ErrorAction Stop
}
catch {
    $statusCodeValue = $_.Exception.Response.StatusCode.value__
    switch ($statusCodeValue) {
        400 {
            Write-Warning -Message "HTTP Status Code 400 Bad Request. Check the URL and try again."
        }
        401 {
            Write-Warning -Message "HTTP Status Code 401 Unauthorized."
        }
        403 {
            Write-Warning -Message "HTTP Status Code 403 Forbidden. Server may be having issues. Check the URL and try again."
        }
        404 {
            Write-Warning -Message "HTTP Status Code 404 Not found. Check the URL and try again."
        }
        500 {
            Write-Warning -Message "HTTP Status Code 500 Internal Server Error."
        }
        Default {
            throw
        }
    }
}
Series Navigation<< PowerShell Input & Output

Leave a Reply

Your email address will not be published. Required fields are marked *