top of page
Writer's pictureSebastian F. Markdanner

Mastering Microsoft Azure RBAC & Entra ID Roles: Automated Role Assignment Reporting Across Your Tenant

As the season for audits approaches (though, let’s be honest, auditing should be an all-year-round endeavor), I’m excited to share a practical solution for managing role assignments across your tenant.

Surreal landscape with a giant eye, colorful swirls, planets, wind turbine, and icons labeled "Identities." Energetic, futuristic mood.

Managing role assignments can feel overwhelming, especially when multiple administrators are involved in assigning, monitoring, auditing, and managing roles. It’s rarely a one-person job, and the complexities only grow with the scale of your organization.


Combine that with increasing regulatory demands for auditing, reporting, and overall security—like the EU’s NIS-2 directive—and it’s clear why visibility and control over assigned privileges are more critical than ever.


To address these challenges, I’ve developed a solution in PowerShell that simplifies and streamlines this process. This solution can be automated and scheduled using Azure Automation Accounts or executed on-demand, whether via the Automation Account or locally, depending on your specific needs.


Let me suggest an optimized structure for your sections that incorporates your focus keyword phrase and improves readability while maintaining the logical flow.


Table of Contents

 

Understanding The Role Assignment Reporting Automation Solution

Let’s dive into the full script, which is also available on my GitHub:

The Complete PowerShell Solution


As you can see, the script is more comprehensive than I initially let on. Here’s a breakdown of its functionality:


  1. Core Functionality

    • The script utilizes a Service Principal to retrieve all active and eligible (if licensed) role assignments across Entra ID and Azure.

    • It also collects last sign-in data for identities and processes the results into a combined, formatted Excel file.

    • The output can either be saved locally or sent as an email attachment, depending on your requirements.


  2. Modules and APIs

    • Built using Microsoft Graph and Az PowerShell modules, the script ensures all necessary modules are installed and up-to-date when running locally.

    • In Azure Automation Accounts, you’ll need to install these modules beforehand—a step we’ll revisit later.


  3. Authentication

    • Authentication is handled via an App Registration Service Principal with a Client Secret.

    • Once Microsoft introduces Graph Powershell SDK support for Managed Identity as federated credentials, I’ll update the solution to leverage Managed Identity. This enhancement will increase security and reduce administrative overhead.


This solution provides a robust way to automate and simplify role assignment reporting across your tenant, ensuring you’re always prepared for audits, whether scheduled or on-demand. Up next, I’ll walk you through setting up and running the script in both local and automated environments.


 

Setting Up The Environment

Required Permissions and Access

To use the solution, a Service Principal is required to collect role assignments. This involves several steps for proper configuration:


Configuring the Service Principal

  1. Create an App Registration

    • Navigate to the Microsoft Entra portal and go to App registrations under the Applications blade.

      Microsoft Entra admin center screen showing "App registrations" with one app, "Valimail," listed under "All applications." Options include "New registration."

    • Click New registration, provide a meaningful name, and select Register.

      Web page for registering an app. Fields for name and redirect URI. Options for account types. Blue "Register" button at bottom.

    • After registration, take note of the Client ID and Tenant ID—these are needed to run the script.


  2. Grant API Permissions

    • Navigate to the API permissions menu in the newly created App registration.

      Azure portal interface showing "Role Assignment Solution" details. IDs and permissions are listed. API permissions are highlighted in red.

  3. Select +Add a permission, choose Microsoft Graph, and assign the following Application permissions:

    • Application.Read.All

    • AuditLog.Read.All

    • Directory.Read.All

    • Mail.Send

    • PrivilegedAccess.Read.AzureAD

    • RoleManagement.Read.All

    • User.Read.All

      • Don’t forget to Grant admin consent for the permissions!

    Configured permissions table for Microsoft Graph APIs, showing application read, audit log, and user data permissions granted.

  4. Create a Client Secret

    • Navigate to Certificates & secrets, select Client secrets, and click + New client secret.

    • Software interface for adding a client secret in "Role Assignment Solution." Details include description and expiry options.

      Copy the secret value—you’ll need this to run the script.


    • Alternatively, you can use PowerShell to create a client secret with an extended lifetime. Here’s a script for that, available here on my GitHub:

<#
.SYNOPSIS
Creates a client secret for an Entra ID app registration.

.DESCRIPTION
This script creates a client secret for an Entra ID app registration using the application's Client ID (ClientId).
It calculates the secret's expiration date based on the specified duration, generates the secret, and copies the secret value to the clipboard.

.PARAMETER ClientId
The Application (Client) ID of the Entra ID app registration.

.PARAMETER Description
A custom description or identifier for the client secret.

.PARAMETER Duration
The number of years the client secret will remain valid. Default is 99 years.

