Skip to content

Instantly share code, notes, and snippets.

@JohnLBevan
Created March 31, 2022 12:40
Show Gist options
  • Select an option

  • Save JohnLBevan/9820de7dcab3b9bac25fffd8bca75cb4 to your computer and use it in GitHub Desktop.

Select an option

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 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 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