Private/Get-AzureUtilsExistingPrincipal.ps1
|
function Get-AzureUtilsExistingPrincipal { <# .SYNOPSIS Returns the subset of directory principal ids that still exist in Entra ID. .DESCRIPTION Resolves the given principal ids against Microsoft Graph (POST /directoryObjects/getByIds) via Invoke-AzRestMethod (Az.Accounts), in batches of up to 1000 ids per request (the Graph limit). Returns a case-insensitive set of the ids that resolved - i.e. the principals that still exist. Ids that do NOT come back have been deleted from Entra ID. The set is returned so the caller can flag role assignments whose principal is absent. If Graph cannot be reached (e.g. missing directory-read permission), this throws so the caller never mistakes an API failure for "every principal is orphaned". .PARAMETER PrincipalId The principal object ids to resolve. .OUTPUTS System.Collections.Generic.HashSet[string] (case-insensitive) of existing ids. #> [CmdletBinding()] [OutputType([System.Collections.Generic.HashSet[string]])] param( [string[]] $PrincipalId ) $existing = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $ids = @($PrincipalId | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique) # Return with the unary comma so PowerShell does not enumerate the HashSet on # output (which would drop its case-insensitive comparer and hand the caller a # plain, case-sensitive array). if ($ids.Count -eq 0) { return , $existing } $batchSize = 1000 for ($i = 0; $i -lt $ids.Count; $i += $batchSize) { $batch = $ids[$i..([math]::Min($i + $batchSize - 1, $ids.Count - 1))] $payload = @{ ids = @($batch) } | ConvertTo-Json -Depth 3 -Compress $response = Invoke-AzRestMethod -Method POST ` -Uri 'https://graph.microsoft.com/v1.0/directoryObjects/getByIds' ` -Payload $payload -ErrorAction Stop if ([int]$response.StatusCode -ge 400) { throw [System.Management.Automation.RuntimeException]::new( "Microsoft Graph getByIds failed with status $($response.StatusCode). A directory-read permission (e.g. Directory.Read.All) is required to detect orphaned role assignments. Response: $($response.Content)" ) } foreach ($obj in (($response.Content | ConvertFrom-Json).value)) { if ($obj.id) { $null = $existing.Add([string]$obj.id) } } } return , $existing } |