Public/Invoke-WithRetry.ps1
|
<# .SYNOPSIS Executes a script block with automatic retry logic on failure. .DESCRIPTION The Invoke-WithRetry function wraps a script block in retry logic, automatically re-executing it when exceptions occur. It supports configurable retry counts, delays with optional exponential backoff, filtering by exception type, and an optional callback on each retry. If all retries are exhausted, the last exception is rethrown. .PARAMETER ScriptBlock The script block to execute. If execution throws an exception, the function will retry according to the configured retry parameters. .PARAMETER MaxRetries The maximum number of retry attempts after the initial failure. Defaults to 3. .PARAMETER DelaySeconds The delay in seconds before the first retry. Fractions are allowed (e.g. 0.5). Defaults to 2. .PARAMETER BackoffMultiplier A multiplier applied to the delay on each successive retry. Set to a value greater than 1 for exponential backoff (e.g., 2 doubles the delay each retry). Defaults to 1 (constant delay). .PARAMETER MaxDelaySeconds The longest delay allowed between retries when backoff is used. Defaults to 300. .PARAMETER RetryableExceptions An optional array of .NET exception types to retry on. When specified, only exceptions matching one of these types will trigger a retry. If omitted, all exceptions trigger a retry. .PARAMETER OnRetry An optional script block invoked on each retry attempt. It receives the current exception as the first argument and the attempt number as the second argument. .INPUTS None This function does not accept pipeline input. .OUTPUTS System.Object Returns the output of the successfully executed ScriptBlock. .EXAMPLE Invoke-WithRetry -ScriptBlock { Get-Content "\\server\share\file.txt" } -MaxRetries 5 -DelaySeconds 3 Attempts to read a file up to 5 times with a 3-second delay between retries. .EXAMPLE Invoke-WithRetry -ScriptBlock { Invoke-RestMethod -Uri $uri } -MaxRetries 4 -DelaySeconds 1 -BackoffMultiplier 2 Calls a REST endpoint with exponential backoff: 1s, 2s, 4s, 8s delays between retries. .EXAMPLE $onRetry = { param($ex, $attempt) Write-Warning "Retry $attempt : $($ex.Message)" } Invoke-WithRetry -ScriptBlock { Connect-Database } -MaxRetries 3 -RetryableExceptions @([System.Net.Sockets.SocketException]) -OnRetry $onRetry Retries only on SocketException, invoking a warning callback on each retry. Exception types match subclasses too, so [System.Net.WebException] also matches its derived types. .NOTES Author: Nigel Tatschner Company: TheCodeSaiyan This function is part of the tcs.core module and provides robust retry logic suitable for network operations, transient fault handling, and resilient scripting. .LINK https://ntatschner.github.io/TheCodeSaiyan-PowerShell-tcs.core/ #> function Invoke-WithRetry { [CmdletBinding()] [OutputType([System.Object])] param( [Parameter(Mandatory = $true, HelpMessage = "The script block to execute with retry logic.")] [scriptblock]$ScriptBlock, [Parameter(HelpMessage = "Maximum number of retry attempts.")] [ValidateRange(0, 1000)] [int]$MaxRetries = 3, [Parameter(HelpMessage = "Delay in seconds before the first retry.")] [ValidateRange(0, 86400)] [double]$DelaySeconds = 2, [Parameter(HelpMessage = "Multiplier applied to the delay on each successive retry.")] [ValidateRange(1, 100)] [double]$BackoffMultiplier = 1, [Parameter(HelpMessage = "Longest delay allowed between retries.")] [ValidateRange(0, 86400)] [double]$MaxDelaySeconds = 300, [Parameter(HelpMessage = "Optional list of exception types to retry on.")] [type[]]$RetryableExceptions, [Parameter(HelpMessage = "Optional callback script block invoked on each retry.")] [scriptblock]$OnRetry ) $attempt = 0 while ($true) { try { return (& $ScriptBlock) } catch { $lastError = $_ $attempt++ # Check if we should retry based on exception type if ($RetryableExceptions) { $shouldRetry = $false foreach ($exType in $RetryableExceptions) { if ($lastError.Exception -is $exType) { $shouldRetry = $true break } } if (-not $shouldRetry) { throw } } if ($attempt -gt $MaxRetries) { # Rethrow the original error record so callers keep the full error details throw $lastError } # First retry waits DelaySeconds, then DelaySeconds * BackoffMultiplier, ... $currentDelay = [Math]::Min($DelaySeconds * [Math]::Pow($BackoffMultiplier, $attempt - 1), $MaxDelaySeconds) Write-Verbose "Attempt $attempt of $($MaxRetries + 1) failed. Retrying in $currentDelay seconds... Exception: $($lastError.Exception.Message)" if ($OnRetry) { & $OnRetry $lastError.Exception $attempt } if ($currentDelay -gt 0) { Start-Sleep -Milliseconds ([int][Math]::Round($currentDelay * 1000)) } } } } |