Main/Invoke-ScriptBlockWithRetry.ps1

<#
.SYNOPSIS
    Executes a ScriptBlock with Retry
 
.DESCRIPTION
    Wraps a ScriptBlock in a try/catch to allow for retrying
    based on specific exceptions as defined in the retry policy passed.
    To create a retry policy object see New-RetryPolicy cmdlet.
 
.EXAMPLE
PS> $policy = New-RetryPolicy -Policy Linear -MilliSeconds 1000 -Retries 3
PS> Invoke-ScriptBlockWithRetry -Context { dir Z:\ } -RetryPolicy $policy
 
Invokes the provided ScriptBlock and will attempt to retry on an exception if it
meets the criteria defined in the policy. The number of retries as well as the
wait time between retries is also defined in the policy. In this case a linear
retry [1000, 2000, 4000] will the determine the wait time. The provided context
will be attempted 3 times according to the policy defined.
 
.NOTES
    This cmdlet requires terminating errors to be raised. If your global $ErrorActionPreference is set to 'Continue'
    or 'SilentlyContinue' errors will not be catched. There are a couple of ways to fix this:
    1. Set your global variable to 'Stop':
    PS> $ErrorActionPreference = 'Stop'
 
    2. Set this preference on the script block commands. i.e. from the example the context to invoke is:
    { dir Z:\ }
    Change this to stop on non-terminating errors as follows:
    { dir Z:\ -ErrorAction Stop}
 
    See New-RetryPolicy cmdlet for details on policy creation as well as all
    available options.
 
#>


function Invoke-ScriptBlockWithRetry {
    [CmdletBinding()]
    param(
        # Context to execute
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ScriptBlock] $Context,
        
        # Retry policy determines wait time, whether wait follows any pattern, and the types of exceptions to under which will retry
        [Parameter(Mandatory)]
        [ValidateScript({ $_.PSTypeNames[0] -eq (GetConfig('Module.RetryBlock.PolicyTypeName')) })]
        $RetryPolicy
        )

    $ErrorActionPreference = 'Stop'
    if ($Global:ErrorActionPreference -ne 'Stop') {
        $Global:ErrorActionPreference = 'Stop'
    }
    
    $continueRetrying = $true
    $retryLogicWorkingSet = $RetryPolicy.WorkingSet.Clone()
    while ($continueRetrying) {
        try {
            . $Context
            $continueRetrying = $false
        }
        catch {
            $e = $_
            if ($e.GetType().Name -eq 'ActionPreferenceStopException') {
                $e = $e.ErrorRecord
            }

            $invokeRetryLogic = $false
            if ($RetryPolicy.ExceptionActivity.Count -eq 0 -and
                $RetryPolicy.ExceptionCategory.Count -eq 0 -and
                $RetryPolicy.ExceptionErrorId.Count -eq 0) {
                # Retry on any error
                $invokeRetryLogic = $true
            }

            $invokeRetryLogic = $invokeRetryLogic -or ($RetryPolicy.ExceptionActivity -contains $e.CategoryInfo.Activity)
            $invokeRetryLogic = $invokeRetryLogic -or ($RetryPolicy.ExceptionCategory -contains $e.CategoryInfo.Category)
            $invokeRetryLogic = $invokeRetryLogic -or ($RetryPolicy.ExceptionErrorId -contains $e.FullyQualifiedErrorId)

            if ($invokeRetryLogic) {
                try {
                    . $RetryPolicy.RetryLogic -WorkingSet $retryLogicWorkingSet
                }
                catch {
                    $e2 = $_
                    $continueRetrying = $false
                    if($e2.GetType().Name -eq 'ActionPreferenceStopException') {
                        $e2 = $e2.ErrorRecord
                    }
                    
                    $retryLimitErrorId = GetConfig('Module.RetryBlock.RetryErrorId')
                    if ($e2.FullyQualifiedErrorId -ne $retryLimitErrorId) {
                        throw new [xUtilityException]::New(
                            'Invoke-ScriptBlockWithRetry',
                            [xUtilityErrorCategory]::InvalidRetryLogicEvaluation,
                            ("Error during RetryLogicEvaluation: {0}" -f $e2))
                    }
                    else {
                        Write-Verbose "Reached Retry Limit: $e2"
                        throw $e
                    }
                }
            }
            else {
                Write-Warning 'Retry Logic was not invoked as exception did not passed proper validation'
                $continueRetrying = $false
                throw $e
            }
        }
    }
} 

Set-Alias RetryBlock Invoke-ScriptBlockWithRetry