Privileged role assignments are among the most sensitive changes in any Microsoft 365 tenant. A user being added to an admin role can be intentional (temporary elevation, onboarding into IT), but it can also signal privilege creep or even malicious escalation.
To maintain strong governance and ensure ongoing visibility, administrators should regularly review who received admin roles recently, who assigned them, and when those assignments happened.
This Graph PowerShell script queries Entra ID directory audit logs, collects all admin role assignment events from the past 30 days, exports the results to CSV, and emails the report automatically to administrators or stakeholders.
Try the M365Corner Microsoft 365 Reporting Tool â your DIY pack with 20+ out-of-the-box M365 reports for Users, Groups, and Teams.
$SenderUPN = "admin@yourtenant.onmicrosoft.com"
$Recipients = @(
"admin@yourtenant.onmicrosoft.com",
"securityteam@yourtenant.onmicrosoft.com"
)
Connect-MgGraph -Scopes "AuditLog.Read.All","Directory.Read.All","User.Read.All","Mail.Send"
$StartDate = (Get-Date).ToUniversalTime().AddDays(-30).ToString("yyyy-MM-ddTHH:mm:ssZ")
$AuditLogs = Get-MgAuditLogDirectoryAudit -All -Filter "activityDateTime ge $StartDate"
$RoleAddEvents = $AuditLogs | Where-Object {
$_.ActivityDisplayName -match "Add member to role|Add eligible member to role|Add user to role"
}
$Report = foreach ($log in $RoleAddEvents) {
$RoleTarget = $log.TargetResources | Where-Object { $_.Type -eq "Role" } | Select-Object -First 1
$UserTarget = $log.TargetResources | Where-Object { $_.Type -eq "User" } | Select-Object -First 1
if (-not $UserTarget -or -not $RoleTarget) { continue }
$ActorUPN = $null
if ($log.InitiatedBy.User) {
$ActorUPN = $log.InitiatedBy.User.UserPrincipalName
}
[PSCustomObject]@{
"Admin/User Name" = $UserTarget.DisplayName
"Admin/User UPN" = $UserTarget.UserPrincipalName
"Role Assigned" = $RoleTarget.DisplayName
"Assigned By" = $ActorUPN
"Assignment DateTime" = $log.ActivityDateTime
"Result" = $log.Result
}
}
$ReportPath = "$env:TEMP\AdminRolesAdded_Last30Days.csv"
$Report | Sort-Object "Assignment DateTime" -Descending | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8
$Count = @($Report).Count
$Subject = "Admin Role Assignments (Last 30 Days) â $(Get-Date -Format 'yyyy-MM-dd')"
$Body = @"
Hello Team,
Attached is the Admin Role Assignments Report for the last 30 days.
This report shows users who were newly added to Entra ID admin roles.
Total new admin assignments found: $Count
Regards,
Graph PowerShell Automation
"@
$AttachmentContent = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($ReportPath))
$Attachments = @(
@{
"@odata.type" = "#microsoft.graph.fileAttachment"
Name = "AdminRolesAdded_Last30Days.csv"
ContentBytes = $AttachmentContent
}
)
$ToRecipients = $Recipients | ForEach-Object {
@{ EmailAddress = @{ Address = $_ } }
}
$Message = @{
Message = @{
Subject = $Subject
Body = @{
ContentType = "HTML"
Content = $Body
}
ToRecipients = $ToRecipients
Attachments = $Attachments
}
SaveToSentItems = "true"
}
Send-MgUserMail -UserId $SenderUPN -BodyParameter $Message
Write-Host "Admin assignments report emailed successfully." -ForegroundColor Green
The script connects using these delegated scopes:
These permissions must be approved with admin consent.
A start date is calculated for the last 30 days and formatted in UTC:
$StartDate = (Get-Date).ToUniversalTime().AddDays(-30)
Only audit events newer than this are pulled.
The script fetches directory audit logs using:
Get-MgAuditLogDirectoryAudit -Filter "activityDateTime ge $StartDate"
This returns all directory actions during the window.
Audit logs contain many change types, so the script filters only:
These represent new privileged role additions.
For each matching audit record:
A structured report is created, exported to CSV, base64-encoded, and sent to configured recipients using Send-MgUserMail.
Example:
Track:
Separate:
Archive role assignment history for audits.
Trigger a Teams or email alert immediately if Global Admin is assigned.
| Error | Cause | Solution |
|---|---|---|
| Authorization_RequestDenied | Missing permissions for audit logs. | Ensure scopes include: AuditLog.Read.All, Directory.Read.All and User.Read.All permissions. Consent must be granted by an admin. |
| Empty CSV Report | No role assignment events occurred during the last 30 days. | This is valid behavior. Increase window (e.g., 60 days) to verify. |
| Actor UPN appears blank | Some role assignments are system-driven or done by non-user principals. | Keep the field; it indicates automated assignment. |
| Throttling or large tenant delay | Very large tenants may hit Graph throttling. | Rerun after a short pause or add paging/backoff logic. |
Tracking new privileged role assignments is critical for preventing privilege creep and ensuring admin governance stays transparent and controlled. This script automates that visibility by extracting admin role additions from directory audit logs, generating a clean CSV, and emailing it to stakeholders for review.
Scheduling this report weekly or monthly provides continuous security assurance and helps teams stay audit-ready at all times.
© m365corner.com. All Rights Reserved. Design by HTML Codex