Private/RKCoreGraphAdapter.ps1

# Private: the module's implementation of the two Graph adapters module/Core calls.
# The RKSolutions Maester pack has its own implementation (helpers/GraphAdapter.ps1); this file is not synced.

function Invoke-RKCoreGraphGet {
    # One GET. The Graph SDK retries 429/503/504 itself; retry 500, 502 and a dropped connection or timeout (no status) here like Maester's own Graph helper.
    [CmdletBinding()]
    param([Parameter(Mandatory)] [string] $Uri)

    $delays = @(2, 4)
    for ($attempt = 0; ; $attempt++) {
        try { return Invoke-MgGraphRequest -Method GET -Uri $Uri -OutputType PSObject -ErrorAction Stop }
        catch {
            if ($attempt -ge $delays.Count -or (Get-RKCoreGraphErrorStatus -ErrorRecord $_) -notin 0, 500, 502) { throw }
            Start-Sleep -Seconds $delays[$attempt]
        }
    }
}

function Invoke-RKCoreGraphRequest {
    <#
    .SYNOPSIS
        One Graph GET. Without -All: the raw body. With -All: every item of value, following @odata.nextLink.
        Throws on an HTTP error.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $Uri,
        [switch] $All
    )

    $target = ConvertTo-RKCoreGraphPath -Uri $Uri
    $next = "https://graph.microsoft.com/$($target.Version)/$($target.Path)"
    if (-not $All) { return Invoke-RKCoreGraphGet -Uri $next }

    $items = [System.Collections.Generic.List[object]]::new()
    $seen = [System.Collections.Generic.HashSet[string]]::new()
    while ($next) {
        if (-not $seen.Add($next)) { throw "Graph returned the same @odata.nextLink twice for $Uri." }
        $page = Invoke-RKCoreGraphGet -Uri $next
        if ($page.PSObject.Properties['value']) { foreach ($item in @($page.value | Where-Object { $null -ne $_ })) { $items.Add($item) } }
        else { $items.Add($page) }
        $next = $page.'@odata.nextLink'
    }
    $items.ToArray()
}

function Invoke-RKCoreGraphBatch {
    <#
    .SYNOPSIS
        Many Graph GETs through $batch. Returns Id, Status and Body (the raw first page) per request; never throws
        for a single request.
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Requests)

    # Invoke-RKGraphBatch takes one version per call, so group the requests by the version in their URL.
    $byVersion = @{}
    foreach ($request in $Requests) {
        $target = ConvertTo-RKCoreGraphPath -Uri $request.Url
        if (-not $byVersion.ContainsKey($target.Version)) { $byVersion[$target.Version] = [System.Collections.Generic.List[object]]::new() }
        $byVersion[$target.Version].Add([pscustomobject]@{ Id = [string]$request.Id; Url = "/$($target.Path)" })
    }
    foreach ($version in $byVersion.Keys) {
        foreach ($response in (Invoke-RKGraphBatch -Requests $byVersion[$version].ToArray() -GraphVersion $version -Activity 'Reading Intune data from Microsoft Graph')) {
            # A request that failed without an answer from Graph has no Body but an Error: give it the shape Get-RKCoreBatchBody reads.
            $body = $response.Body
            if ($null -eq $body -and $response.PSObject.Properties['Error'] -and $response.Error) { $body = [pscustomobject]@{ error = [pscustomobject]@{ message = [string]$response.Error } } }
            [pscustomobject]@{ Id = [string]$response.Id; Status = [int]$response.Status; Body = $body }
        }
    }
}