Get-IntuneRbac.ps1
|
<#PSScriptInfo .VERSION 1.2.0 .GUID 415ccc16-75d1-4f6c-99e6-78b167580c95 .AUTHOR Jeff Gilbert .COMPANYNAME Community .EXTERNALMODULEDEPENDENCIES Microsoft.Graph.Authentication .COPYRIGHT Community Script .TAGS Intune .RELEASENOTES 10.19.2026 1.0.3 Initial release of Get-IntuneRBAC script. 10.20.2026 1.1.0 Added Microsoft.Graph.Authentication module check and improved connection handling. 10.21.2026 1.2.0 Updated script to include group names before Ids in all the RBAC reports. #> <# .SYNOPSIS Generates a comprehensive Intune RBAC inventory and delegated administration report from Microsoft Graph. .DESCRIPTION This script performs a comprehensive inventory of Microsoft Intune Role-Based Access Control (RBAC) by querying Microsoft Graph and exporting the results into multiple reporting formats. Intune environments often contain dozens of custom roles, assignments, scope tags, and administrative groups that can be difficult to document and audit manually. This tool provides administrators with a complete view of who has access to what within Intune, helping improve governance, security, troubleshooting, and compliance efforts. The script collects and correlates: • Role Definitions Retrieves all built-in and custom Intune roles, including descriptions and role identifiers. • Role Permissions Enumerates every permitted resource action assigned to each role, providing visibility into the exact administrative capabilities granted. • Role Assignments Documents all RBAC assignments, including group names and IDs for members and scopes, assigned roles, and scope tag names and IDs. • Scope Tags Inventories all Intune scope tags used for delegated administration and administrative boundaries. • Assignment Relationships Maps the relationship between administrative groups, assigned roles, scope groups, and scope tag names and IDs to show how access is delegated across the environment. Generated reports can be used to: • Audit administrative privileges and delegated access • Validate least-privilege RBAC implementations • Identify excessive or unnecessary permissions • Document Intune administrative models for compliance reviews • Support security assessments and access reviews • Troubleshoot unexpected administrator access or permission issues • Create change baselines before RBAC modifications • Accelerate tenant migrations and environment handoffs Output includes both human-readable CSV reports and a JSON export suitable for archival, automation, governance tooling, or integration with reporting platforms such as Power BI. Reports generated: • ScopeTags.csv Inventory of all Intune scope tags. • RoleDefinitions.csv Inventory of all built-in and custom Intune role definitions. • RolePermissions.csv Detailed mapping of roles to Microsoft Intune resource actions. • RoleAssignments.csv Comprehensive view of RBAC assignments, members, scopes, and scope tags. • RBAC-Roles-Assignments-Groups.csv Relationship report showing delegated administration paths between groups, roles, and scopes. • IntuneRBAC.json Full RBAC configuration export for backup, analysis, or automation scenarios. This script is particularly useful for Intune administrators, security teams, auditors, consultants, and managed service providers that need a complete understanding of delegated administration within a Microsoft Intune tenant. .NOTES Required PowerShell Module: Microsoft.Graph.Authentication Install with: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser Required Microsoft Graph Permissions: DeviceManagementRBAC.Read.All Group.Read.All Directory.Read.All #> [CmdletBinding()] param( [string]$OutputPath = ".\IntuneRBAC" ) if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication)) { throw "The Microsoft.Graph.Authentication module is required. Install it with: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser" } #region Functions function Write-Log { param([string]$Message) Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message" } function Invoke-GraphPaged { param([string]$Uri) while ($Uri) { $Response = Invoke-MgGraphRequest -Method GET -Uri $Uri if ($Response.ContainsKey('value')) { $Response.value } else { $Response } $Uri = $Response.'@odata.nextLink' } } $GraphBase = "https://graph.microsoft.com/beta" #endregion #region Connect if (-not (Get-MgContext)) { Write-Log "Connecting to Microsoft Graph..." Connect-MgGraph ` -Scopes @( "DeviceManagementRBAC.Read.All", "Group.Read.All", "Directory.Read.All" ) ` -NoWelcome } #endregion #region Output Folder $OutputPath = $OutputPath.TrimEnd('\') + "-$((Get-Date).ToString('yyyyMMdd'))" if (-not (Test-Path $OutputPath)) { New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null } #endregion Write-Log "Collecting Intune RBAC data:" #region Scope Tags Write-Log " Retrieving Scope Tags.........ScopeTags.csv" $ScopeTags = @(Invoke-GraphPaged "$GraphBase/deviceManagement/roleScopeTags") $ScopeTagNameById = @{} foreach ($ScopeTag in $ScopeTags) { $ScopeTagNameById[$ScopeTag.id] = $ScopeTag.displayName } $ScopeTags | ForEach-Object { [pscustomobject]@{ DisplayName = $_.displayName; Id = $_.id; Description = $_.description } } | Export-Csv ` "$OutputPath\ScopeTags.csv" ` -NoTypeInformation #endregion #region Role Definitions Write-Log " Retrieving Role Definitions...RoleDefinitions.csv" $RoleDefinitions = @(Invoke-GraphPaged "$GraphBase/deviceManagement/roleDefinitions") $RoleInventory = foreach ($Role in $RoleDefinitions) { [PSCustomObject]@{ RoleName = $Role.displayName RoleId = $Role.id Description = $Role.description BuiltIn = $Role.isBuiltInRoleDefinition } } $RoleInventory | Export-Csv ` "$OutputPath\RoleDefinitions.csv" ` -NoTypeInformation #endregion #region Role Permissions Write-Log " Retrieving Role Permissions...RolePermissions.csv" $PermissionInventory = foreach ($Role in $RoleDefinitions) { $RoleDetail = Invoke-MgGraphRequest -Method GET ` -Uri "$GraphBase/deviceManagement/roleDefinitions/$($Role.id)" foreach ($RolePermission in $RoleDetail.rolePermissions) { foreach ($Action in $RolePermission.resourceActions) { foreach ($AllowedAction in $Action.allowedResourceActions) { [PSCustomObject]@{ RoleName = $Role.displayName RoleId = $Role.id ResourceAction = $AllowedAction } } } } } $PermissionInventory | Export-Csv ` "$OutputPath\RolePermissions.csv" ` -NoTypeInformation #endregion #region Assignments Write-Log " Retrieving Role Assignments...RoleAssignments.csv" function Get-ScopeGroupIds($Assignment) { @($Assignment.scopeMembers) + @($Assignment.resourceScopes) | Where-Object { $_ } | Select-Object -Unique } function Get-GroupNames($Ids) { ($Ids | ForEach-Object { $GroupId = $_ try { (Invoke-MgGraphRequest -Method GET -Uri "$GraphBase/groups/${GroupId}?`$select=id,displayName" -ErrorAction Stop).displayName } catch { $GroupId } }) -join ";" } function Get-ScopeTagNames($Ids) { ($Ids | ForEach-Object { $ScopeTagId = "$_" if ($ScopeTagNameById.ContainsKey($ScopeTagId)) { $ScopeTagNameById[$ScopeTagId] } else { $ScopeTagId } }) -join ";" } $AssignmentList = @(Invoke-GraphPaged "$GraphBase/deviceManagement/roleAssignments") # $expand is ignored on the list call, so fetch each assignment individually to get its role definition $Assignments = foreach ($Item in $AssignmentList) { Invoke-MgGraphRequest -Method GET ` -Uri "$GraphBase/deviceManagement/roleAssignments/$($Item.id)?`$expand=roleDefinition" } $AssignmentInventory = foreach ($Assignment in $Assignments) { $ScopeIds = Get-ScopeGroupIds $Assignment [PSCustomObject]@{ AssignmentName = $Assignment.displayName AssignmentId = $Assignment.id RoleName = $Assignment.roleDefinition.displayName RoleId = $Assignment.roleDefinition.id ScopeType = $Assignment.scopeType MemberGroupNames = (Get-GroupNames $Assignment.members) MemberGroupIds = ($Assignment.members -join ";") ScopeGroups = (Get-GroupNames $ScopeIds) ScopeGroupIds = ($ScopeIds -join ";") ScopeTagNames = (Get-ScopeTagNames $Assignment.roleScopeTagIds) ScopeTagIds = ($Assignment.roleScopeTagIds -join ";") } } $AssignmentInventory | Export-Csv ` "$OutputPath\RoleAssignments.csv" ` -NoTypeInformation #endregion #region Relationship Report Write-Log " Building relationship report..RBAC-Roles-Assignments-Groups.csv" $RelationshipReport = foreach ($Assignment in $Assignments) { $Role = $Assignment.roleDefinition $ScopeIds = Get-ScopeGroupIds $Assignment $ScopeNames = Get-GroupNames $ScopeIds $ScopeTagNames = Get-ScopeTagNames $Assignment.roleScopeTagIds foreach ($Member in $Assignment.members) { try { $Group = Invoke-MgGraphRequest -Method GET ` -Uri "$GraphBase/groups/${Member}?`$select=id,displayName" -ErrorAction Stop [PSCustomObject]@{ AssignmentName = $Assignment.displayName AssignmentId = $Assignment.id RoleName = $Role.displayName RoleId = $Role.id AdminGroupName = $Group.DisplayName AdminGroupId = $Group.Id ScopeGroups = $ScopeNames ScopeGroupIds = ($ScopeIds -join ";") ScopeTagNames = $ScopeTagNames ScopeTagIds = ($Assignment.roleScopeTagIds -join ";") } } catch { [PSCustomObject]@{ AssignmentName = $Assignment.displayName AssignmentId = $Assignment.id RoleName = $Role.displayName RoleId = $Role.id AdminGroupName = "<Unable to Resolve>" AdminGroupId = $Member ScopeGroups = $ScopeNames ScopeGroupIds = ($ScopeIds -join ";") ScopeTagNames = $ScopeTagNames ScopeTagIds = ($Assignment.roleScopeTagIds -join ";") } } } } $RelationshipReport | Export-Csv ` "$OutputPath\RBAC-Roles-Assignments-Groups.csv" ` -NoTypeInformation #endregion #region JSON Export Write-Log " Creating JSON export..........IntuneRBAC.json" Write-Log " Building relationship report..RBAC-Roles-Assignments-Groups.csv" $ExportObject = [ordered]@{ ExportDate = Get-Date ScopeTags = @($ScopeTags | ForEach-Object { $ScopeTag = [ordered]@{ displayName = $_.displayName id = $_.id } foreach ($Key in $_.Keys) { if ($Key -notin @('displayName', 'id')) { $ScopeTag[$Key] = $_[$Key] } } $ScopeTag }) RoleDefinitions= @($RoleDefinitions | ForEach-Object { $RoleDefinition = [ordered]@{ RoleName = $_.displayName id = $_.id } foreach ($Key in $_.Keys) { if ($Key -notin @('displayName', 'id')) { $RoleDefinition[$Key] = $_[$Key] } } $RoleDefinition }) Assignments = @($Assignments | ForEach-Object { $Assignment = $_ $AssignmentExport = [ordered]@{ displayName = $Assignment.displayName id = $Assignment.id roleDefinition = [ordered]@{ displayName = $Assignment.roleDefinition.displayName id = $Assignment.roleDefinition.id } MemberGroupNames = Get-GroupNames $Assignment.members members = @($Assignment.members) ScopeGroups = Get-GroupNames (Get-ScopeGroupIds $Assignment) ScopeGroupIds = @(Get-ScopeGroupIds $Assignment) ScopeTagNames = Get-ScopeTagNames $Assignment.roleScopeTagIds roleScopeTagIds = @($Assignment.roleScopeTagIds) } foreach ($Key in $Assignment.Keys) { if ($Key -notin @('displayName', 'id', 'roleDefinition', 'members', 'scopeMembers', 'resourceScopes', 'roleScopeTagIds')) { $AssignmentExport[$Key] = $Assignment[$Key] } } foreach ($Key in $Assignment.roleDefinition.Keys) { if ($Key -notin @('displayName', 'id')) { $AssignmentExport.roleDefinition[$Key] = $Assignment.roleDefinition[$Key] } } $AssignmentExport }) } $ExportObject | ConvertTo-Json -Depth 25 | Out-File "$OutputPath\IntuneRBAC.json" #endregion #region Summary $Summary = [PSCustomObject]@{ ScopeTags = $ScopeTags.Count Roles = $RoleDefinitions.Count Assignments = $Assignments.Count ExportLocation = (Resolve-Path $OutputPath).Path } $Summary | Format-List Write-Log "Inventory complete" Write-Log "Disconnecting from Microsoft Graph" $null = Disconnect-MgGraph #endregion |