Live Cyber Attack Lab 🎯 Watch our IR team detect & respond to a rogue insider trying to steal data! Choose a Session

X

Using PowerShell Try, Catch and Finally for Error Handling [Tutorial]

PowerShell

In This Article

illustration of error handling with powershell

Errors are a natural part of writing code, unfortunately. How you handle them can make the difference between a script that stops elegantly and one that deletes large parts of your system. In PowerShell, one of the most effective and popular ways to handle errors is using PowerShell Try / Catch blocks.

Try / Catch blocks simplify the process of writing code by allowing you to catch errors that would normally be ignored by PowerShell, but that could potentially have disastrous consequences. Try / Catch is also an important part of your broader error handling process, which should be designed so that you can easily see all the errors your scripts are generating.

Get the Free PowerShell and Active Directory Essentials Video Course

I'd recommend this for both new and advanced PowerShell users. Building an AD tool is a great learning experience.

In this article, we’ll take you through the nuances of how errors work in PowerShell, and how you can use Try / Catch to limit the consequences of them. We’ll take you through the following topics:

How Errors Work in PowerShell

illustration of errors in PowerShell

Before looking at error handling in PowerShell, it pays to understand a little about the way that errors work within PowerShell itself. We’ve covered a little about the way that errors work in PowerShell in our tutorials on PowerShell, and in our guide to getting started with AD scripting, but in this article, we’ll look at errors specifically.

At the most fundamental level, there are two types of error in PowerShell: terminating and non-terminating. PowerShell, by default, will only catch certain types of errors, and if it does it will exit the script it is running. These are known as terminating errors. If you make a mistake in your syntax, or you run out of memory, that’s a terminating error.

Non-terminating errors are more dangerous. If one of these occurs, by default PowerShell will keep running. This type of error can have significant consequences for your system because you won’t normally know that an error has been produced.

Here’s a good example of the danger of non-terminating errors. Let’s say that a system admin at Varonis is expecting our HR department to regularly upload a list of staff members who should have access to our expenses database. If a name is not on this list from HR, we’ll write a script to automatically remove that person from the group so they can’t make expenses claims. Here’s a basic script to achieve that:

$AuthorizedUsers= Get-Content \\ FileServer\HRShare\UserList.txt
$CurrentUsers=Get-ADGroupMember "Expenses Claimants"
 
Foreach($User in $CurrentUsers)
{
    If($AuthorizedUsers -notcontains $User)
    {
        Remove-ADGroupMember -Identity "Expenses Claimants" -User $User
    }
}

Read through the script, and you’ll be able to see what might happen. If HR forgets to upload the list, PowerShell will throw an error on the Get-Content cmdlet. Unfortunately, this is a non-terminating error, so the script will continue to run. And because $AuthorizedUsers variable is empty, it will remove everyone from the expenses group. Then our system admin will start to get floods of angry phone calls from staff across the company.

This type of problem is exactly what error handling processes aim to prevent, and the Try / Catch command is an important tool in implementing these processes. First, though, let’s look a little deeper into how PowerShell handles errors.

The $Error Automatic Variable

$Error Automatic Variable illustration

When looking to build an error handling strategy, it’s useful to know a little about how PowerShell handles error by default. As we’ve previously explained in our guides to PowerShell [link to hub], PowerShell has a lot of automatic variables that it uses to store information about your system. One of these, and a very important one, is the $Error automatic variable.

This array variable stores all errors encountered in a session, sorted by the most recent error. When you first start a PowerShell session, the $Error variable will be empty, but once you generate an error it will be added and stored in this variable.

You can work with the $Error variable just as you would with any normal array. You can count how many errors are stored in the variable by using $Error.count, for instance, or access any individual error by calling its position in the array. Remember that array indexing starts at zero, though, so to call the most recent error you can run $Error[0].

The $Error Object Properties

As you will know if you’ve been working with PowerShell for a while, everything within PowerShell is an object, and as such everything is assigned a number of properties that give you greater control and further information on it. The $Error variable is no exception to this rule. It’s, therefore, possible to talk about the $Error object and to describe its properties.

You can retrieve the properties of the $Error variable in the same way you would with any other object:

$Error | Get-Member

This will return an extensive list of all the properties available for the variable:

Equals Method

bool Equals(System.Object obj)

GetHashCode Method

int GetHashCode()

GetObjectData Method

