4

UPDATE May 2021 - When I originally asked this question, the core thing that made this question relevant (for me) was that when rethrowing an exception from a catch via a simple throw (by itself), the original exception stack was lost. So that made using a catch to detect if an exception was thrown off-limits.

This incorrect loss-of-stack behavior was fixed sometime between when the question was asked (2017) and now. So a simple catch and rethrow (call throw with no other arguments) is now the most straightforward way to detect an exception was thrown from the finally block. Thanks to @JohnLBevan for his answer letting me know that rethrowing from the catch was no longer problematic.

ORIGINAL QUESTION:

I've got some code structured like this

try{
...
}
finally{
...
<code that may throw>
}

Of course one should generally avoid code that throws in a finally. But it can happen. And when it does, one unfortunate side effect is that the original exception is lost. So the first thing I'd like to do in the finally is log information about the exception thrown in the try, if one was thrown.

But how can I determine if an exception did occur in the try block, once I'm in the finally? Is there a slick way? I don't want to catch the exception in a catch. I can set a boolean at the end of the try which would indicate an exception was not thrown, but I'm not a big fan of having to do that every time. That would look like this:

$exceptionThrown = $true
try{
...
$exceptionThrown = $false
}
finally{
<if $exceptionThrown log info about it>
...
<code that may throw>
}

Can I do better?

aggieNick02
  • 2,557
  • 2
  • 23
  • 36
  • I guess this is the powershell version of this question: https://stackoverflow.com/questions/3301507/determine-if-executing-in-finally-block-due-to-exception-being-thrown . Unless powershell exposes something C# doesn't, I imagine I'm out of luck. – aggieNick02 Sep 11 '17 at 18:51
  • Just realized that writing code in the finally to do something with the exception is harder than I hoped. I had been using $Error[0] to get the last exception, which works fine unless the code in the try block exits early without throwing an exception - e.g., return or exit. In that case, $Error[0] either doesn't exist, or is a stale error value for your current environment... sigh... – aggieNick02 Oct 13 '17 at 14:56

2 Answers2

1

This page about Powershell 5.1 doesn't explicitly cover the case of throwing an exception inside a "finally" block, but says that Powershell behavior is similar to C# behavior. And the accepted answer to this SO question about C# behavior indicates that:

  1. the code in the "finally" block beyond the point where the exception was thrown is not executed, and
  2. if the "finally" block was executed during the handling of an earlier exception, then that first exception is lost

So I think what you really want is something like this:

  try { ... set flag ... }
  catch { ... adjust flag ... }
  finally { ... check flag ... }

The catch block only executes if there was an exception thrown in the "try" block, but the "finally" block happens in every case (and can tell whether or not an exception was thrown in the original "try" block by checking the value of the flag).

Of course if an exception can be thrown in the "finally" block and you want to handle that, then you're going to need to wrap that whole business in another "try".

Peter
  • 2,526
  • 1
  • 23
  • 32
  • 1
    It is important that I not catch the exception, as I want it to propagate up the stack. It looks like rethrowing in powershell, regardless of how you do it, loses the original stack trace, so that's no good either. For now I'm resigned to setting a boolean as the last statement of my try block... – aggieNick02 Sep 12 '17 at 20:41
  • C# will let you re-throw exceptions and keep the original exception context (including stack trace, source location, etc.), but the accepted answer to the following SO question agrees with your assessment that this, unfortunately, does not work in Powershell: https://stackoverflow.com/questions/13820140/how-can-i-rethrow-an-exception-from-catch-block-in-powershell (although it might be possible to stuff some of the most useful attributes of the original exception into the error message of the "rethrown" exception) – Peter Sep 13 '17 at 02:09
1

If the only reason you're avoiding the catch block is because you don't want to affect the stack trace, you can use it then rethrow the error with the original line number by using throw with no arguments; thus rethrowing the original exactly as if you'd not used the catch block. For example:

$exceptionInfo = $null
try {
    1/0 # cause some error
} catch {
    $exceptionInfo = $_.Exception # you could set a flag / whatever here; without knowing your requirement I can't advise further
    throw # you said you didn't want to catch it... but if you just don't want to impact the stack trace this is fine as we're rethrowing the original exception; not throwing a new one
} finally {
    if ($null -eq $exceptionInfo) {
        Write-Information 'Completed Successfully' -InformationAction Continue
    } else {
        Write-Warning "An error occurred $exceptionInfo"
    }
}

If you don't want to use a catch block and don't want to use some variable you've defined to flag whether an exception's occurred, you could use $Error; though you may need to clear it first as it will contain all errors which have been raised in the current session...

$Error.Clear()
try {
    1/0
} finally {
    if ($Error.Count) {
        Write-Warning "An error occurred $($Error[0])"
    } else {
        Write-Information 'Completed Successfully' -InformationAction Continue
    }
}

Generally you shouldn't need to determine whether something was successful in a finally block though; rather:

  • If you have logic that you only want to occur if the command is successful, place it in the TRY block after the line that may cause an exception.
  • If you have logic that you only want to occur if the command hits an error, place it in the catch block (and use throw if you want to rethrow the original exception so that it still bubbles up afterwards).
  • If you have logic that should run in either case, that's where the finally block comes in. Sometimes you may need to know the state of some component here (e.g. is a connection to your database still open) and that state may have changed due to the exception... If that's the case, normally such components should provide their own flags.

Below's a rough illustration; the actual example's a bit poor as I couldn't think of a good & succinct real world example scenario; but hopefully you get the idea.

try {
    $con = Get-MyDbConnection
    New-DbRecord -Connection $con -Data $data 
} catch {
    Write-Log $_.Exception
} finally {
    if (($null -ne $con) -and ($con.IsConnected)) {
        $con.Disconnect()
    }
}
JohnLBevan
  • 22,735
  • 13
  • 96
  • 178
  • 1
    So back in 2017 when the question was asked, rethrowing via `throw` in the `catch` would lose the original stack trace. But a comment on the answer to the question at https://stackoverflow.com/questions/13820140 indicates that was fixed at some point. I just tried in windows 10 1909, and indeed it seems to work just fine now. Which is great news. – aggieNick02 May 12 '21 at 15:19
  • Ah nice - I'd not realised that it hadn't worked historically; that explains a lot. Thanks / hope the info's still useful, if 4 years late ;) – JohnLBevan May 12 '21 at 15:58
  • 1
    Absolutely useful. Your confidence on the answer and high rep were enough to make me doubt my 4 year old self, and quickly look at related questions and see the note that the behavior had changed and been fixed. Found some other wacky powershell exception issues too, :-) But a quick experiment showed the stack is indeed now preserved. Maybe someday I'll spin up some VMs and try to see when it was fixed. Thanks! – aggieNick02 May 12 '21 at 18:21