.EXAMPLE
.\CreateClientSecret.ps1 `
	-ClientId "12345678-90ab-cdef-1234-567890abcdef" `
	-Description "MyAppSecret" `
	-Duration 99

Creates a client secret for the specified app with a validity period of 99 years and copies the secret value to the clipboard.

.NOTES
Author:     Sebastian Flæng Markdanner
Website:    https://chanceofsecurity.com
Version:    1.0

- Requires the Az PowerShell module.
- User must be authenticated to Azure with sufficient permissions to manage app registrations.
#>

param (
    [Parameter(Mandatory = $true)]
    [string]$ClientId,

    [Parameter(Mandatory = $true)]
    [string]$Description,
    
    [Parameter(Mandatory = $true)]
    [int]$Duration = 99
)

# Authenticate to Azure
Connect-AzAccount

# Calculate start and end dates
$StartDate = Get-Date
$EndDate = $StartDate.AddYears($Duration)

# Encode the description to Base64
$EncodedDescription = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Description))

# Create client secret
$ClientSecret = Get-AzADApplication -ApplicationId $ClientId | New-AzADAppCredential -StartDate $StartDate -EndDate $EndDate -CustomKeyIdentifier $EncodedDescription

# Copy secret to clipboard
$ClientSecret.SecretText | Set-Clipboard

# Notify user and wait
Write-Output "Client secret created and copied to clipboard. Script will end in 10 seconds."
Start-Sleep -Seconds 10

Granting Entra ID Permissions

The Service Principal requires the Directory Reader role in Entra ID. Assign it with the following PowerShell command:

# Assign Directory Reader role
$servicePrincipalId = "<AppRegistrationObjectID>"
$directoryReaderRole = "88d8e3e3-8f55-4a1e-953a-9b9898b8876b"
New-AzureADDirectoryRoleMember `
	-ObjectId $directoryReaderRole `
	-RefObjectId $servicePrincipalId

Configuring Azure RBAC Access

To collect Azure RBAC roles, assign the Reader role to the Service Principal at the desired scope (e.g., subscription, management group, or root level). For maximum coverage, assign the role at the root (/) level:

New-AzRoleAssignment `
	-ObjectID "<AppRegistrationObjectID>" `
	-RoleDefinitionName "Reader" `
	-Scope "/"

Setting Up Email Permissions

The Mail.Send API permission allows the Service Principal to send emails. To restrict its ability to send emails as any user (a major security risk), you must limit it to specific email addresses.


This can be achieved using PowerShell. Here’s a script (found on my GitHub here) to automate the process:

<#
.SYNOPSIS
Creates a mail-enabled security group, a shared mailbox, and an application access policy in Exchange Online.

.DESCRIPTION
This script connects to Exchange Online, creates a mail-enabled security group, a shared mailbox, and an application access policy with specified parameters. It hides the mail-enabled security group from the address list and restricts access for the app to the group members.

.PARAMETER GroupName
The name of the mail-enabled security group to be created.

.PARAMETER GroupAlias
The alias for the mail-enabled security group.

.PARAMETER GroupEmail
The email address for the mail-enabled security group.

.PARAMETER SharedMailboxName
The email address for the shared mailbox.

.PARAMETER SharedMailboxDisplayName
The display name for the shared mailbox.

.PARAMETER SharedMailboxAlias
The alias for the shared mailbox.

.PARAMETER ClientId
The Application (Client) ID of the Entra ID app registration.

.EXAMPLE
.\RestrictServicePrincipalEmail.ps1 `
    -GroupName "SMTP Graph" `
    -GroupAlias "smtp-graph" `
    -GroupEmail "smtp-graph@contoso.com" `
    -SharedMailboxName "privroles@contoso.com" `
    -SharedMailboxDisplayName "Privileged Roles Monitoring" `
    -SharedMailboxAlias "privroles" `
    -ClientId "12345678-abcd-efgh-ijkl-9876543210ab" `

.NOTES
Author: Sebastian Flæng Markdanner
Website: https://chanceofsecurity.com
Version: 1.0

- Requires the ExchangeOnlineManagement PowerShell module.
#>

param (
    [Parameter(Mandatory = $true)]
    [string]$GroupName,

    [Parameter(Mandatory = $true)]
    [string]$GroupAlias,

    [Parameter(Mandatory = $true)]
    [string]$GroupEmail,

    [Parameter(Mandatory = $true)]
    [string]$SharedMailboxName,

    [Parameter(Mandatory = $true)]
    [string]$SharedMailboxDisplayName,

    [Parameter(Mandatory = $true)]
    [string]$SharedMailboxAlias,

    [Parameter(Mandatory = $true)]
    [string]$ClientId
)

# Connect to Exchange Online
Connect-ExchangeOnline

# Creates a new mail-enabled security group
New-DistributionGroup -Name $GroupName -Alias $GroupAlias -Type Security

