Powershell: If You Want to Catch, Don't Forget to Throw

I made a Powershell scripting error the other day, and wanted to share my lesson learned.  In hindsight it seems obvious, but I made the mistake nevertheless, then cursed when my script presented with an apparent bug.

I had a script that iterated through a collection of items using a ForEach loop - an extremely common task in Powershell.  For each iteration of that ForEach loop, I performed a function that had a chance of failing, so I wrapped it in a Try/Catch block.  The rest of the ForEach loop below that performed further processing that depended on the success of the preceding function, so if the previous function failed, I wanted to skip the rest of that iteration and use the Continue statement to skip directly to the next ForEach iteration.

Let me illustrate:

:NextUser ForEach ($User In $Users)
{
    Try
    {
        Write-Debug "I'm about to try something risky..."
        Pre-Process $User
    }
    Catch
    {
        Write-Error $_.Exception        
        Continue NextUser
    }
    Write-Debug "Since pre-processing for $User was successful, I'm going to do more stuff here..."
    Post-Processing $User
}

And the Pre-Process function looked like this:

Function Pre-Process ($User)
{
    Try
    {
        [System.Some.Static]::Method($User)
    }
    Catch
    {
        Write-Error $_.Exception
    }
}

So what actually ended up happening was when the "Pre-Processing" function failed, it flashed an error message on the screen... but then the rest of the ForEach iteration continued to execute in an unpredictable and undesired way.  Why did my Continue statement in the Catch block not take me back to the next ForEach iteration?

Well as it turns out, the Catch block never ran.  Just because you catch an exception and use the Write-Error cmdlet doesn't mean you've thrown a terminating error to the caller.  And you need a terminating error in order to exit a Try block and go into a Catch block.  Since I was using a custom function, ErrorAction -Stop and $ErrorActionPreference didn't do anything for me. What I needed to do was use Throw to generate the needed terminating error.  What was throwing me off (no pun intended) was that I was confused as to which one of the Write-Error statements was actually taking place.  The one in the custom function, or the one in the ForEach loop that contained a call to the custom function?

In retrospect, I should probably stop using Try/Catch blocks altogether in custom Powershell functions.  You can easily suppress a needed exception with a Try/Catch and cause unintended bugs.

But for now, simply adding a Throw statement to my custom function was all I needed to activate the Catch block in the calling script:

Function Pre-Process ($User)
{
    Try
    {
        [System.Some.Static]::Method($User)
    }
    Catch
    {
        Write-Error $_.Exception
        Throw $_.Exception
    }
}
Comments are closed