Automate Entra ID RBAC Governance with Role-Assignable Groups Using PowerShell

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:

  • RBAC sprawl
  • inconsistent privilege management
  • difficult access reviews
  • excessive standing privileges
  • reduced auditability

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:

  • Bulk create role-assignable groups
  • Assign Entra roles automatically
  • Validate role assignments
  • Detect duplicate groups
  • Standardize RBAC naming conventions
  • Generate governance reports
  • Automatically email governance summaries

The solution is ideal for:

  • privileged admin onboarding
  • RBAC modernization
  • Zero Trust administration
  • tiered admin architecture
  • privileged access governance

🚀 Community Edition Released!

Try the M365Corner Microsoft 365 Reporting Tool — your DIY pack with 20+ out-of-the-box M365 reports for Users, Groups, and Teams.

Why Role-Assignable Groups Are Important

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:

  • assign roles to groups
  • onboard admins more efficiently
  • simplify access reviews
  • improve auditability
  • strengthen RBAC governance

This supports:

  • least privilege administration
  • Zero Trust security principles
  • privileged access governance
  • scalable admin lifecycle management

Benefits of Assigning Roles to Groups Instead of Users

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.


How Role-Assignable Groups Support Zero Trust

Role-assignable groups help organizations implement Zero Trust principles by:

  • centralizing privileged access
  • reducing direct admin assignments
  • improving RBAC segmentation
  • simplifying governance reviews
  • supporting least privilege administration

This creates a more secure and auditable Microsoft 365 environment.

Prerequisites

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"

CSV File Format

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

Complete Script to Automate Entra ID RBAC Governance

                            
# 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


How the Script Works

  1. Imports RBAC Group Definitions from CSV
  2. The script imports:

    • group names
    • mail nicknames
    • assigned Entra roles

    using:

    Import-Csv

    Each CSV row defines:

    • a role-assignable group
    • one or more Microsoft Entra roles

    to be assigned automatically.

  3. Detects Duplicate RBAC Groups
  4. 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:

    • RBAC duplication
    • naming conflicts
    • governance inconsistencies
  5. Validates Naming Conventions
  6. The script validates whether group names follow the recommended RBAC naming standard:

    PIM-T0-
    PIM-T1-
    PIM-T2-

    • AccountEnabled
    • UserType
    • UserPrincipalName

    This helps organizations standardize:

    • tiered administration
    • privileged access governance
    • RBAC architecture
  7. Validates Entra Roles
  8. Each role specified in the CSV file is validated using:

    Get-MgDirectoryRoleTemplate

    This ensures:

    • invalid roles are detected
    • unsupported assignments are skipped
    • governance visibility is maintained

    before RBAC assignments occur.

  9. Activates Roles Automatically
  10. 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.

  11. Creates Role-Assignable Groups
  12. The script creates Microsoft Entra security groups with:

    -IsAssignableToRole:$true

    This enables the groups to receive Microsoft Entra administrative roles.

  13. Assigns Roles Automatically
  14. The script assigns one or more Entra roles to each group using:

    New-MgRoleManagementDirectoryRoleAssignment

    This enables scalable RBAC onboarding workflows.

  15. Detects Critical Privileged Roles
  16. The script identifies high-risk privileged roles such as:

    • Global Administrator
    • Privileged Role Administrator
    • Authentication Administrator

    Groups containing these roles are automatically labeled with higher governance severity levels.

  17. Calculates Governance Scores
  18. Each RBAC group receives a Governance Score based on:

    • naming compliance
    • role assignment complexity
    • privileged role exposure
    • RBAC governance quality

    This helps administrators prioritize RBAC reviews.

  19. Emails Governance Reports Automatically
  20. The script:

    • exports the RBAC governance report to CSV
    • embeds governance statistics in HTML format
    • emails the governance summary automatically

    The report provides:

    • operational visibility
    • auditability
    • RBAC governance tracking
    • privileged access oversight

Real-World Use Cases

  • Privileged Admin Onboarding
  • Standardize onboarding using governed role-assignable groups.

  • Tiered Administration Rollouts
  • Implement Tier 0, Tier 1, and Tier 2 administrative models.
  • Zero Trust RBAC Modernization
  • Replace direct role assignments with governed group-based RBAC.

  • Compliance and Security Reviews
  • Generate RBAC governance reports for audit and security teams.

Automating RBAC Governance

You can automate this solution using:

  • Azure Automation
  • Windows Task Scheduler
  • GitHub Actions
  • Scheduled PowerShell Jobs

This enables recurring RBAC governance reviews and privileged onboarding workflows.

Possible Errors and Solutions

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.

Conclusion

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:

  • standardize RBAC architecture
  • automate privileged onboarding
  • validate role assignments
  • strengthen governance visibility
  • improve Zero Trust administration

By automating Entra ID RBAC governance with role-assignable groups, administrators can build more secure, scalable, and governable privileged access environments across Microsoft 365.


Graph PowerShell Explorer Widget

20 Graph PowerShell cmdlets with easily accessible "working" examples.


Permission Required

Example:


                            


                            


                            

© Created and Maintained by LEARNIT WELL SOLUTIONS. All Rights Reserved.