Private/Get-AACDirectoryObject.ps1

function Get-AACDirectoryObject {
    <#
    .SYNOPSIS
        Looks up Entra ID objects - users, groups, service principals - by
        ID with Microsoft Graph's directoryObjects/getByIds, 1,000 at a
        time, several requests at once.
    .DESCRIPTION
        Returns @{ Objects (ID, lower case -> the object, as Graph returns
        it: @odata.type, displayName, userPrincipalName, userType,
        accountEnabled, appId, servicePrincipalType...); Error (why Graph
        refused, or '') }. An ID Graph doesn't return is a deleted object.
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [AllowEmptyCollection()]
        [string[]] $Id = @()
    )

    $ids = @($Id | Where-Object { $_ -match '^[0-9a-fA-F-]{36}$' } | ForEach-Object { $_.ToLowerInvariant() } | Select-Object -Unique)
    # Script blocks below run inside Invoke-AACHttpBatch: their names are prefixed 'directory'.
    $directoryFound = @{}
    if (-not $ids.Count) { return @{ Objects = $directoryFound; Error = '' } }
    $requests = @(for ($i = 0; $i -lt $ids.Count; $i += 1000) {
            $chunk = @($ids[$i..([Math]::Min($i + 999, $ids.Count - 1))])
            @{ Key = "ids$i"; Uri = 'https://graph.microsoft.com/v1.0/directoryObjects/getByIds?$select=id,displayName,userPrincipalName,userType,accountEnabled,appId,servicePrincipalType,mail'; Body = (ConvertTo-Json -InputObject @{ ids = $chunk; types = @('user', 'group', 'servicePrincipal') } -Depth 4 -Compress) }
        })
    $failures = Invoke-AACHttpBatch -Request $requests -Resource 'https://graph.microsoft.com' -ThrottleLimit 4 -OnResponse {
        param($DirectoryKey, [string] $DirectoryContent)
        $directoryPage = ConvertFrom-Json -InputObject $DirectoryContent -AsHashtable -Depth 20
        foreach ($directoryItem in @($directoryPage['value'])) { if ($directoryItem) { $directoryFound[([string]$directoryItem['id']).ToLowerInvariant()] = $directoryItem } }
    }
    $why = @($failures.Values | Where-Object { $_ }) | Select-Object -First 1
    @{ Objects = $directoryFound; Error = $(if ($why) { [string]$why } else { '' }) }
}