Private/Invoke-WindowsDeviceLinkGraphGet.ps1

function Invoke-WindowsDeviceLinkGraphGet {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Uri,
        [AllowNull()][string]$AccessToken,
        [switch]$SdkMode,
        [ValidateRange(1, 5)][int]$MaxAttempts = 3,
        [ValidateRange(0, 60)][int]$MaxRetryAfterSeconds = 30,
        [scriptblock]$RequestScript,
        [scriptblock]$SleepScript
    )

    $attempt = 0
    while ($true) {
        $attempt++
        try {
            if ($RequestScript) { return & $RequestScript $Uri ([bool]$SdkMode) $AccessToken }
            if ($SdkMode) { return Invoke-MgGraphRequest -Method GET -Uri $Uri -ErrorAction Stop }
            if ([string]::IsNullOrWhiteSpace($AccessToken)) { throw 'A native Graph GET requires an access token.' }
            return Invoke-RestMethod -Method GET -Uri $Uri -Headers @{ Authorization = "Bearer $AccessToken" } -ErrorAction Stop
        }
        catch {
            $errorRecord = $_
            $statusCode = $null
            try {
                if ($errorRecord.Exception.Response -and $errorRecord.Exception.Response.StatusCode) { $statusCode = [int]$errorRecord.Exception.Response.StatusCode }
            }
            catch { $statusCode = $null }

            if ($null -eq $statusCode -and $errorRecord.Exception.Message -match '(?<!\d)(401|403|404|409|429|500|502|503|504)(?!\d)') { $statusCode = [int]$Matches[1] }

            $transportRetryable = $false
            try {
                if ($errorRecord.Exception -is [System.Net.WebException]) {
                    $transportRetryable = $errorRecord.Exception.Status -in @(
                        [System.Net.WebExceptionStatus]::Timeout,
                        [System.Net.WebExceptionStatus]::ConnectFailure,
                        [System.Net.WebExceptionStatus]::ConnectionClosed,
                        [System.Net.WebExceptionStatus]::NameResolutionFailure,
                        [System.Net.WebExceptionStatus]::ReceiveFailure,
                        [System.Net.WebExceptionStatus]::SendFailure
                    )
                }
            }
            catch {}
            if (-not $transportRetryable -and $null -eq $statusCode) {
                $transportRetryable = $errorRecord.Exception.Message -match '(?i)(timed out|timeout|temporarily unavailable|connection (?:was )?closed|remote name could not be resolved|name resolution)'
            }

            $retryable = ($statusCode -in @(429,500,502,503,504)) -or $transportRetryable
            if (-not $retryable -or $attempt -ge $MaxAttempts) {
                $safeDetail = Protect-WindowsDeviceLinkSensitiveText -Text ([string]$errorRecord.Exception.Message) -SensitiveValue @($AccessToken)
                if ($statusCode) { throw "Microsoft Graph GET failed with HTTP $statusCode. $safeDetail" }
                throw "Microsoft Graph GET failed. $safeDetail"
            }

            $retryAfter = $null
            if ($statusCode -eq 429) {
                try { $retryAfter = [string]$errorRecord.Exception.Response.Headers['Retry-After'] }
                catch { $retryAfter = $null }
            }

            $delaySeconds = Get-WindowsDeviceLinkGraphRetryDelay -Attempt $attempt -StatusCode $statusCode -RetryAfter $retryAfter -MaxRetryAfterSeconds $MaxRetryAfterSeconds
            $reason = if ($null -ne $statusCode) { "HTTP $statusCode" } else { 'a transient transport failure' }
            Write-Information -InformationAction Continue -MessageData ("Microsoft Graph GET encountered {0}; retrying attempt {1}/{2} after {3} second(s)." -f $reason,($attempt+1),$MaxAttempts,$delaySeconds)
            if ($delaySeconds -gt 0) { if ($SleepScript) { & $SleepScript $delaySeconds } else { Start-Sleep -Seconds $delaySeconds } }
        }
    }
}