Catching Exceptions in PowerShell


Things can go wrong even on the simplest tasks. A network failure, a fail over, inadequate permissions and many other factors can make your scripts and functions fail. Let's see how we can control failure!

There are two statements in PowerShell that help to control failed commands. These are "try" and "catch". Their usage is pretty straightforward, you wrap the commands that might fail in a try block and specify the actions to be executed upon the failure in the catch block.

Below is a simple try/catch block in a function:

try
{
    Remove-Item -Path myfile.txt -ErrorAction Stop
}
catch
{
    Write-Host "Cound not remove item"
}

Here, if the Remove-Item command fails, the string "Could not remove item" will be written on the console.

A couple of things to notice here. First, the -ErrorAction preference is set to Stop for the command since this way the command will generate and exception instead of just writing to the error stream. If an exception is not thrown, it cannot be caught and handled. Second, when a command in a try block fails, the execution is immediately moved to the catch block. Any commands that may follow the one throwing the exception are not executed.

Let's see a more complex example:

Function Test
{
    try
    {
        Get-ADUser cpolydorou-test -ErrorAction Stop
    }
    catch
    {
        $_
    }
}

When executed, the above function will have output like the following:

PS C:\> Test
Get-ADUser : Cannot find an object with identity: 'cpolydorou-test' under: 'DC=LAB,DC=local'.
At line:5 char:9
+ Get-ADUser cpolydorou-test -ErrorAction Stop
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (cpolydorou-test:ADUser) [Get-ADUser], ADIdentityNotFoundException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Management.Commands.GetADUser

PS C:\>

The command failed since the user "cpolydorou-test" does not exist. What you see on the above output is the exception object which you can reference using the $_ variable from within the catch block.

But what if the above command had failed due to the server being unavailable? Fortunately, we can differentiate between the various types of exceptions:

Function Test
{
    try
    {
        Get-ADUser cpolydorou-test -Server DC3 -ErrorAction Stop
    }
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
    {
        Write-Host "Not Found - $($_.Exception.GetType().FullName)"
    }
    catch 
    {
        Write-Host "General - $($_.Exception.GetType().FullName)"
    }
}

The first catch will only be executed if the Get-ADUser command throws an exception of type Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException. The last one, if none of the catch blocks above it did not catch the exception. It is a good practice to have a generic catch statement at the end in order to handle any unpredictable exceptions.

PS C:\> test -server dc3
Not Found - Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException

PS C:\> test -server dc4
General - Microsoft.ActiveDirectory.Management.ADServerDownException

PS C:\>

That's nice, but how can we get the type of the exception? That's easy, every exception object has a method called GetType() that returns an object with a property named Fullname. This is the value to use next to the catch keyword.

There are some cases where we would like to execute a block of code in any case after the try or catch. You can define such a block using the finally keyword. The following code connects to a SQL server, executes a query and then closes the connection:

$connectionString = Server=LabSQLListener;Database=LabDatabase;Integrated Security=True;”
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString

try
{
    $connection.Open()
}
catch
{
    Write-Host "Failed to connect to server."
    break
}
try { $query = SELECT * FROM Table_1 $command = $connection.CreateCommand() $command.CommandText = $query $result = $command.ExecuteReader() $table = new-object System.Data.DataTable $table.Load($result) $table } catch { Write-Host "Failed to execute the query." } finally { $connection.Close() }

The key here is that the connection is closed even if an exception is throw from the query.

I suggest you start getting familiar with the above techniques since it will greatly enhance your code and the experience of those using it.

Have fun!

Popular posts from this blog

Domain Controller Machine Password Reset

Configuring a Certificate on Exchange Receive Connector

Running Multiple NGINX Ingress Controllers in AKS