System.Void GetObjectData(System.Runtime.Serialization.Serialization

GetType Method

type GetType()

ToString Method

string ToString()

CategoryInfo Property

System.Management.Automation.ErrorCategoryInfo CategoryInfo {get;}

ErrorDetails Property

System.Management.Automation.ErrorDetails ErrorDetails {get;set;}

Exception Property

System.Exception Exception {get;}

FullyQualifiedErrorId Property

System.String FullyQualifiedErrorId {get;}

InvocationInfo Property

System.Management.Automation.InvocationInfo InvocationInfo {get;}

PipelineIterationInfo Property

System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Int32,

TargetObject Property

System.Object TargetObject {get;}

PSMessageDetails ScriptProperty

System.Object PSMessageDetails {get=& { Set-StrictMode -Version 1;

Of these, one of the most important is the InvocationInfo property, which holds information on the reason for a particular error. To access this property, run:

$Error[0]. InvocationInfo

As you can see, this will return more detailed information on this property. You can use the same syntax, with different array indexing numbers, to access information on the other errors stored in the $Error variable.

What are Terminating Errors?

terminating errors illustration

As we explained above, there are two types of errors in PowerShell: terminating and non-terminating. In order to develop a rigorous error handling strategy, you will need to know about both. So let’s take a deeper look at both types.

Terminating errors will stop the execution flow of PowerShell, whether they occur in a command or in a script. There are many different terminating errors, and they can be generated by many cmdlets. A common way in which a terminating error occurs is if you call a cmdlet with a parameter that does not exist.

For instance, if you call the Get-Process command with a legitimate process name – let’s say “Notepad” — the command is valid, and will show the normal output. However, if you try to use a parameter that does not relate to the Get-Process command, say “-handle 251”, then the cmdlet will cease execution without showing any output, and return information on the error.

PowerShell handles terminating errors in the following way. When this type of error occurs, the cmdlet will report the error by calling the System.Management.Automation.Cmdlet.Throwterminatingerror* method. This method allows the cmdlet to send an error record that describes the condition that caused the terminating error.

When the System.Management.Automation.Cmdlet.Throwterminatingerror* method is called, the Windows PowerShell runtime permanently stops the execution of the pipeline and throws a System.Management.Automation.Pipelinestoppedexception exception. Any subsequent attempts to call System.Management.Automation.Cmdlet.WriteObject, System.Management.Automation.Cmdlet.WriteError or several other APIs cause those calls to throw a System.Management.Automation.Pipelinestoppedexception exception.

The System.Management.Automation.Pipelinestoppedexception exception can also occur if another cmdlet in the pipeline reports a terminating error, if the user has asked to stop the pipeline, or if the pipeline has been halted before completion for any reason. The cmdlet does not need to catch the System.Management.Automation.Pipelinestoppedexception exception unless it must clean up open resources or its internal state.

Cmdlets can write any number of output objects or non-terminating errors before reporting a terminating error. However, the terminating error permanently stops the pipeline, and no further output, terminating errors, or non-terminating errors can be reported.

What are Non-Terminating Errors?

illustration of non-terminating errors

The second type of error in PowerShell is a non-terminating error. These errors do not stop the execution of a script or a command, either of which will keep running despite encountering an error.

The simplest example of a non-terminating error is when you try to access a file that does not exist. In this case, PowerShell will report that it cannot find the file, but it will not stop the execution of your command or your script. For example, here is a script to get a list of file names from the fileslist.txt file, then to go through each file name, read the contents of each file, and outputs it on the screen:

$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
    Get-Content $file
}

Let’s say that the content of filelist.txt is as follows:

File_1.log
File_2.log
File_3.log
File_4.log
File_5.log
File_6.log
File_7.log
File_8.log
File_9.log
File_10.log

But what if one of these files doesn’t actually exist? In that case, the script will run, then report an error in red text, and then continue to run through its list of files. In other words, it didn’t terminate.

The way PowerShell reports on non-terminating errors is also different from the way in which it reports on terminating errors. When a non-terminating error occurs, the cmdlet should report this error by calling the System.Management.Automation.Cmdlet.WriteError method. When the cmdlet reports a non-terminating error, the cmdlet can continue to operate on this input object and on further incoming pipeline objects. If the cmdlet calls the System.Management.Automation.Cmdlet.WriteError method, the cmdlet can write an error record that describes the condition that caused the non-terminating error.

Cmdlets can call System.Management.Automation.Cmdlet.WriteError as necessary from within their input processing methods. However, cmdlets can call System.Management.Automation.Cmdlet.WriteError only from the thread that called the System.Management.Automation.Cmdlet.BeginProcessing, System.Management.Automation.Cmdlet.ProcessRecord, or System.Management.Automation.Cmdlet.EndProcessing input processing method.

Do not call System.Management.Automation.Cmdlet.WriteError from another thread. Instead, communicate errors back to the main thread.

The $ErrorActionPreferenceVariable

illustration of erroractionpreferencevariable

Now you understand the difference between terminating and non-terminating errors, you should begin to see why non-terminating errors are so dangerous. Because a script or command will continue to run even if a non-terminating error is encountered, it can easily cause havoc in your systems.

For that reason, PowerShell provides a way for you to tell it to treat ALL errors as terminating. This option is controlled by the $ErrorActionPreference variable. This is one of the preference variables in PowerShell that allow you to control the way that your PowerShell environment behaves.

By default, the $ErrorActionPreference variable is set to “Continue”. This means that script will continue to execute after it encounters the error. If you change the value of this variable to “STOP”, PowerShell will treat ALL errors as terminating errors.

We can now change the code we used above – to read a list of files and output their contents – in order to treat the missing file as a terminating error. For example:

# Set the $ErrorActionPreference value to STOP
$ErrorActionPreference = "STOP"
$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
    Get-Content $file
}

Now, if we run the script on the same filelist.txt file we used before, it will halt at file_6.log, because it does not exist.

Changing the default value of the $ErrorActionPreference variable in this way can be extremely useful when you are writing new code, and want to make sure that you are not causing accidental damage. Note, as well, that the $ErrorActionPreference value is only valid in the current PowerShell session. It resets to the default value once a new PowerShell session is started.

However, you should also recognize that in many cases non-terminating errors in PowerShell do not cause commands or scripts to halt for a good reason: often, changing every error to a terminating error will lock up many of your scripts that would otherwise run just fine.

The ErrorAction Common Parameter

There is another way to affect which errors PowerShell counts as terminating: the ErrorAction parameter.

If the $ErrorActionPreference value is applied to the PowerShell session, the ErrorAction parameter applies to any cmdlet that supports common parameters. The ErrorAction parameter accepts the same values that the $ErrorActionPreference variable does.

Note that the ErrorAction parameter value takes precedence over the $ErrorActionPreference value, and that both can be used in the same session. For an example, let’s use the same code as above, but this time add the ErrorAction parameter to the Get-Content line:

# Set the $ErrorActionPreference value to default (CONTINUE)
$ErrorActionPreference = "CONTINUE"
$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
                # Use the -ErrorAction common parameter
                Get-Content $file -ErrorAction STOP
}

