internal/Get-MsGraphResults.ps1
<#
.SYNOPSIS Query Microsoft Graph API .EXAMPLE PS C:\>Get-MsGraphResults 'users' Return query results for first page of users. .EXAMPLE PS C:\>Get-MsGraphResults 'users' -ApiVersion beta Return query results for all users using the beta API. .EXAMPLE PS C:\>Get-MsGraphResults 'users' -UniqueId 'user1@domain.com','user2@domain.com' -Select id,userPrincipalName,displayName Return id, userPrincipalName, and displayName for user1@domain.com and user2@domain.com. #> function Get-MsGraphResults { [CmdletBinding()] [OutputType([PSCustomObject])] param ( # Graph endpoint such as "users". [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [uri[]] $RelativeUri, # Specifies unique Id(s) for the URI endpoint. For example, users endpoint accepts Id or UPN. [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $false)] #[ValidateNotNullOrEmpty()] [string[]] $UniqueId, # Filters properties (columns). [Parameter(Mandatory = $false)] [string[]] $Select, # Filters results (rows). https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter [Parameter(Mandatory = $false)] [string] $Filter, # Specifies the page size of the result set. [Parameter(Mandatory = $false)] [int] $Top, # Include a count of the total number of items in a collection [Parameter(Mandatory = $false)] [switch] $Count, # Parameters such as "$orderby". [Parameter(Mandatory = $false)] [hashtable] $QueryParameters, # API Version. [Parameter(Mandatory = $false)] [ValidateSet('v1.0', 'beta')] [string] $ApiVersion = 'v1.0', # Specifies consistency level. [Parameter(Mandatory = $false)] [string] $ConsistencyLevel = "eventual", # Copy OData Context to each object. [Parameter(Mandatory = $false)] [switch] $KeepODataContext, # Disable deduplication of UniqueId values. [Parameter(Mandatory = $false)] [switch] $DisableUniqueIdDeduplication, # Only return first page of results. [Parameter(Mandatory = $false)] [switch] $DisablePaging, # Force individual requests to MS Graph. [Parameter(Mandatory = $false)] [switch] $DisableBatching, # Specify Batch size. [Parameter(Mandatory = $false)] [int] $BatchSize = 20, # Base URL for Microsoft Graph API. [Parameter(Mandatory = $false)] [uri] $GraphBaseUri = 'https://graph.microsoft.com/' ) begin { $listRequests = New-Object 'System.Collections.Generic.List[pscustomobject]' function Catch-MsGraphError ($ErrorRecord) { if ($_.Exception -is [System.Net.WebException]) { $StreamReader = New-Object System.IO.StreamReader -ArgumentList $_.Exception.Response.GetResponseStream() try { $responseBody = ConvertFrom-Json $StreamReader.ReadToEnd() } finally { $StreamReader.Close() } if ($responseBody.error.code -eq 'Authentication_ExpiredToken' -or $responseBody.error.code -eq 'Service_ServiceUnavailable') { #Write-AppInsightsException $_.Exception Write-Error -Exception $_.Exception -Message $responseBody.error.message -ErrorId $responseBody.error.code -Category $_.CategoryInfo.Category -CategoryActivity $_.CategoryInfo.Activity -CategoryReason $_.CategoryInfo.Reason -CategoryTargetName $_.CategoryInfo.TargetName -CategoryTargetType $_.CategoryInfo.TargetType -TargetObject $_.TargetObject -ErrorAction Stop } else { Write-Error -Exception $_.Exception -Message $responseBody.error.message -ErrorId $responseBody.error.code -Category $_.CategoryInfo.Category -CategoryActivity $_.CategoryInfo.Activity -CategoryReason $_.CategoryInfo.Reason -CategoryTargetName $_.CategoryInfo.TargetName -CategoryTargetType $_.CategoryInfo.TargetType -TargetObject $_.TargetObject -ErrorVariable cmdError Write-AppInsightsException $cmdError.Exception } } else { throw $ErrorRecord } } function Test-MsGraphBatchError ($BatchResponse) { if ($BatchResponse.status -ne '200') { if ($BatchResponse.body.error.code -eq 'Authentication_ExpiredToken' -or $BatchResponse.body.error.code -eq 'Service_ServiceUnavailable') { Write-Error -Message $BatchResponse.body.error.message -ErrorId $BatchResponse.body.error.code -ErrorAction Stop } else { Write-Error -Message $BatchResponse.body.error.message -ErrorId $BatchResponse.body.error.code -ErrorVariable cmdError Write-AppInsightsException $cmdError.Exception } return $true } return $false } function New-MsGraphBatchRequest { param ( [object[]] $Requests, [int] $BatchSize = 20, [int] $Depth = 1, [uri] $GraphBatchUri = 'https://graph.microsoft.com/v1.0/$batch' ) for ($iRequest = 0; $iRequest -lt $Requests.Count; $iRequest += [System.Math]::Pow($BatchSize, $Depth)) { $indexEnd = [System.Math]::Min($iRequest + [System.Math]::Pow($BatchSize, $Depth) - 1, $Requests.Count - 1) if ($Depth -gt 1) { $BatchRequest = New-MsGraphBatchRequest $Requests[$iRequest..$indexEnd] -Depth ($Depth - 1) } else { $BatchRequest = $Requests[$iRequest..$indexEnd] } [pscustomobject]@{ id = $iRequest method = 'POST' url = $GraphBatchUri.AbsoluteUri body = [PSCustomObject]@{ requests = $BatchRequest } } } } function Get-MsGraphResultsCount ([uri]$Uri) { $uriQueryEndpointCount = New-Object System.UriBuilder -ArgumentList $Uri -ErrorAction Stop $uriQueryEndpointCount.Path = ([IO.Path]::Combine($uriQueryEndpointCount.Path, '$count')) [int] $Count = $null try { $Count = Invoke-RestMethod -WebSession $MsGraphSession -UseBasicParsing -Method GET -Uri $uriQueryEndpointCount.Uri.AbsoluteUri -Headers @{ ConsistencyLevel = 'eventual' } -Verbose:$false -ErrorAction Ignore } catch {} return $Count } function Format-Result ($results, [bool]$RawOutput, [bool]$KeepODataContext) { if (!$RawOutput -and $results.psobject.Properties.Name -contains 'value') { foreach ($result in $results.value) { if ($KeepODataContext) { if ($result -is [hashtable]) { $result.Add('@odata.context', ('{0}/$entity' -f $results.'@odata.context')) } else { $result | Add-Member -MemberType NoteProperty -Name '@odata.context' -Value ('{0}/$entity' -f $results.'@odata.context') } } Write-Output $result } } else { Write-Output $results } } function Complete-Result ($results, [bool]$DisablePaging, [bool]$KeepODataContext, [string]$RequestMethod = 'GET', [uri]$RequestUri, [hashtable]$RequestHeaders) { Format-Result $results $DisablePaging $KeepODataContext if (!$DisablePaging -and $results) { $ProgressState = $null if (Get-ObjectPropertyValue $results '@odata.nextLink') { $Total = Get-MsGraphResultsCount $RequestUri if ($Total) { $Activity = 'Microsoft Graph Request' if ($RequestUri) { $Activity = ('Microsoft Graph Request - {0} {1}' -f $RequestMethod, $RequestUri.AbsolutePath) } $ProgressState = Start-Progress -Activity $Activity -Total $Total $ProgressState.CurrentIteration = $results.value.Count } } try { while (Get-ObjectPropertyValue $results '@odata.nextLink') { if ($ProgressState) { Update-Progress $ProgressState -IncrementBy $results.value.Count } $nextLink = $results.'@odata.nextLink' # Confirm-ModuleAuthentication -ErrorAction Stop # $results = Invoke-MgGraphRequest -Method GET -Uri $results.'@odata.nextLink' -Headers $RequestHeaders $MsGraphSession = Confirm-ModuleAuthentication -MsGraphSession -ErrorAction Stop $results = $null try { $results = Invoke-RestMethod -WebSession $MsGraphSession -UseBasicParsing -Method Get -Uri $nextLink -Headers $RequestHeaders -ErrorAction Stop } catch { Catch-MsGraphError $_ } Format-Result $results $DisablePaging $KeepODataContext } } finally { if ($ProgressState) { Stop-Progress $ProgressState } } } } } process { ## Initialize if ($DisableBatching -and ($RelativeUri.Count -gt 1 -or $UniqueId -and $UniqueId.Count -gt 1)) { Write-Warning ('This command is invoking {0} individual Graph requests. For better performance, remove the -DisableBatching parameter.' -f ($RelativeUri.Count * $UniqueId.Count)) } ## Process Each RelativeUri [System.Collections.Generic.List[uri]] $uriQueryEndpoints = New-Object 'System.Collections.Generic.List[uri]' foreach ($uri in $RelativeUri) { if ($uri.IsAbsoluteUri) { $uriQueryEndpoint = New-Object System.UriBuilder -ArgumentList $uri } else { $uriQueryEndpoint = New-Object System.UriBuilder -ArgumentList ([IO.Path]::Combine($GraphBaseUri.AbsoluteUri, $ApiVersion, $uri)) } ## Combine query parameters from URI and cmdlet parameters if ($uriQueryEndpoint.Query) { [hashtable] $finalQueryParameters = ConvertFrom-QueryString $uriQueryEndpoint.Query -AsHashtable if ($QueryParameters) { foreach ($ParameterName in $QueryParameters.Keys) { $finalQueryParameters[$ParameterName] = $QueryParameters[$ParameterName] } } } elseif ($QueryParameters) { [hashtable] $finalQueryParameters = $QueryParameters } else { [hashtable] $finalQueryParameters = @{ } } if ($Select) { $finalQueryParameters['$select'] = $Select -join ',' } if ($Filter) { $finalQueryParameters['$filter'] = $Filter } if ($Top) { $finalQueryParameters['$top'] = $Top } if ($PSBoundParameters.ContainsKey('Count')) { $finalQueryParameters['$count'] = $Count } $uriQueryEndpoint.Query = ConvertTo-QueryString $finalQueryParameters ## Expand with UniqueIds if ($PSBoundParameters.ContainsKey('UniqueId')) { foreach ($id in $UniqueId) { if ($id) { ## If the URI contains '{0}', then replace it with Unique Id. if ($uriQueryEndpoint.Uri.AbsoluteUri.Contains('%7B0%7D')) { $uriQueryEndpointUniqueId = New-Object System.UriBuilder -ArgumentList ([System.Net.WebUtility]::UrlDecode($uriQueryEndpoint.Uri.AbsoluteUri) -f $id) } else { $uriQueryEndpointUniqueId = New-Object System.UriBuilder -ArgumentList $uriQueryEndpoint.Uri $uriQueryEndpointUniqueId.Path = ([IO.Path]::Combine($uriQueryEndpointUniqueId.Path, $id)) } if (!$uriQueryEndpoints.Contains($uriQueryEndpointUniqueId.Uri) -or $DisableUniqueIdDeduplication) { $uriQueryEndpoints.Add($uriQueryEndpointUniqueId.Uri) } } } } else { $uriQueryEndpoints.Add($uriQueryEndpoint.Uri) } } ## Invoke graph requests individually or save for single batch request foreach ($uriFinal in $uriQueryEndpoints) { if (!$DisableBatching -and ($uriQueryEndpoints.Count -gt 1)) { ## Create batch request entry $request = [pscustomobject]@{ id = $listRequests.Count #(New-Guid).ToString() method = 'GET' url = $uriFinal.AbsoluteUri -replace ('{0}{1}/' -f $GraphBaseUri.AbsoluteUri, $ApiVersion) headers = @{ ConsistencyLevel = $ConsistencyLevel } } $listRequests.Add($request) } else { ## Get results # Confirm-ModuleAuthentication -ErrorAction Stop # [hashtable] $results = Invoke-MgGraphRequest -Method GET -Uri $uriFinal.AbsoluteUri -Headers @{ ConsistencyLevel = $ConsistencyLevel } $MsGraphSession = Confirm-ModuleAuthentication -MsGraphSession -ErrorAction Stop $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() try { $results = Invoke-RestMethod -WebSession $MsGraphSession -UseBasicParsing -Method GET -Uri $uriFinal.AbsoluteUri -Headers @{ ConsistencyLevel = $ConsistencyLevel } -ErrorAction Stop #Format-Result $results $DisablePaging $KeepODataContext Complete-Result $results $DisablePaging $KeepODataContext 'GET' $uriFinal @{ ConsistencyLevel = $ConsistencyLevel } } catch { Catch-MsGraphError $_ } finally { $Stopwatch.Stop() Write-AppInsightsDependency ('{0} {1}' -f 'GET', $uriFinal.AbsolutePath) -Type 'MS Graph' -Data ('{0} {1}' -f 'GET', $uriFinal.AbsoluteUri) -Duration $Stopwatch.Elapsed -Success ($null -ne $results) } } } } end { if ($listRequests.Count -gt 0) { $uriQueryEndpoint = New-Object System.UriBuilder -ArgumentList ([IO.Path]::Combine($GraphBaseUri.AbsoluteUri, $ApiVersion, '$batch')) $ProgressState = Start-Progress -Activity 'Microsoft Graph Requests - Batched' -Total $listRequests.Count $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() for ($iRequest = 0; $iRequest -lt $listRequests.Count; $iRequest += $BatchSize) { Update-Progress $ProgressState -IncrementBy $BatchSize # foreach ($request in $listRequests[$iRequest..$indexEnd]) { # $request.url = $request.url.Substring($uriQueryEndpoint.Uri.AbsoluteUri.Length - 6) # $request.url # } $indexEnd = [System.Math]::Min($iRequest + $BatchSize - 1, $listRequests.Count - 1) $jsonRequests = [PSCustomObject]@{ requests = $listRequests[$iRequest..$indexEnd] } | ConvertTo-Json -Depth 5 -Compress # Confirm-ModuleAuthentication -ErrorAction Stop # [hashtable] $resultsBatch = Invoke-MgGraphRequest -Method POST -Uri $uriQueryEndpoint.Uri.AbsoluteUri -Body $jsonRequests # [hashtable[]] $resultsBatch = $resultsBatch.responses | Sort-Object -Property id $MsGraphSession = Confirm-ModuleAuthentication -MsGraphSession -ErrorAction Stop $resultsBatch = Invoke-RestMethod -WebSession $MsGraphSession -UseBasicParsing -Method POST -Uri $uriQueryEndpoint.Uri.AbsoluteUri -ContentType 'application/json' -Body $jsonRequests -ErrorAction Stop [array] $resultsBatch = $resultsBatch.responses | Sort-Object -Property { [int]$_.id } foreach ($results in ($resultsBatch)) { if (!(Test-MsGraphBatchError $results)) { #Format-Result $results.body $DisablePaging $KeepODataContext Complete-Result $results.body $DisablePaging $KeepODataContext $listRequests[$results.id].method $uriQueryEndpoint.Uri.AbsoluteUri.Replace('$batch', $listRequests[$results.id].url) $listRequests[$results.id].headers } } } $Stopwatch.Stop() Write-AppInsightsDependency ('{0} {1}' -f 'POST', $uriQueryEndpoint.Uri.AbsolutePath) -Type 'MS Graph' -Data ("{0} {1}`r`n`r`n{2}" -f 'POST', $uriQueryEndpoint.Uri.AbsoluteUri, ('{{"requests":[...{0}...]}}' -f $listRequests.Count)) -Duration $Stopwatch.Elapsed -Success ($null -ne $resultsBatch) Stop-Progress $ProgressState } } } |