Get Intune Role Assignments
This script connects to Microsoft Graph to retrieve all Intune role definitions and their assignments, providing a clear view of who has administrative access to Intune. It shows both built-in and custom roles, the assigned users/groups, assignment dates, and scopes. Perfect for security audits and access reviews.
Quality checks
All checks pass- ParsePass
- LintPass
- MetadataPass
- Runbook-readyPass
- Module depsPass
Tests run automatically on every change. What does each check mean?
Required Permissions
DeviceManagementRBAC.Read.AllUser.Read.AllGroup.Read.AllAllows the app to read group properties and memberships, and read conversations for all groups, without a signed-in user.
<#
.TITLE
Get Intune Role Assignments
.SYNOPSIS
Lists all Intune role assignments showing who has which roles for security auditing.
.DESCRIPTION
This script connects to Microsoft Graph to retrieve all Intune role definitions
and their assignments, providing a clear view of who has administrative access
to Intune. It shows both built-in and custom roles, the assigned users/groups,
assignment dates, and scopes. Perfect for security audits and access reviews.
.TAGS
Security
.MINROLE
Intune Administrator
.PERMISSIONS
DeviceManagementRBAC.Read.All,User.Read.All,Group.Read.All
.AUTHOR
Ugur Koc
.VERSION
1.0
.CHANGELOG
1.0 - Initial release
.LASTUPDATE
2025-06-23
.EXAMPLE
.\get-intune-role-assignments.ps1
Shows all Intune role assignments
.EXAMPLE
.\get-intune-role-assignments.ps1 -ShowEmptyRoles
Shows all roles including those with no current assignments
.EXAMPLE
.\get-intune-role-assignments.ps1 -ExportToCsv
Exports the role assignments report to a CSV file
.NOTES
- Requires Microsoft.Graph.Authentication module
- Shows both built-in and custom Intune roles
- Resolves user and group names for assignments
- Assignment dates may not be available for older assignments
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false, HelpMessage = "Show roles with no assignments")]
[switch]$ShowEmptyRoles,
[Parameter(Mandatory = $false, HelpMessage = "Export results to CSV")]
[switch]$ExportToCsv,
[Parameter(Mandatory = $false, HelpMessage = "Output path for exports")]
[string]$OutputPath = ".",
[Parameter(Mandatory = $false, HelpMessage = "Force module installation without prompting")]
[switch]$ForceModuleInstall
)
# ============================================================================
# ENVIRONMENT DETECTION AND SETUP
# ============================================================================
function Initialize-RequiredModule {
param(
[string[]]$ModuleNames,
[bool]$IsAutomationEnvironment,
[bool]$ForceInstall = $false
)
foreach ($ModuleName in $ModuleNames) {
Write-Verbose "Checking module: $ModuleName"
$module = Get-Module -ListAvailable -Name $ModuleName | Select-Object -First 1
if (-not $module) {
if ($IsAutomationEnvironment) {
throw "Module '$ModuleName' is not available in Azure Automation"
}
else {
Write-Information "Module '$ModuleName' not found. Installing..." -InformationAction Continue
if (-not $ForceInstall) {
$response = Read-Host "Install module '$ModuleName'? (Y/N)"
if ($response -notmatch '^[Yy]') {
throw "Module '$ModuleName' is required but installation was declined."
}
}
try {
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
$scope = if ($isAdmin) { "AllUsers" } else { "CurrentUser" }
Install-Module -Name $ModuleName -Scope $scope -Force -AllowClobber -Repository PSGallery
Write-Information "✓ Successfully installed '$ModuleName'" -InformationAction Continue
}
catch {
throw "Failed to install module '$ModuleName': $($_.Exception.Message)"
}
}
}
Import-Module -Name $ModuleName -Force -ErrorAction Stop
}
}
# Detect execution environment
$IsAzureAutomation = $null -ne $PSPrivateMetadata.JobId.Guid
# Initialize required modules
$RequiredModules = @("Microsoft.Graph.Authentication")
try {
Initialize-RequiredModule -ModuleNames $RequiredModules -IsAutomationEnvironment $IsAzureAutomation -ForceInstall $ForceModuleInstall
Write-Verbose "✓ All required modules are available"
}
catch {
Write-Error "Module initialization failed: $_"
exit 1
}
# ============================================================================
# AUTHENTICATION
# ============================================================================
try {
if ($IsAzureAutomation) {
Write-Output "Connecting to Microsoft Graph using Managed Identity..."
Connect-MgGraph -Identity -NoWelcome -ErrorAction Stop
}
else {
Write-Information "Connecting to Microsoft Graph..." -InformationAction Continue
$Scopes = @(
"DeviceManagementRBAC.Read.All",
"User.Read.All",
"Group.Read.All"
)
Connect-MgGraph -Scopes $Scopes -NoWelcome -ErrorAction Stop
}
Write-Information "✓ Successfully connected to Microsoft Graph" -InformationAction Continue
}
catch {
Write-Error "Failed to connect to Microsoft Graph: $($_.Exception.Message)"
exit 1
}
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
function Get-MgGraphAllPage {
param(
[string]$Uri,
[int]$DelayMs = 100
)
$allResults = @()
$nextLink = $Uri
do {
try {
if ($allResults.Count -gt 0) {
Start-Sleep -Milliseconds $DelayMs
}
$response = Invoke-MgGraphRequest -Uri $nextLink -Method GET
if ($response.value) {
$allResults += $response.value
}
else {
$allResults += $response
}
$nextLink = $response.'@odata.nextLink'
}
catch {
if ($_.Exception.Message -like "*429*") {
Write-Information "Rate limit hit, waiting 60 seconds..." -InformationAction Continue
Start-Sleep -Seconds 60
continue
}
Write-Warning "Error fetching data: $($_.Exception.Message)"
break
}
} while ($nextLink)
return $allResults
}
function Get-PrincipalName {
param(
[string]$PrincipalId,
[string]$PrincipalType
)
try {
if ($PrincipalType -eq "user") {
$uri = "https://graph.microsoft.com/v1.0/users/$PrincipalId"
$principal = Invoke-MgGraphRequest -Uri $uri -Method GET
return @{
DisplayName = $principal.displayName
Email = $principal.userPrincipalName
Type = "User"
}
}
elseif ($PrincipalType -eq "group") {
$uri = "https://graph.microsoft.com/v1.0/groups/$PrincipalId"
$principal = Invoke-MgGraphRequest -Uri $uri -Method GET
return @{
DisplayName = $principal.displayName
Email = $principal.mail
Type = "Group"
}
}
else {
return @{
DisplayName = $PrincipalId
Email = ""
Type = "Unknown"
}
}
}
catch {
Write-Verbose "Could not resolve principal ${PrincipalId}: $($_.Exception.Message)"
return @{
DisplayName = $PrincipalId
Email = ""
Type = $PrincipalType
}
}
}
# ============================================================================
# MAIN SCRIPT LOGIC
# ============================================================================
try {
Write-Information "Retrieving Intune role definitions..." -InformationAction Continue
# Get all role definitions first
$roleDefinitionsUri = "https://graph.microsoft.com/v1.0/deviceManagement/roleDefinitions"
$roleDefinitions = Get-MgGraphAllPage -Uri $roleDefinitionsUri
Write-Information "✓ Found $($roleDefinitions.Count) role definitions" -InformationAction Continue
# Create role lookup table
$roleLookup = @{}
foreach ($role in $roleDefinitions) {
$roleLookup[$role.id] = $role
}
# Get all role assignments directly
Write-Information "Retrieving role assignments..." -InformationAction Continue
$roleAssignmentsUri = "https://graph.microsoft.com/v1.0/deviceManagement/roleAssignments"
$roleAssignments = Get-MgGraphAllPage -Uri $roleAssignmentsUri
Write-Information "✓ Found $($roleAssignments.Count) role assignments" -InformationAction Continue
# Process assignments
[System.Collections.Generic.List[Object]]$allAssignments = @()
$totalAssignments = 0
$rolesWithAssignments = 0
$processedRoles = @{}
Write-Information "Processing assignments..." -InformationAction Continue
foreach ($assignment in $roleAssignments) {
Write-Verbose "Processing assignment: $($assignment.displayName)"
# For simplified version, we'll show assignments without linking to role definitions
# since the API doesn't provide a direct link
# Create assignment record
$assignmentRecord = @{
RoleId = ""
RoleName = "Unknown Role" # We'll update this if we can determine it
RoleType = "Assignment"
Description = $assignment.description
AssignmentId = $assignment.id
AssignmentName = $assignment.displayName
Scope = if ($assignment.resourceScopes) { $assignment.resourceScopes -join "; " } else { "All" }
Members = @()
}
# Process members
if ($assignment.members) {
foreach ($memberId in $assignment.members) {
# First try as user, then as group
$principalInfo = Get-PrincipalName -PrincipalId $memberId -PrincipalType "user"
# If user lookup failed, try as group
if ($principalInfo.DisplayName -eq $memberId) {
$groupInfo = Get-PrincipalName -PrincipalId $memberId -PrincipalType "group"
if ($groupInfo.DisplayName -ne $memberId) {
$principalInfo = $groupInfo
}
}
$assignmentRecord.Members += $principalInfo
}
}
$allAssignments.Add($assignmentRecord)
$totalAssignments++
}
# Add roles without assignments if ShowEmptyRoles is specified
if ($ShowEmptyRoles) {
foreach ($role in $roleDefinitions) {
if (-not $processedRoles.ContainsKey($role.id)) {
$allAssignments += @{
RoleId = $role.id
RoleName = $role.displayName
RoleType = if ($role.isBuiltIn) { "Built-in" } else { "Custom" }
Description = $role.description
AssignmentId = ""
AssignmentName = "No assignments"
Scope = ""
Members = @()
}
}
}
}
# Count unique roles with assignments
$rolesWithAssignments = $processedRoles.Count
# Display results
Write-Information "`n🔐 INTUNE ROLE ASSIGNMENTS REPORT" -InformationAction Continue
Write-Information ("=" * 50) -InformationAction Continue
Write-Information "Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -InformationAction Continue
Write-Information ("=" * 50) -InformationAction Continue
# Group by role for display
$groupedAssignments = $allAssignments | Group-Object -Property RoleName
foreach ($roleGroup in $groupedAssignments | Sort-Object Name) {
$firstAssignment = $roleGroup.Group[0]
$roleColor = if ($firstAssignment.RoleType -eq "Built-in") { "Cyan" } else { "Yellow" }
Write-Information "`n[$($firstAssignment.RoleType)] $($roleGroup.Name)" -InformationAction Continue
if ($firstAssignment.Description) {
Write-Information " Description: $($firstAssignment.Description)" -InformationAction Continue
}
foreach ($assignment in $roleGroup.Group) {
if ($assignment.AssignmentName -ne "No assignments") {
Write-Information " Assignment: $($assignment.AssignmentName)" -InformationAction Continue
if ($assignment.Members.Count -gt 0) {
foreach ($member in $assignment.Members) {
$memberInfo = " • $($member.DisplayName) "
if ($member.Email) {
$memberInfo += "($($member.Email)) "
}
$memberInfo += "- $($member.Type)"
Write-Information $memberInfo -InformationAction Continue
}
}
else {
Write-Information " • Direct assignment (check portal for members)" -InformationAction Continue
}
if ($assignment.Scope) {
Write-Information " Scope: $($assignment.Scope)" -InformationAction Continue
}
}
else {
Write-Information " • No current assignments" -InformationAction Continue
}
}
}
# Summary
Write-Information "`n" -InformationAction Continue
Write-Information ("=" * 50) -InformationAction Continue
Write-Information "Summary: $($roleDefinitions.Count) roles, $rolesWithAssignments roles with assignments, $totalAssignments total assignments" -InformationAction Continue
Write-Information ("=" * 50) -InformationAction Continue
# Export to CSV if requested
if ($ExportToCsv) {
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$csvPath = Join-Path $OutputPath "Intune_Role_Assignments_$timestamp.csv"
# Flatten the data for CSV export
[System.Collections.Generic.List[Object]]$csvData = @()
foreach ($assignment in $allAssignments) {
if ($assignment.Members.Count -gt 0) {
foreach ($member in $assignment.Members) {
$csvData.Add([PSCustomObject]@{
RoleName = $assignment.RoleName
RoleType = $assignment.RoleType
AssignmentName = $assignment.AssignmentName
MemberName = $member.DisplayName
MemberEmail = $member.Email
MemberType = $member.Type
Scope = $assignment.Scope
})
}
}
else {
$csvData.Add([PSCustomObject]@{
RoleName = $assignment.RoleName
RoleType = $assignment.RoleType
AssignmentName = $assignment.AssignmentName
MemberName = "No members"
MemberEmail = ""
MemberType = ""
Scope = $assignment.Scope
})
}
}
$csvData | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
Write-Information "✓ CSV report saved: $csvPath" -InformationAction Continue
}
}
catch {
Write-Error "Script execution failed: $($_.Exception.Message)"
exit 1
}
finally {
try {
$null = Disconnect-MgGraph
Write-Information "✓ Disconnected from Microsoft Graph" -InformationAction Continue
}
catch {
Write-Verbose "Graph disconnection completed"
}
}Related Scripts
Discover similar scripts that might be useful for your automation needs
Multi-Admin Approval Compliance Dashboard Report
This script connects to Microsoft Graph and analyzes Multi-Admin Approval configurations, usage patterns, and compliance metrics across your Intune environment. It generates detailed reports showing MAA coverage gaps, approval statistics, admin permissions, and trends. The script helps organizations ensure proper implementation of MAA controls and identify areas for security improvement. Reports are generated in both HTML and CSV formats for different audiences.
BitLocker Key Storage Checker
This script connects to Microsoft Graph API, retrieves all Windows devices from Intune, and checks if each device has BitLocker recovery keys stored in Entra ID. The script provides detailed reporting on compliance status, identifies devices without stored keys, and exports comprehensive results to CSV format for further analysis. This helps ensure proper BitLocker key escrow for data recovery scenarios.
FileVault Key Storage Checker
This script connects to Microsoft Graph API, retrieves all macOS devices from Intune, and checks if each device has FileVault recovery keys stored in Intune. The script provides detailed reporting on compliance status, identifies devices without stored keys, and exports comprehensive results to CSV format for further analysis. This helps ensure proper FileVault key escrow for data recovery scenarios.