# Set email address and hide the mail-enabled security group from the address list
Set-DistributionGroup -Identity $GroupName -EmailAddresses SMTP:$GroupEmail -HiddenFromAddressListsEnabled $true

# Creates a new shared mailbox
New-Mailbox -Shared -Name $SharedMailboxName -DisplayName $SharedMailboxDisplayName -Alias $SharedMailboxAlias

# Add the shared mailbox to the mail-enabled security group
Add-DistributionGroupMember -Identity $GroupName -Member $SharedMailboxName

# Create the application access policy
New-ApplicationAccessPolicy -AppId $ClientId -PolicyScopeGroupId $GroupEmail -AccessRight RestrictAccess -Description "Restrict this app to send mails only to members of the group $GroupName"

# Output confirmation
Write-Output "Resources created successfully: mail-enabled security group '$GroupName' and Shared Mailbox '$SharedMailboxName'. Application Access Policy applied."

This script ensures the Service Principal can only send emails as the shared mailbox or addresses within the mail-enabled security group. To change the allowed email, simply add it to the group.


With these prerequisites in place, your Service Principal is now fully configured and ready to run the role assignment reporting solution.


 

Implementing the Solution

This script is designed to work seamlessly both locally and in an automated Azure Automation Account setup.


Manual Execution Guide

Running the script manually is straightforward. Here are a few examples:

  1. Saving the output file locally to C:\Temp:

.\CollectRoleAssignments.ps1 `
	-TenantId "your-tenant-id" `
	-ClientId "your-client-id" `
	-Client_secret "your-client-secret" `
	-localRun $true

  1. Emailing the output to the auditor team’s shared mailbox without saving the generated report:

