Private/Operations/Invoke-OperationWithRetry.ps1

function Invoke-OperationWithRetry {
    <#
    .SYNOPSIS
        Executes a script block with configurable retry logic for transient failures
 
    .DESCRIPTION
        Provides a reusable retry mechanism for any operation that may experience
        transient failures. Uses exponential backoff with jitter for optimal retry timing.
 
    .PARAMETER ScriptBlock
        The operation to execute with retry logic
 
    .PARAMETER MaxRetries
        Maximum number of retry attempts (default: 3)
 
    .PARAMETER RetryableErrorPattern
        Regex pattern to identify retryable errors
 
    .PARAMETER OperationName
        Descriptive name for logging purposes
 
    .EXAMPLE
        PS> Invoke-OperationWithRetry -ScriptBlock { Get-ADUser 'testuser' } -OperationName 'Get User'
 
        DESCRIPTION: Executes AD user lookup with automatic retry on transient failures
        OUTPUT: AD user object or throws on permanent failure
        USE CASE: Handling intermittent AD connectivity issues
 
    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
 
        TROUBLESHOOTING:
        - For retry configuration: .\Troubleshooting\Performance\Retry-Configuration.md
        - For error patterns: .\Troubleshooting\Common\Error-Classification.md
    #>


    [CmdletBinding()]
    [OutputType('OperationResult')]
    param(
        [Parameter(Mandatory)]
        [scriptblock]$ScriptBlock,

        [Parameter()]
        [ValidateRange(1, 10)]
        [int]$MaxRetries = 3,

        [Parameter()]
        [string]$RetryableErrorPattern = 'timeout|network|connection|busy|unavailable|server not operational|replication|domain controller',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$OperationName = 'Operation',

        [Parameter()]
        [string]$CorrelationId = [System.Guid]::NewGuid().ToString()
    )

    begin {
        $startMessage = "Starting retry operation: $OperationName (Max Retries: $MaxRetries)"
        Write-Verbose "$startMessage - CorrelationId: $CorrelationId"
    }

    process {
        $attempt = 0
        $lastError = $null

        do {
            $attempt++
            try {
                Write-Verbose "Executing $OperationName (attempt $attempt/$MaxRetries)"
                $result = & $ScriptBlock

                Write-Verbose "Operation '$OperationName' succeeded on attempt $attempt"
                return $result
            }
            catch {
                $lastError = $_
                $isRetryable = $_.Exception.Message -match $RetryableErrorPattern

                if (-not $isRetryable -or $attempt -ge $MaxRetries) {
                    Write-Verbose "Operation '$OperationName' failed permanently after $attempt attempts"
                    throw $lastError
                }

                $waitTime = [Math]::Min([Math]::Pow(2, $attempt - 1) + (Get-Random -Maximum 2), 30)
                Write-Verbose "Operation '$OperationName' failed (attempt $attempt/$MaxRetries), retrying in $waitTime seconds"
                Start-Sleep -Seconds $waitTime
            }
        } while ($attempt -lt $MaxRetries)

        throw $lastError
    }
}