Private/Invoke-ArgusGraphRequest.ps1
|
function Invoke-ArgusGraphRequest { <# .SYNOPSIS Read-only Microsoft Graph GET with paging, throttling retries and advanced-query support. .DESCRIPTION All Graph reads in ARGUS go through this function. - Only GET is allowed: any other method throws before a request is sent (ARGUS is read-only). - -Uri is relative ('/users?$select=id') or an absolute https://graph.microsoft.com URL (used for @odata.nextLink); other hosts are rejected so the token is never sent elsewhere. - -All follows @odata.nextLink until the last page. - -Advanced adds 'ConsistencyLevel: eventual' and $count=true (advanced directory queries). - -Beta targets /beta instead of /v1.0 (checks using it must set UsesBeta = $true). - -OutputFilePath saves the response body to a file (used for usage-report CSV downloads). - -Raw emits the response body as is, for entities that have their own 'value' property (for example copilotPolicySetting). Collection responses are emitted item by item; single-entity responses are emitted as one object. #> [CmdletBinding()] [OutputType([object])] param( [Parameter(Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [string] $Uri, [Parameter()] [string] $Method = 'GET', [Parameter()] [switch] $All, [Parameter()] [System.Collections.IDictionary] $Headers = @{}, [Parameter()] [switch] $Advanced, [Parameter()] [switch] $Beta, [Parameter()] [string] $OutputFilePath, [Parameter()] [switch] $Raw, [Parameter()] [ValidateRange(0, 10)] [int] $MaxRetries = 5 ) if ($Method -ne 'GET') { throw "ARGUS is read-only: HTTP method '$Method' is not allowed (only GET)." } $requestHeaders = @{} foreach ($key in $Headers.Keys) { $requestHeaders[$key] = $Headers[$key] } if ($Uri -match '^https?://') { $parsed = [System.Uri]$Uri $expected = ([System.Uri]$script:ArgusGraphBaseUri).Host if ($parsed.Scheme -ne 'https' -or $parsed.Host -ne $expected) { throw "Refusing to call '$($parsed.Scheme)://$($parsed.Host)': only $script:ArgusGraphBaseUri is allowed." } $url = $Uri } else { $version = if ($Beta) { 'beta' } else { 'v1.0' } $url = '{0}/{1}/{2}' -f $script:ArgusGraphBaseUri, $version, $Uri.TrimStart('/') } if ($Advanced) { $requestHeaders['ConsistencyLevel'] = 'eventual' if ($url -notmatch '[?&]\$count=') { $url += $(if ($url.Contains('?')) { '&' } else { '?' }) + '$count=true' } } if ($OutputFilePath) { Invoke-ArgusWithRetry -MaxRetries $MaxRetries -Activity "GET $url" -ScriptBlock { Invoke-MgGraphRequest -Method GET -Uri $url -Headers $requestHeaders -OutputFilePath $OutputFilePath -ErrorAction Stop } | Out-Null return } while ($url) { Write-Verbose "GET $url" $response = Invoke-ArgusWithRetry -MaxRetries $MaxRetries -Activity "GET $url" -ScriptBlock { Invoke-MgGraphRequest -Method GET -Uri $url -Headers $requestHeaders -OutputType PSObject -ErrorAction Stop } if ($null -eq $response) { return } $valueProperty = $response.PSObject.Properties['value'] if ($Raw -or $null -eq $valueProperty) { $response return } foreach ($item in @($valueProperty.Value)) { $item } $next = $response.PSObject.Properties['@odata.nextLink'] $url = if ($All -and $next -and $next.Value) { [string]$next.Value } else { $null } if ($url -and $url -notlike "$script:ArgusGraphBaseUri/*") { throw "Unexpected @odata.nextLink host: '$url'." } } } |