Private/Get-NSPConditionalAccessAdminRoleHolder.ps1
|
function Get-NSPConditionalAccessAdminRoleHolder { <# .SYNOPSIS Users who hold an active, tenant-wide assignment of a role NSP-ADMIN-MFA-01 targets, directly or through a role-assignable group. .DESCRIPTION Returns a map of user ID to { Id, Roles (role names), Via (role names with 'via <group>' when assigned through a group) }. These users are covered by their role: the admin policy requires MFA at every sign-in, so coverage leaves cloud-only admins out of the groups. Only what Conditional Access sees counts: active assignments scoped to the whole directory ('/'). Eligible (PIM) assignments and administrative-unit scopes are not read. Service principals are ignored. Any read failure throws: an unread role is never an empty one. Needs RoleManagement.Read.Directory. .EXAMPLE (Get-NSPConditionalAccessAdminRoleHolder).Keys #> [CmdletBinding()] [OutputType([hashtable])] param() $table = Get-NSPConditionalAccessRoleTable $definition = @(Get-NSPConditionalAccessDefinition | Where-Object { $_['Id'] -eq 'NSP-ADMIN-MFA-01' })[0] $names = if ($definition) { @($definition['Users']['IncludeRoles'] | Where-Object { $_ -match '^\{Role:(.+)\}$' } | ForEach-Object { $Matches[1] }) } else { @($table.Keys | Sort-Object) } $holders = @{} function Add-Holder { param([string]$Id, [string]$Role) if (-not $holders.ContainsKey($Id)) { $holders[$Id] = [pscustomobject]@{ Id = $Id; Roles = @(); Via = @() } } if ($holders[$Id].Roles -notcontains $Role) { $holders[$Id].Roles = @($holders[$Id].Roles) + $Role } } $groupMembers = @{} foreach ($name in $names) { $templateId = $table[$name] if (-not $templateId) { continue } $filter = [uri]::EscapeDataString("roleDefinitionId eq '$templateId'") foreach ($assignment in Invoke-NSPGraphCollection -Uri "roleManagement/directory/roleAssignments?`$filter=$filter&`$expand=principal") { $scope = Get-NSPConditionalAccessMapValue $assignment 'directoryScopeId' if ($scope -and $scope -ne '/') { continue } $principal = Get-NSPConditionalAccessMapValue $assignment 'principal' $principalId = [string](Get-NSPConditionalAccessMapValue $assignment 'principalId') switch ([string](Get-NSPConditionalAccessMapValue $principal '@odata.type')) { '#microsoft.graph.user' { Add-Holder $principalId $name $holders[$principalId].Via = @($holders[$principalId].Via) + $name } '#microsoft.graph.group' { if (-not $groupMembers.ContainsKey($principalId)) { $groupMembers[$principalId] = @(Invoke-NSPGraphCollection -Uri "groups/$principalId/transitiveMembers/microsoft.graph.user?`$select=id" | ForEach-Object { Get-NSPConditionalAccessMapValue $_ 'id' }) } $groupName = Get-NSPConditionalAccessMapValue $principal 'displayName' foreach ($memberId in $groupMembers[$principalId]) { Add-Holder $memberId $name $holders[$memberId].Via = @($holders[$memberId].Via) + "$name via $groupName" } } } } } $holders } |