If we run this code in the same way as before, it will still terminate when it reaches file_6.log. This is because, even though the $ErrorActionPreference is set to “continue”, the -ErrorAction parameter takes precedence.

Error Handling: What are PowerShell Try-Catch Blocks?

illustration of powershell try catch blocks

Now you know how PowerShell deals with errors, and some basic techniques to affect the way that it does this, you are probably wondering what to do with these errors. This is where Try / Catch blocks come in.

Try / Catch blocks are essentially a way of throwing a fence around a particular piece of code and catching any errors that it generates. The basic syntax of a Try / Catch block is as follows:

try {

<something stupid>

}

catch [[<error type>][',' <error type>]*]{

<user tried to do something stupid>

}

Here, you are telling PowerShell to “try” the code contained in the first block, and monitor it for errors. If any of the code throws an error, this will be added to the $Error variable, and then passed to the Catch block.

The Catch block contains instructions that will be executed if errors are received from the Try block. It can contain complex responses to each type of error that you might encounter, and multiple Catch blocks can be used for the same Try statement.

Finally, there is another optional part of Try / Catch blocks: the Finally block. This contains code that will be executed after the Try block, and this code will be executed whatever errors were thrown by the Try block.

A Simple Example of Try / Catch / Finally

Before we get into the real-world applications of the Try / Catch block, it’s worth practicing with a basic example of how to use the syntax. For instance, to conduct a simple check of your AD User directory, you can use:

