Created
March 31, 2022 12:40
-
-
Save JohnLBevan/9820de7dcab3b9bac25fffd8bca75cb4 to your computer and use it in GitHub Desktop.
A colleague recently asked about where to handle exceptions in PowerShell. My response was; it depends / handle them wherever you can do something about them / consider what else should happen even if one part fails, vs what shouldn't happen. Always ensure that issues get handled or bubbled up to be logged/output somewhere, rather than catching …
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| This example shows how you can collect information on exceptions which occur in a loop, | |
| whilst still processing other items in that loop, and bubbling up the exceptions which | |
| were thrown during the loop | |
| #> | |
| # Dummy methods for illustration | |
| function Do-Something{'Did Something'} | |
| function Do-SomethingToTheThing([int]$thing) { | |
| if ($thing % 7 -eq 0) { | |
| throw [System.InvalidOperationExceltion]::new("Can't handle number [$thing]") | |
| } else { | |
| "[$thing] did not error" | |
| } | |
| } | |
| # The actual logic we're interested in | |
| try { | |
| Do-Something | |
| $listOfThings = 1..100 | |
| $exceptions = [System.Collections.Generic.Queue[Exception]]::new() # have a place to store exceptions until we're ready for them | |
| foreach ($thing in $listOfThings) | |
| { | |
| try { | |
| Do-SomethingToTheThing $thing | |
| } catch { # NB: you may want to only catch certain types of exceptions / exactly what will depend on your logic... This is a basic demo, so catches everything | |
| $exceptions.Enqueue($_.Exception) # any exceptions which occur get queued for once we've completed the loop | |
| } | |
| } | |
| if ($exceptions.Count) { # once you're done processing all items, handle the exceptions | |
| throw [System.AggregateException]::new($exceptions) # the aggregate exception is designed for this use case | |
| } | |
| 'No exceptions occurred' | |
| } catch { | |
| 'There were issues' | |
| $_.Exception.ToString() | |
| } finally { | |
| 'Script completed' | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| This example shows how we can retry on failure; e.g. if we were trying to do something that required a connection, | |
| but it's possible that the connection may have been closed/lost since we begun we can add logic to wait &/or try | |
| opening it again. | |
| We can set a max number of retries to avoid infinite loops if the recovery logic is not sufficient. | |
| #> | |
| function Retry-Command { | |
| [CmdletBinding()] | |
| Param( | |
| [Parameter(Mandatory)] | |
| [scriptblock]$TryThis | |
| , | |
| [Parameter()] | |
| [scriptblock]$FixLogic = {Param($Exception)Write-Verbose "An issue occurred; waiting a bit before retrying. $($Exception.Message)";Start-Sleep -Seconds 5} | |
| , | |
| [Parameter()] | |
| [int]$MaxAttempts = 3 | |
| ) | |
| for ($attempt=1; $attempt -le $MaxAttempts; $attempt++) { | |
| Write-Verbose "Attempt #$($attempt)" | |
| try { | |
| & $TryThis | |
| break # i.e. break out of the loop. | |
| } catch { | |
| if ($attempt -lt $maxAttempts) { | |
| & $FixLogic $_.Exception | |
| } else { # if we want to bubble up the exception if we hit max attempts, do so here / if not, we need to have some sort of flag to say that we've exitted the loop because things we exceeded max retries vs succeeded | |
| throw [Exception]::new('Max Attempts Exceeded', $_.Exception) # best to use an appropriate exception type; not just Exception | |
| } | |
| } | |
| } | |
| } | |
| Retry-Command -TryThis {'Works first time'} | |
| Retry-Command -TryThis {if ($null -eq $connection) { throw [Exception]::new('Could not connect')} else {'Connected Successfully :)'}} -FixLogic {Param($Exception)Write-Verbose "An issue occurred: [$($Exception.Message)]; fixing...";Set-Variable -Name 'connection' -Scope 1 -Value 1} -Verbose | |
| Retry-Command -TryThis {throw [Exception]::new('this will always fail')} -Verbose | |
| # we'd ideally have the Retry-Commands in their own try/catch, so we can show when issues are bubbled up... | |
| # I've skipped that here to keep the demo basic |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment