Private/Get-PSUBatchDirectoryObjects.ps1

<#
.SYNOPSIS
    Batch-retrieves directory objects from Microsoft Graph to reduce per-object API calls.
 
.DESCRIPTION
    Uses the Microsoft Graph /$batch endpoint to fetch up to 20 directory objects per
    request. Falls back to individual Get-MgDirectoryObject calls if the batch request
    fails. Requires an active Connect-MgGraph session.
 
.PARAMETER Ids
    Array of directory object IDs to retrieve.
 
.OUTPUTS
    [System.Object[]] Array of directory objects with id, displayName, mail,
    userPrincipalName, and @odata.type properties.
 
.NOTES
    Author: Lakshmanachari Panuganti
    Created: 11th August 2025
    Last Modified: 7th March 2026
    Version: 1.0
#>

function Get-PSUBatchDirectoryObjects {
    param([string[]] $Ids)

    if (-not $Ids -or $Ids.Count -eq 0) { return @() }

    # Graph batch supports up to 20 requests per batch
    $chunkSize = 20
    $out = @()

    foreach ($chunk in ($Ids | ForEach-Object -Begin { $i=0 } -Process { [PSCustomObject]@{Index = [int]($i++); Id = $_ } } | Group-Object { [int]($_.Index / $chunkSize) } | ForEach-Object { $_.Group | Select-Object -ExpandProperty Id })) {
        # Build requests for this chunk
        $requests = @()
        $reqId = 1
        foreach ($id in $chunk) {
            $requests += @{
                id = "$reqId"
                method = "GET"
                url = "/directoryObjects/$id"
            }
            $reqId++
        }
        $body = @{ requests = $requests }

        try {
            $resp = Invoke-MgGraphRequest -Method POST -Uri '/$batch' -Body ($body | ConvertTo-Json -Depth 5) -ContentType 'application/json'
        } catch {
            Write-Verbose "Graph batch request failed: $_"
            # fall back: try per-id
            foreach ($id in $chunk) {
                try {
                    $obj = Get-MgDirectoryObject -DirectoryObjectId $id -ErrorAction Stop
                    if ($obj) { $out += $obj }
                } catch { Write-Verbose "Get-MgDirectoryObject failed for $id`: $_" }
            }
            continue
        }

        # resp has 'responses' array
        if ($resp -and $resp.responses) {
            foreach ($r in $resp.responses) {
                if ($r.status -ge 200 -and $r.status -lt 300) {
                    # content is a JSON object - parse
                    $contentJson = $r.body | ConvertTo-Json -Compress
                    # The SDK returns objects already - but to be safe, rehydrate minimal properties
                    $obj = $r.body
                    $out += $obj
                } else {
                    Write-Verbose "Batch sub-request failed (id $($r.id)) status $($r.status)"
                }
            }
        }
    }

    return $out
}