Workoho.Automation.Graph/Public/Invoke-Auto_MgGraphRequest.ps1

<#
.SYNOPSIS
    Wrapper for Invoke-MgGraphRequest to add retries for rate limiting and service unavailable errors.

.DESCRIPTION
    This script is a wrapper for the Invoke-MgGraphRequest script to add retries in case of rate limiting or service unavailable errors.
    The script will retry the request up to 5 times with an exponential backoff strategy. The script will also handle the Retry-After header for rate limiting errors.
    Note that when using batch requests, each response must be checked for rate limiting separately as this script only handles this for the batch request itself.

.PARAMETER Params
    The parameters to pass to the Invoke-MgGraphRequest cmdlet using splatting.

.OUTPUTS
    The response from the Microsoft Graph REST API request.
#>


function Invoke-Auto_MgGraphRequest {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [hashtable] $Params
    )

    Write-Auto_FunctionBegin $MyInvocation -OnceOnly

    $maxRetries = 5
    $retryCount = 0
    $baseWaitTime = 1 # start with 1 second

    do {
        try {
            $response = Invoke-MgGraphRequest @Params
            $rateLimitExceeded = $false
        }
        catch {
            if ($null -eq $_.Exception.Response) {
                Throw "Network error: $($_.Exception.Message)"
            }
            if ($_.Exception.Response.StatusCode -eq 404) {
                $rateLimitExceeded = $false
            }
            elseif (
                $_.Exception.Response.StatusCode -eq 429 -or
                $_.Exception.Response.StatusCode -eq 503
            ) {
                $waitTime = [math]::max($_.Exception.Response.Headers['Retry-After'] -as [int], $baseWaitTime)
                $jitter = Get-Random -Minimum 0 -Maximum 0.5 # random jitter between 0 and 0.5 seconds, with decimal precision
                $waitTime += $jitter
                Clear-Variable -Name response
                [System.GC]::Collect()
                [System.GC]::WaitForPendingFinalizers()

                if ($_.Exception.Response.StatusCode -eq 429) {
                    Write-Verbose "[COMMON]: - Rate limit exceeded, retrying in $waitTime seconds..."
                }
                else {
                    Write-Verbose "[COMMON]: - Service unavailable, retrying in $waitTime seconds..."
                }

                Start-Sleep -Milliseconds ($waitTime * 1000) # convert wait time to milliseconds for Start-Sleep
                $retryCount++
                $baseWaitTime *= 1.5 # client side exponential backoff
                $rateLimitExceeded = $true
            }
            else {
                $errorMessage = $_.Exception.Response.Content.ReadAsStringAsync().Result | ConvertFrom-Json
                Throw "Error $($_.Exception.Response.StatusCode.value__) $($_.Exception.Response.StatusCode): [$($errorMessage.error.code)] $($errorMessage.error.message)"
            }
        }
    } while ($rateLimitExceeded -and $retryCount -lt $maxRetries)

    if ($rateLimitExceeded) {
        Throw "Rate limit exceeded after $maxRetries retries."
    }

    Write-Auto_FunctionEnd $MyInvocation -OnceOnly
    return $response
}