$Users = Get-Content C:\temp\usersimport.txt
foreach ($User in $Users) { 
try { 
Get-ADUser -Identity $User | Out-Null 
Write-Output "$user exists" 
} 
catch { 
write-output "$User doesn't exist" 
} 
}

As you can see, this code will return a descriptive, useful error message if the user is not found.

How to Use PowerShell Try-Catch Blocks

illustration of using powershell try catch blocks

Let’s go a little beyond this generic example, though, and use a Try / Catch block on the example we’ve been working with up until now.

One of the most basic implementations of Try / Catch blocks is simply to let the Catch part of the block catch all errors, and return a generic error message. In the following code, as you can see, the foreach statement is enclosed inside the Try block. Then, a Catch block contains the code to display the string An Error Occurred if an error happened. The code in the Finally block just clears the $Error variable:

$file_list = Get-Content .\\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch {
    Write-Host "An Error Occurred" -ForegroundColor RED
}
finally {
                $Error.Clear()
}

If you run this code on the same filelist.txt and files that we tried before, you will see that it gets to File_6.log, and then exits. It will also return “An Error Occurred” in red. This is a great start to error handling because you’ve handled the error. However, the error message here is not very helpful.

This is where the properties of the $Error variable that we mentioned above are useful. You can tell the catch block to display the Exception property of the errors generated, rather than a generic message. For example:

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch {
    Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
                $Error.Clear()
}

Now, as you can see if you run this code again, the error message is a lot more descriptive.

Catching Specific Errors

While catching non-specific errors is a good approach as you are developing new code, as your project matures you will likely want to catch specific errors and develop instructions for how to respond to each.

The type of error for each error held in the $Error variable is contained within the TypeName value of the Exception property. For example, to find the error type of the last error that was added to the $Error array, run:

$Error[0].Exception | Get-Member

Using this call, you can intercept the error type from every error that is generated, and deal with it specifically. Here is an example, modified from the script above, in which there are now two Catch blocks. The first Catch block intercepts a specific type of error (System.Management.Automation.ItemNotFoundException). In contrast, the second Catch block contains the generic, catch-all error message:

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch [System.Management.Automation.ItemNotFoundException]{
    Write-Host "The file $file is not found." -ForegroundColor RED
}
catch {
    Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
                $Error.Clear()
}

If you run this code in the same way as before, you will see that the error is now reported in a much more effective way. If any other type of error is encountered, then the generic error message will be displayed.

Using Try / Catch Blocks for Error Logging

The steps above will allow you to build an effective and efficient error handling system, but you should also ensure that you are logging all the errors that your code generates, at least once it has gone live. You can do this using the same Try / Catch syntax that we’ve used above. Here are the steps to putting this in place:

  1. Set the ErrorAction parameter to Stop for any command that needs Error Handling. This will stop executing the command when an error occurs and it will show the error message. As above, here we are making non-terminating errors into terminating in order to be able to handle the error in the catch block.
  2. The command that has an ErrorAction parameter set to value Stop is then wrapped in a Try { } block. When an Error occurs, handling of the code will jump from the Try block to Catch block.
  3. In the Catch { } block that immediately follows after Try {} block, the error is handled by writing to the error log file on the disk.
  4. You can then use the Write-ErrorLog CmdLet inside the Catch{ } block to write the error in a text file on the disk.
  5. This cmdlet has an additional switch error parameter to decide whether you want to write an error in the log file or not. This is totally optional.
  6. Use the Finally { } block if needed.
  7. Test the whole setup by intentionally breaking the code while in the development phase.
  8. If some of your cmdlets calls are scheduled, you should also put in place a routine to check the external Error Log text file at least once a week and investigate errors that are caught.

A Final Word

Try / Catch blocks in PowerShell are an important part of building an effective error handling system. They can be used to safely monitor blocks of code for errors, and to build specific responses to each type of error. This functionality is invaluable, both as you are building new projects, and as you are seeking to make mature projects more usable.

In this guide, we’ve shown you the basics of using Try / Catch Blocks in PowerShell. However, if you want to go further, you can check out our guide to more advanced ways of using PowerShell Objects and Data Piping, and how to improve your PowerShell efficiency using the tools that are available.

Jeff Petters

Jeff Petters

Jeff has been working on computers since his Dad brought home an IBM PC 8086 with dual disk drives. Researching and writing about data security is his dream job.

 

Does your cybersecurity start at the heart?

Get a highly customized data risk assessment run by engineers who are obsessed with data security.