Public/Get-IntuneAdminAccess.ps1
|
function Get-IntuneAdminAccess { <# .SYNOPSIS Explains the Intune RBAC access associated with an administrator. .DESCRIPTION Correlates the target user, group membership, role assignments, role definitions, scope groups and scope tags. Each permission retains its granting evidence. .PARAMETER UserPrincipalName The administrator's Microsoft Entra user principal name. .PARAMETER UserId The administrator's Microsoft Entra object ID. .EXAMPLE Get-IntuneAdminAccess -UserPrincipalName 'admin@contoso.com' .EXAMPLE Get-IntuneAdminAccess -UserId '00000000-0000-0000-0000-000000000000' #> [CmdletBinding(DefaultParameterSetName = 'ByUpn')] param( [Parameter(Mandatory, ParameterSetName = 'ByUpn')] [ValidateNotNullOrEmpty()] [string] $UserPrincipalName, [Parameter(Mandatory, ParameterSetName = 'ById')] [guid] $UserId ) $analysisScopes = @('User.Read', 'User.Read.All', 'GroupMember.Read.All', 'DeviceManagementRBAC.Read.All') $context = Assert-IntuneAccessConnection -RequiredScope $analysisScopes $user = if ($PSCmdlet.ParameterSetName -eq 'ById') { Resolve-IntuneAccessUser -UserId $UserId } else { Resolve-IntuneAccessUser -UserPrincipalName $UserPrincipalName } $tenant = Get-IntuneAccessTenant $memberships = @(Get-IntuneAccessGroupMembership -UserId ([guid] $user.Id)) $membershipById = @{} foreach ($membership in $memberships) { $membershipById[$membership.Id] = $membership } $warnings = [System.Collections.Generic.List[string]]::new() $definitions = @(Get-IntuneAccessRoleDefinitions) foreach ($definition in $definitions) { if ([string]::IsNullOrWhiteSpace([string] $definition.Id)) { $warnings.Add('A role definition did not return an ID. Its assignments could not be evaluated.') } if ((Get-IntuneAccessProperty $definition 'PermissionDataState' 'Available') -eq 'Missing') { $warnings.Add("Role '$($definition.DisplayName)' did not return rolePermissions. No permission conclusion was inferred from that role.") } } $allAssignments = @(Get-IntuneAccessRoleAssignments -RoleDefinition $definitions -ResolveNames) $relevantAssignments = [System.Collections.Generic.List[object]]::new() foreach ($assignment in $allAssignments) { if ([string]::IsNullOrWhiteSpace([string] $assignment.Id)) { $warnings.Add("Role '$($assignment.RoleDefinition.DisplayName)' returned an assignment without an ID. Its raw relationships are retained, but the source cannot be uniquely identified.") } if ((Get-IntuneAccessProperty $assignment 'AdminGroupDataState' 'Available') -eq 'Missing') { $warnings.Add("Assignment '$($assignment.Name)' did not return the members property. Administrator-group applicability could not be evaluated for that assignment.") } } foreach ($assignment in $allAssignments) { $matchingMemberships = [System.Collections.Generic.List[object]]::new() foreach ($adminGroupId in @($assignment.RawIds.AdminGroupIds)) { if ($membershipById.ContainsKey($adminGroupId)) { $matchingMemberships.Add($membershipById[$adminGroupId]) } } if ($matchingMemberships.Count -eq 0) { continue } $hasDirectMembership = @($matchingMemberships | Where-Object MembershipType -EQ 'Direct').Count -gt 0 $assignment.Applicability = if ($hasDirectMembership) { 'Confirmed' } else { 'NotEvaluated' } $assignment.AdminGroupEvidence = @($matchingMemberships | ForEach-Object { [PSCustomObject] @{ GroupId = $_.Id GroupName = $_.DisplayName MembershipType = $_.MembershipType EvidenceState = if ($_.MembershipType -eq 'Direct') { 'Observed' } else { 'ObservedNested' } } }) if (-not $hasDirectMembership) { $warnings.Add("Assignment '$($assignment.Name)' matched only through nested administrator-group membership. Intune's licence-dependent nested-group behaviour was not evaluated, so its permissions are marked NotEvaluated.") } if ((Get-IntuneAccessProperty $assignment 'ScopeTagDataState' 'Available') -eq 'Missing') { $warnings.Add("Assignment '$($assignment.Name)' did not return the beta roleScopeTagIds property. Scope-tag applicability is NotEvaluated; an empty value was not assumed.") } if ((Get-IntuneAccessProperty $assignment 'ScopeTypeDataState' 'Available') -eq 'Missing' -or (Get-IntuneAccessProperty $assignment 'ScopeGroupDataState' 'Available') -eq 'Missing') { $warnings.Add("Assignment '$($assignment.Name)' returned incomplete scope-group data. Resource scope applicability is NotEvaluated where that data is required.") } if ($assignment.ScopeType -notin @('resourceScope', 'allDevices', 'allLicensedUsers', 'allDevicesAndLicensedUsers')) { $warnings.Add("Assignment '$($assignment.Name)' returned scopeType '$($assignment.ScopeType)', which this version does not evaluate.") } foreach ($group in @($assignment.AdminGroups) + @($assignment.ScopeGroups)) { if ((Get-IntuneAccessProperty $group 'ResolutionState' 'Resolved') -eq 'Unresolved') { $warnings.Add("Group '$($group.Id)' on assignment '$($assignment.Name)' could not be resolved. The raw ID was retained.") } } foreach ($tag in @($assignment.ScopeTags)) { if ($tag.DisplayName -eq '[Unresolved scope tag]') { $warnings.Add("Scope tag '$($tag.Id)' on assignment '$($assignment.Name)' could not be resolved. The raw ID was retained.") } } if (@($assignment.RawIds.ScopeMemberIds).Count -gt 0 -and (@($assignment.RawIds.ScopeMemberIds) -join ',') -ne (@($assignment.RawIds.ScopeGroupIds) -join ',')) { $warnings.Add("Assignment '$($assignment.Name)' returned beta scopeMembers data that differs from v1-compatible resourceScopes. Both raw ID sets were retained; resourceScopes is used as Scope (Groups).") } $relevantAssignments.Add($assignment) } if ($relevantAssignments.Count -eq 0) { $warnings.Add('No matching Intune RBAC role assignment was found for the observed group memberships.') } $warnings.Add('The tenant setting for the March 2026 Scoped permissions preview is not exposed by a documented Graph contract used here. Permission sources are retained per assignment; cross-tag permission merging is not asserted.') $warnings.Add('Hidden Microsoft Entra group membership is not evaluated because Member.Read.Hidden is not requested by the default connection. An assignment through a hidden Admin Group might be absent from this result.') $warnings.Add('Microsoft Entra administrative roles are outside the Intune RBAC model evaluated by this command. They can provide additional Intune access that is not shown here.') $effectivePermissions = @(Resolve-IntuneAccessPermissions -RoleAssignment $relevantAssignments.ToArray()) $scopeGroups = @($relevantAssignments | ForEach-Object ScopeGroups | Group-Object Id | ForEach-Object { $_.Group[0] }) $scopeTags = @($relevantAssignments | ForEach-Object ScopeTags | Group-Object Id | ForEach-Object { $_.Group[0] }) $adminGroups = @($relevantAssignments | ForEach-Object AdminGroups | Group-Object Id | ForEach-Object { $_.Group[0] }) $evidence = @($relevantAssignments | ForEach-Object { $assignment = $_ foreach ($group in @($assignment.AdminGroupEvidence)) { [PSCustomObject] @{ UserId = $user.Id UserPrincipalName = $user.UserPrincipalName AdminGroupId = $group.GroupId AdminGroupName = $group.GroupName MembershipType = $group.MembershipType RoleAssignmentId = $assignment.Id RoleAssignmentName = $assignment.Name RoleDefinitionId = $assignment.RoleDefinition.Id RoleDefinitionName = $assignment.RoleDefinition.DisplayName Applicability = $assignment.Applicability } } }) [PSCustomObject] @{ PSTypeName = 'IntuneAccess.AdminAccess' User = $user Tenant = $tenant RoleAssignments = $relevantAssignments.ToArray() EffectivePermissions = $effectivePermissions AdminGroups = $adminGroups ScopeGroups = $scopeGroups ScopeTags = $scopeTags Warnings = $warnings.ToArray() Evidence = $evidence PermissionModel = [PSCustomObject] @{ ScopedPermissionsMode = 'Unknown' UnionRule = 'Confirmed assignments are unioned by exact Graph action; evidence stays assignment-specific.' DenyRule = 'No cross-assignment deny rule is inferred.' } GraphPermissionsUsed = $analysisScopes GraphPermissionsGranted = @($context.Scopes) GeneratedAt = [DateTimeOffset]::Now ToolVersion = $script:IntuneAccessVersion } } |