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