Private/10-InvokeAtlasGraphRequest.ps1
|
function Get-AtlasResponseProperty { [CmdletBinding()] param( [Parameter(Mandatory)] [AllowNull()] [object] $InputObject, [Parameter(Mandatory)] [string] $Name ) if ($null -eq $InputObject) { return $null } if ($InputObject -is [System.Collections.IDictionary]) { return $InputObject[$Name] } $property = $InputObject.PSObject.Properties[$Name] if ($null -ne $property) { return $property.Value } return $null } function Invoke-AtlasGraphRequest { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Uri, [hashtable] $Headers = @{}, [ValidateRange(0, 10)] [int] $MaximumRetryCount = 5 ) if (-not (Get-Command -Name Invoke-MgGraphRequest -ErrorAction SilentlyContinue)) { throw 'Microsoft.Graph.Authentication is required for live collection. Install it with Install-PSResource Microsoft.Graph.Authentication -Scope CurrentUser.' } $items = [System.Collections.Generic.List[object]]::new() $nextLink = $Uri $requestCount = 0 $retryCount = 0 $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() while ($nextLink) { if (-not (Test-AtlasGraphUri -Uri $nextLink)) { throw 'The Microsoft Graph request URI was rejected because its scheme, host or API path is not permitted.' } $response = $null $attempt = 0 while ($null -eq $response) { try { $requestCount++ Update-AtlasProgressRequest -RequestIncrement 1 -Status 'Requesting Microsoft Graph' $response = Invoke-MgGraphRequest -Method GET -Uri $nextLink -Headers $Headers -OutputType PSObject } catch { $attempt++ $statusCode = $null if ($_.Exception.PSObject.Properties['ResponseStatusCode']) { $statusCode = [int] $_.Exception.ResponseStatusCode } elseif ( $_.Exception.PSObject.Properties['Response'] -and $_.Exception.Response -and $_.Exception.Response.PSObject.Properties['StatusCode'] ) { $statusCode = [int] $_.Exception.Response.StatusCode } if ($attempt -gt $MaximumRetryCount -or $statusCode -notin @(429, 502, 503, 504)) { throw } $retryCount++ $retryAfterSeconds = 0 $responseHeaders = if ( $_.Exception.PSObject.Properties['Response'] -and $_.Exception.Response ) { $_.Exception.Response.Headers } else { $null } $retryAfter = if ($responseHeaders -and $responseHeaders.PSObject.Properties['RetryAfter']) { $responseHeaders.RetryAfter } else { $null } if ($retryAfter) { if ($retryAfter.PSObject.Properties['Delta'] -and $retryAfter.Delta) { $retryAfterSeconds = [int] [math]::Ceiling($retryAfter.Delta.TotalSeconds) } elseif ($retryAfter.PSObject.Properties['Date'] -and $retryAfter.Date) { $retryAfterSeconds = [int] [math]::Ceiling(($retryAfter.Date - [datetimeoffset]::UtcNow).TotalSeconds) } } if ($retryAfterSeconds -le 0) { $retryAfterSeconds = [math]::Min(60, [math]::Pow(2, $attempt)) } $retryReason = if ($statusCode -eq 429) { 'Microsoft Graph throttled the request' } else { "Microsoft Graph returned HTTP $statusCode" } Update-AtlasProgressRequest ` -RetryIncrement 1 ` -Status "$retryReason. Retrying in $retryAfterSeconds seconds" $progressContext = Get-Variable ` -Name IdentityAtlasProgressContext ` -Scope Script ` -ValueOnly ` -ErrorAction SilentlyContinue $collectorDisplayName = if ($progressContext -and $progressContext.CollectorDisplayName) { $progressContext.CollectorDisplayName } else { 'Microsoft Graph data' } Write-Warning ( "$retryReason while collecting $collectorDisplayName. " + "Retrying in $retryAfterSeconds seconds (attempt $attempt of $MaximumRetryCount)." ) Start-Sleep -Seconds $retryAfterSeconds } } $hasValueCollection = if ($response -is [System.Collections.IDictionary]) { $response.Contains('value') } else { $null -ne $response.PSObject.Properties['value'] } if ($hasValueCollection) { $pageItems = Get-AtlasResponseProperty -InputObject $response -Name 'value' foreach ($item in @($pageItems)) { if ($null -ne $item) { $items.Add($item) } } $nextLink = Get-AtlasResponseProperty -InputObject $response -Name '@odata.nextLink' } else { $items.Add($response) $nextLink = $null } } $stopwatch.Stop() return [pscustomobject] @{ Items = $items Metrics = @{ requestCount = $requestCount retryCount = $retryCount itemCount = $items.Count durationMilliseconds = [math]::Round($stopwatch.Elapsed.TotalMilliseconds, 2) } } } |