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.
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:
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.
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.
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
Create an App Registration
Navigate to the Microsoft Entra portal and go to App registrations under the Applications blade.
Click New registration, provide a meaningful name, and select Register.
After registration, take note of the Client ID and Tenant ID—these are needed to run the script.
Grant API Permissions
Navigate to the API permissions menu in the newly created App registration.
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!
Create a Client Secret
Navigate to Certificates & secrets, select Client secrets, and click + New client secret.
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:
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
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:
Create the Automation Account
In the Azure portal, search for Automation Accounts, and select Create.
Complete the setup process.
Configure Modules
Navigate to the Runtime Environment (preferred) or Modules (Old) blade in the Automation Account.
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
Create and Upload the Runbook
Go to the Runbooks menu and select Create.
Upload the script, choosing the Runtime Environment that contains the required modules.
Test the Runbook
Before publishing, test the Runbook in the Test pane.
For automation runs, set the following parameters:
LocalRun and SaveFiles to $false.
The OutDir is automatically overwritten to a temporary location.
Publish and Schedule the Runbook
Once testing is successful, publish the Runbook. - This allows for on-demand runs, which will prompt for parameters
To automate execution, navigate to the Schedules menu:
Select Add a schedule and configure start date, recurrence, and expiration.
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.
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.
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:
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:
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:
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