.\CollectRoleAssignments.ps1 `
	-TenantId "your-tenant-id" `
	-ClientId "your-client-id" `
	-Client_secret "your-client-secret" `
	-LocalRun $true `
	-mailFrom "automation@yourdomain.com" `
	-mailTo "AuditTeam@yourdomain.com" `
	-SaveFiles $false

Automating with Azure Automation

While manual execution works well, automating the process ensures continuous and consistent reporting. Here’s how to set it up in an Azure Automation Account:

  1. Create the Automation Account

    • In the Azure portal, search for Automation Accounts, and select Create.

      Search bar showing "automation," with tabs for All, Services, and Marketplace. Highlighted result: "Automation Accounts" under Services.

    • Complete the setup process.

      Screenshot of "Create an Automation Account" screen with a "Validation passed" message. Details include name, region, and network settings.

  2. Configure Modules

    • Navigate to the Runtime Environment (preferred) or Modules (Old) blade in the Automation Account.

      Azure Automation interface showing overview and runtime environments. "Create a Runtime environment" button highlighted. Text on job execution.
      Azure dashboard showing modules under "aa-roleassignmentreporting." Modules are listed by name and status, mostly marked as available.

    • If using the Runtime Environment, create one that includes the following required modules, otherwise install the modules directly:

      • Az.Accounts

      • Az.Resources

      • Microsoft.Graph.Authentication

      • Microsoft.Graph.Users

      • Microsoft.Graph.Identity.DirectoryManagement

      • Microsoft.Graph.Identity.SignIns

      • Microsoft.Graph.Reports

      • Microsoft.Graph.Identity.Governance

      • ImportExcel

        Interface showing "Create a Runtime environment" with package list and versions, including Az 11.2.0. Warnings on upload limits.

  1. Create and Upload the Runbook

    • Go to the Runbooks menu and select Create.

      Azure portal interface showing "aa-roleassignmentreporting" Runbooks. "Create" button highlighted. Sidebar lists automation options.

    • Upload the script, choosing the Runtime Environment that contains the required modules.

      Azure interface for creating a runbook. Options for PowerShell, file browsing, and naming are visible. The text explains runbook usage.

  2. Test the Runbook

    • Before publishing, test the Runbook in the Test pane.

      PowerShell Runbook editing interface, showing code snippets for role assignments. Menu with CMDLETS, RUNBOOKS, and ASSETS. Test pane highlighted.

    • For automation runs, set the following parameters:

      • LocalRun and SaveFiles to $false.

      • The OutDir is automatically overwritten to a temporary location.

    User interface for a test run with parameter fields on the left and instructions on the right. Text prompts to click 'Start' for results.

  3. Publish and Schedule the Runbook

    • Once testing is successful, publish the Runbook. - This allows for on-demand runs, which will prompt for parameters

      Azure Runbook interface showing 'CollectRoleAssignments' with 'Start' button highlighted. Input fields for tenant, client ID, and secret are empty.

  • To automate execution, navigate to the Schedules menu:

    Azure portal interface displaying the "CollectRoleAssignments" runbook schedule. No schedules listed. Options: "Add a schedule" and "Refresh."

  • Select Add a schedule and configure start date, recurrence, and expiration.

    Screen showing schedule setup for “Monthly-Reporting”. Details include start date, time zone, and recurrence options. No schedules yet.

  • Once a schedule is linked, access the Parameters and Run Settings menu. Here, you’ll configure the parameters that will be reused every time the schedule runs.

    Form with fields for TENANTID, CLIENTID, CLIENT_SECRET. Red error messages indicate missing values. Blue "OK" button below.

 

Understanding the Reports

The script generates a detailed and formatted Excel report that is divided into two sheets: RBAC Roles and Entra Roles. Here’s an overview of how each sheet is structured and its key features:


Azure RBAC Role Assignments

This sheet provides detailed insights into role assignments across Azure resources.

A table lists names, emails, subscriptions, active roles, and last sign-in dates. Blue and white rows with text showing user details.

Columns:

  • AccountName: Displays the identity's UPN, ObjectID or App DisplayName

  • DisplayName: Displays the identity’s name with a prefix (e.g., User, External User, Service Principal, Group).

  • SubscriptionName: The subscription where the role is assigned.

  • RoleDefinitionName: The role assigned to the identity (e.g., Global Administrator, Reader).

  • AssignmentType: Whether the role is Active or Eligible.

  • LastSignIn: Indicates the last time the identity signed in.

  • Scope: The level at which the role is assigned (e.g., subscription, resource group).

  • ObjectType: Specifies the type of identity (User, External User, Group, Service Principal).


Key Details:

  • The prefix in DisplayName distinguishes identity types:

    • User: Standard users in the directory.

    • External User: Collaborators from outside the organization.

    • Service Principal: Applications or automated accounts.

    • Group: Security groups with role assignments.

    • Unknown: Foreign identities such as GDAP partner relationships, which are not translatable. These still display scope and roles but lack additional data.


From a production environment, the RBAC Roles sheet demonstrates:

Spreadsheet with columns for user roles and permissions. Active statuses in blue, eligible in red. Text includes "RG1" to "RG5".

All of these are for the same external user.

This demonstrates:

  • Active and Eligible roles across multiple scopes.

  • Duplicates removed—each role and scope is listed uniquely based on AssignmentType and role.


Entra ID Role Management

The Entra roles feature fewer scopes than we typically use. We have global roles and Administrative Units for scoping, meaning each Identity is shown once, with all active assignments consolidated into one cell, and eligible assignments in another.

Let's examine the same two tenants as earlier:

Table displaying user data: emails, names, principal types, last sign-ins, active roles. Blue background, some text blurred for privacy.

Let’s take a closer look at some key columns and what they mean:

  • Principal:

    This could be an email, App ID, or group object ID. For external users, emails are displayed directly for clarity.

    Example:

    john.smith_contoso.com#EXT#@woodgrove.com becomes john.smith@contoso.com.


  • PrincipalDisplayName

    The display name of the identity, with prefixes matching the options listed in the RBAC Roles sheet (email, App ID, or object ID).


  • PrincipalType

    This indicates the type of identity (e.g., User, App, Group).


  • LastSignIn

    Shows the last time the identity signed into the tenant.


  • ActiveRoles

    A combined list of all active role assignments.


  • EligibleRoles

    A combined list of all roles the identity is eligible for but not actively assigned to.


  • IsBuiltIn

    Identifies whether the role is a built-in role or a custom one.


Here’s a snapshot from a production tenant to better illustrate these concepts:

Spreadsheet with columns: PrincipalDisplayName, PrincipalType, LastSignIn, ActiveRoles. Displays various user roles and last sign-in dates.

This view highlights:

  • The variety of identity types.

  • How they’re organized and sorted.

  • The formatting when multiple roles are assigned to a single identity.


Understanding this structure makes it much easier to keep track of roles and identities in your tenant. It’s a simple way to maintain clarity in what can quickly become a maze of assignments.


 

Conclusion: Transforming Your Role Management Strategy

Managing role assignments across your tenant doesn’t have to feel like herding cats. With this solution, you can take control of your Azure RBAC and Entra roles, streamline audits, and stay ahead of regulatory demands like NIS-2. Whether you’re running the script locally or automating it with Azure Automation Accounts, you’ll have clear and actionable insights at your fingertips.


And as always, here's another bad joke!


What did Git say to the repository?

Let me commit to our relationship! 😎


If you’ve found this solution helpful, be sure to check out my GitHub repository for more tools, tips, and updates. And don’t forget to stay tuned for my next posts, as I've got big plans coming up!


Until then, happy auditing—and may your roles be secure and your Excel sheets perfectly formatted.

Comentarios

Obtuvo 0 de 5 estrellas.
Aún no hay calificaciones

Agrega una calificación
bottom of page