Public/Get-EntraPrivilegedRoleReview.ps1
|
function Get-EntraPrivilegedRoleReview { <# .SYNOPSIS Reviews Entra ID privileged role assignments and PIM configuration. .DESCRIPTION Enumerates all directory role assignments, flags permanent (active) assignments to high-privilege roles, identifies users with multiple privileged roles, and checks for Global Administrator sprawl. Evaluates whether PIM (Privileged Identity Management) eligible assignments are in use. .PARAMETER MaxGlobalAdmins Maximum acceptable number of Global Administrators before flagging. Defaults to 4. .EXAMPLE Get-EntraPrivilegedRoleReview -MaxGlobalAdmins 2 #> [CmdletBinding()] param( [Parameter()] [ValidateRange(1, 20)] [int]$MaxGlobalAdmins = 4 ) begin { Test-GraphConnection $results = [System.Collections.Generic.List[PSObject]]::new() $highPrivRoles = @( 'Global Administrator', 'Privileged Role Administrator', 'Privileged Authentication Administrator', 'Exchange Administrator', 'SharePoint Administrator', 'User Administrator', 'Application Administrator', 'Cloud Application Administrator', 'Authentication Administrator', 'Intune Administrator' ) } process { # Get all directory roles that have been activated $directoryRoles = Get-MgDirectoryRole -All $assignmentMap = @{} # Track per-user role accumulation foreach ($role in $directoryRoles) { $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id -All $isHighPriv = $role.DisplayName -in $highPrivRoles foreach ($member in $members) { $memberId = $member.Id $memberName = $member.AdditionalProperties.displayName $memberUpn = $member.AdditionalProperties.userPrincipalName $memberType = $member.AdditionalProperties.'@odata.type' -replace '#microsoft.graph.', '' # Track role accumulation if (-not $assignmentMap.ContainsKey($memberId)) { $assignmentMap[$memberId] = @{ Name = $memberName UPN = $memberUpn Type = $memberType Roles = @() } } $assignmentMap[$memberId].Roles += $role.DisplayName $findings = @() if ($isHighPriv) { $findings += 'HIGH-PRIVILEGE ROLE' } if ($memberType -eq 'servicePrincipal') { $findings += 'SERVICE PRINCIPAL IN ROLE' } $results.Add([PSCustomObject]@{ RoleName = $role.DisplayName MemberName = $memberName MemberUPN = $memberUpn MemberType = $memberType IsHighPriv = $isHighPriv AssignmentType = 'Permanent (Active)' Finding = if ($findings.Count -gt 0) { $findings -join ' | ' } else { 'OK' } }) } } # Check for PIM eligible assignments if available try { $eligibleAssignments = Get-MgRoleManagementDirectoryRoleEligibilitySchedule -All -ErrorAction Stop foreach ($assignment in $eligibleAssignments) { $roleDef = Get-MgRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $assignment.RoleDefinitionId -ErrorAction SilentlyContinue $principal = Get-MgDirectoryObject -DirectoryObjectId $assignment.PrincipalId -ErrorAction SilentlyContinue $results.Add([PSCustomObject]@{ RoleName = $roleDef.DisplayName MemberName = $principal.AdditionalProperties.displayName MemberUPN = $principal.AdditionalProperties.userPrincipalName MemberType = ($principal.AdditionalProperties.'@odata.type' -replace '#microsoft.graph.', '') IsHighPriv = $roleDef.DisplayName -in $highPrivRoles AssignmentType = 'PIM Eligible' Finding = 'PIM Eligible (Good)' }) } } catch { Write-Verbose "PIM not available or insufficient permissions to read eligible assignments." } # Flag Global Admin sprawl $globalAdmins = $results | Where-Object { $_.RoleName -eq 'Global Administrator' -and $_.AssignmentType -eq 'Permanent (Active)' } if ($globalAdmins.Count -gt $MaxGlobalAdmins) { Write-Warning "FINDING: $($globalAdmins.Count) permanent Global Administrators (recommended max: $MaxGlobalAdmins)" } # Flag users with multiple privileged roles foreach ($entry in $assignmentMap.GetEnumerator()) { $privRoles = $entry.Value.Roles | Where-Object { $_ -in $highPrivRoles } if ($privRoles.Count -gt 2) { Write-Warning "FINDING: $($entry.Value.Name) holds $($privRoles.Count) privileged roles: $($privRoles -join ', ')" } } } end { $flagged = $results | Where-Object { $_.Finding -ne 'OK' -and $_.Finding -ne 'PIM Eligible (Good)' } Write-Host " Role assignments reviewed: $($results.Count) | Findings: $($flagged.Count)" -ForegroundColor Gray $results | Sort-Object RoleName, MemberName } } |