Managing privileged access in Microsoft Entra ID becomes increasingly difficult as organizations scale administrative operations across multiple teams, departments, and security domains. Directly assigning administrative roles to users often leads to:
Role-assignable groups provide a more secure and scalable way to manage privileged access by assigning Microsoft Entra roles to groups instead of individual users.
This Graph PowerShell automation solution helps administrators:
The solution is ideal for:
Try the M365Corner Microsoft 365 Reporting Tool â your DIY pack with 20+ out-of-the-box M365 reports for Users, Groups, and Teams.
Role-assignable groups help organizations centralize privileged access management and reduce administrative complexity across Microsoft Entra ID environments.
Instead of assigning privileged roles directly to users, organizations can:
This supports:
| Direct Role Assignment | Group-Based RBAC |
|---|---|
| Harder to audit | Centralized governance |
| Manual onboarding | Standardized onboarding |
| Increased admin sprawl | Cleaner RBAC structure |
| Difficult lifecycle reviews | Easier access governance |
| Inconsistent assignments | Standardized administration |
Role-assignable groups provide a more governable and scalable RBAC architecture.
Role-assignable groups help organizations implement Zero Trust principles by:
This creates a more secure and auditable Microsoft 365 environment.
Install the Microsoft Graph PowerShell module if it is not already installed:
Install-Module Microsoft.Graph -Scope CurrentUser
Connect to Microsoft Graph with the required permissions:
Connect-MgGraph -Scopes `
"Group.ReadWrite.All",
"RoleManagement.ReadWrite.Directory",
"Directory.ReadWrite.All",
"Mail.Send"
Prepare a CSV file with the following columns:
| GroupName | MailNickname | RoleNames |
|---|---|---|
| PIM-T0-GlobalAdmins | pimt0globaladmins | Global Administrator |
| PIM-T1-ExchangeAdmins | pimt1exchangeadmins | Exchange Administrator |
| PIM-T2-HelpdeskAdmins | pimt2helpdeskadmins | Helpdesk Administrator |
Save the CSV file as:
C:\Reports\RoleAssignableGroups.csv
# Connect to Microsoft Graph
Connect-MgGraph -Scopes `
"Group.ReadWrite.All",
"RoleManagement.ReadWrite.Directory",
"Directory.ReadWrite.All",
"Mail.Send"
# CSV input file
$CsvPath = "C:\Reports\RoleAssignableGroups.csv"
# Export report path
$ReportPath = "C:\Reports\RBACGovernanceReport.csv"
# Email settings
$Sender = "admin@contoso.com"
$Recipient = "securityteam@contoso.com"
# Dry-run mode
$DryRun = $false
# Critical roles
$CriticalRoles = @(
"Global Administrator",
"Privileged Role Administrator",
"Authentication Administrator"
)
# Import CSV
$Entries = Import-Csv $CsvPath
$GovernanceReport = @()
foreach ($Entry in $Entries) {
try {
$GroupName = $Entry.GroupName.Trim()
$MailNickname = $Entry.MailNickname.Trim()
$RoleNames = $Entry.RoleNames.Split(";") | ForEach-Object {
$_.Trim()
}
Write-Host "Processing group: $GroupName" -ForegroundColor Cyan
# Duplicate group detection
$ExistingGroup = Get-MgGroup `
-Filter "displayName eq '$GroupName'"
if ($ExistingGroup) {
$GovernanceReport += [PSCustomObject]@{
GroupName = $GroupName
AssignedRoles = $Entry.RoleNames
Status = "Skipped"
Severity = "Medium"
Risk = "Duplicate Group"
Recommendation = "Review existing RBAC group before creating duplicates"
}
continue
}
# Naming convention validation
if ($GroupName -notmatch "^PIM-T[0-9]-") {
$NamingWarning = "Naming Convention Warning"
}
else {
$NamingWarning = "None"
}
# Excessive role detection
if ($RoleNames.Count -gt 3) {
$RoleThresholdRisk = "Too Many Roles Assigned"
$Severity = "High"
}
else {
$RoleThresholdRisk = "None"
$Severity = "Low"
}
# Dry-run mode
if ($DryRun -eq $true) {
$GovernanceReport += [PSCustomObject]@{
GroupName = $GroupName
AssignedRoles = $Entry.RoleNames
Status = "DryRun"
Severity = $Severity
Risk = "Preview Mode"
Recommendation = "Validation completed successfully"
}
continue
}
# Create role-assignable group
$NewGroup = New-MgGroup `
-DisplayName $GroupName `
-MailEnabled:$false `
-MailNickname $MailNickname `
-SecurityEnabled:$true `
-IsAssignableToRole:$true
Write-Host "Created group: $GroupName" -ForegroundColor Green
$AssignedRoles = @()
foreach ($RoleName in $RoleNames) {
# Validate role template
$RoleTemplate = Get-MgDirectoryRoleTemplate | Where-Object {
$_.DisplayName -eq $RoleName
}
if (-not $RoleTemplate) {
$AssignedRoles += "$RoleName (Invalid Role)"
continue
}
# Activate role if not already active
$DirectoryRole = Get-MgDirectoryRole | Where-Object {
$_.DisplayName -eq $RoleName
}
if (-not $DirectoryRole) {
New-MgDirectoryRole `
-DirectoryRoleTemplateId $RoleTemplate.Id
Start-Sleep -Seconds 5
$DirectoryRole = Get-MgDirectoryRole | Where-Object {
$_.DisplayName -eq $RoleName
}
}
# Assign role to group
New-MgRoleManagementDirectoryRoleAssignment `
-PrincipalId $NewGroup.Id `
-RoleDefinitionId $DirectoryRole.Id `
-DirectoryScopeId "/"
$AssignedRoles += $RoleName
Write-Host "Assigned role: $RoleName" -ForegroundColor Yellow
}
# Critical role detection
$CriticalRoleDetected = (
$AssignedRoles | Where-Object {
$_ -in $CriticalRoles
}
)
if ($CriticalRoleDetected) {
$Severity = "Critical"
}
# Governance score
$GovernanceScore = 100
if ($NamingWarning -ne "None") {
$GovernanceScore -= 20
}
if ($RoleThresholdRisk -ne "None") {
$GovernanceScore -= 30
}
if ($CriticalRoleDetected) {
$GovernanceScore -= 30
}
$GovernanceReport += [PSCustomObject]@{
GroupName = $GroupName
AssignedRoles = $AssignedRoles -join "; "
Status = "Success"
Severity = $Severity
Risk = "$NamingWarning; $RoleThresholdRisk"
GovernanceScore = $GovernanceScore
Recommendation = "Review RBAC assignments periodically"
}
}
catch {
$GovernanceReport += [PSCustomObject]@{
GroupName = $GroupName
AssignedRoles = $Entry.RoleNames
Status = "Failed"
Severity = "High"
Risk = "Validation Failure"
GovernanceScore = 0
Recommendation = $_.Exception.Message
}
Write-Host "Error processing group: $GroupName" -ForegroundColor Red
Write-Host $_.Exception.Message
}
}
# Export governance report
$GovernanceReport | Export-Csv `
-Path $ReportPath `
-NoTypeInformation `
-Encoding UTF8
Write-Host "RBAC governance report exported successfully." -ForegroundColor Green
# Governance statistics
$SuccessfulGroups = (
$GovernanceReport |
Where-Object {
$_.Status -eq "Success"
}
).Count
$CriticalFindings = (
$GovernanceReport |
Where-Object {
$_.Severity -eq "Critical"
}
).Count
$DuplicateGroups = (
$GovernanceReport |
Where-Object {
$_.Risk -match "Duplicate Group"
}
).Count
$Failures = (
$GovernanceReport |
Where-Object {
$_.Status -eq "Failed"
}
).Count
# HTML preview
$HtmlPreview = (
$GovernanceReport |
Select-Object -First 10 |
ConvertTo-Html -Fragment
)
# Email body
$EmailBody = @"
<html>
<body>
<h2>Entra ID RBAC Governance Report</h2>
<p>The automated RBAC governance review has completed successfully.</p>
<ul>
<li>Successfully Created Groups: $SuccessfulGroups</li>
<li>Critical RBAC Findings: $CriticalFindings</li>
<li>Duplicate Groups Detected: $DuplicateGroups</li>
<li>Failures: $Failures</li>
</ul>
<p>Below is a preview of the first 10 processed groups:</p>
$HtmlPreview
</body>
</html>
"@
# Send governance report
$params = @{
message = @{
subject = "Entra ID RBAC Governance Report"
body = @{
contentType = "HTML"
content = $EmailBody
}
toRecipients = @(
@{
emailAddress = @{
address = $Recipient
}
}
)
attachments = @(
@{
"@odata.type" = "#microsoft.graph.fileAttachment"
name = "RBACGovernanceReport.csv"
contentBytes = [System.Convert]::ToBase64String(
[System.IO.File]::ReadAllBytes($ReportPath)
)
}
)
}
saveToSentItems = "true"
}
Send-MgUserMail `
-UserId $Sender `
-BodyParameter $params
Write-Host "RBAC governance report emailed successfully." -ForegroundColor Green
The script imports:
using:
Import-Csv
Each CSV row defines:
to be assigned automatically.
Before creating a new role-assignable group, the script checks whether a group with the same display name already exists.
Duplicate groups are skipped and labeled as:
Duplicate Group
This helps prevent:
The script validates whether group names follow the recommended RBAC naming standard:
PIM-T0-
PIM-T1-
PIM-T2-
This helps organizations standardize:
Each role specified in the CSV file is validated using:
Get-MgDirectoryRoleTemplate
This ensures:
before RBAC assignments occur.
If a Microsoft Entra role has not yet been activated in the tenant, the script automatically activates it using:
New-MgDirectoryRole
This eliminates the need for manual role activation before automation.
The script creates Microsoft Entra security groups with:
-IsAssignableToRole:$true
This enables the groups to receive Microsoft Entra administrative roles.
The script assigns one or more Entra roles to each group using:
New-MgRoleManagementDirectoryRoleAssignment
This enables scalable RBAC onboarding workflows.
The script identifies high-risk privileged roles such as:
Groups containing these roles are automatically labeled with higher governance severity levels.
Each RBAC group receives a Governance Score based on:
This helps administrators prioritize RBAC reviews.
The script:
The report provides:
Standardize onboarding using governed role-assignable groups.
Replace direct role assignments with governed group-based RBAC.
Generate RBAC governance reports for audit and security teams.
You can automate this solution using:
This enables recurring RBAC governance reviews and privileged onboarding workflows.
| Error | Cause | Solution |
|---|---|---|
| Insufficient privileges to complete the operation | Required Microsoft Graph permissions were not granted. | Reconnect using: Connect-MgGraph -Scopes ` "Group.ReadWrite.All", "RoleManagement.ReadWrite.Directory", "Directory.ReadWrite.All", "Mail.Send" and ensure admin consent is granted. |
| Request_BadRequest | The specified role does not exist or is unsupported. | Validate role names in the CSV file before execution. |
| Role assignment already exists | The group already has the specified Entra role assigned. | Review existing RBAC assignments before re-running the script. |
Managing privileged access at scale requires strong RBAC governance, standardized onboarding, and centralized privileged access management. Role-assignable groups help organizations simplify RBAC administration while improving security and auditability across Microsoft Entra ID environments.
This Graph PowerShell automation solution helps organizations:
By automating Entra ID RBAC governance with role-assignable groups, administrators can build more secure, scalable, and governable privileged access environments across Microsoft 365.
© Created and Maintained by LEARNIT WELL SOLUTIONS. All Rights Reserved.