Private/Invoke-InforcerGraphRequest.ps1
|
function Invoke-InforcerGraphRequest { <# .SYNOPSIS Wraps Invoke-MgGraphRequest with retry logic for documentation enrichment calls. .DESCRIPTION Sends a Graph API request and returns the response as a PSObject. For paged results, follows @odata.nextLink automatically (up to 10,000 items; a warning says when more were available). Retries only what can succeed on a second try: throttling (429), server errors (5xx) and network failures. 401/403 are not retried: a warning names the permission the call most likely lacks and the call returns what it has. Single-object 404 returns $null. Based on the Invoke-GraphRequestWithPaging pattern from RKSolutions-Module. .PARAMETER Uri The Graph API URI (e.g., "https://graph.microsoft.com/v1.0/directoryObjects/{id}"). .PARAMETER Method HTTP method (default: GET). .PARAMETER Body Optional JSON body (sent as application/json). .PARAMETER MaxRetries Maximum number of attempts for retryable failures (default: 3). .PARAMETER SingleObject When set, returns the response directly (not paged). Use for single-object lookups. #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$Uri, [Parameter()][string]$Method = 'GET', [Parameter()][string]$Body, [Parameter()][int]$MaxRetries = 3, [Parameter()][switch]$SingleObject ) $mgParams = @{ Method = $Method; OutputType = 'PSObject'; ErrorAction = 'Stop' } if ($Body) { $mgParams['Body'] = $Body; $mgParams['ContentType'] = 'application/json' } # Returns $true when the caller should retry; warns once for a missing permission. $shouldRetry = { param($ErrorRecord, [string]$RequestUri, [int]$Attempt) $ex = $ErrorRecord.Exception $statusCode = $null if ($ex.Response) { $statusCode = [int]$ex.Response.StatusCode } elseif ($ex.InnerException -and $ex.InnerException.Response) { $statusCode = [int]$ex.InnerException.Response.StatusCode } if ($statusCode -in 401, 403) { $permission = switch -Regex ($RequestUri) { '/deviceManagement/' { 'DeviceManagementConfiguration.Read.All' } '/conditionalAccess/' { 'Policy.Read.All' } default { 'Directory.Read.All' } } $path = ([uri]$RequestUri).AbsolutePath Write-Warning "Microsoft Graph refused $path (HTTP $statusCode). The signed-in account likely lacks $permission; those names stay as raw IDs." return $false } $retryable = $null -eq $statusCode -or $statusCode -eq 429 -or $statusCode -ge 500 if ($retryable -and $Attempt -lt $MaxRetries) { Start-Sleep -Seconds (2 * $Attempt) return $true } $false } if ($SingleObject) { for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { try { return Invoke-MgGraphRequest -Uri $Uri @mgParams } catch { if (-not (& $shouldRetry $_ $Uri $attempt)) { return $null } } } return $null } $results = [System.Collections.Generic.List[object]]::new() $currentUri = $Uri while ($currentUri) { $response = $null for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { try { $response = Invoke-MgGraphRequest -Uri $currentUri @mgParams break } catch { if (-not (& $shouldRetry $_ $currentUri $attempt)) { return $results.ToArray() } } } if ($null -eq $response -or -not $response.PSObject.Properties['value']) { break } if ($response.value) { $results.AddRange(@($response.value)) } $currentUri = $response.'@odata.nextLink' if ($currentUri -and $results.Count -ge 10000) { Write-Warning "Microsoft Graph returned more than 10,000 items for $(([uri]$Uri).AbsolutePath); only the first $($results.Count) are used." break } } $results.ToArray() } |