Accountability in the Digital Age: Making Shadow Work Visible
Some people prefer to work in the shadows, believing their privileged activities go unnoticed. They would be wrong.
In environments with Privileged Identity Management (PIM), users activating highly privileged roles must provide a justification. Unfortunately, this is often just a freeform text field where users can enter whatever they desire and then proceed to do something entirely different with that access. Worse still, many provide vague activation reasons that leave you wondering what was actually accomplished with those elevated permissions.
This script fills in the missing pieces by looking for specific activities in the audit logs and reporting on them, allowing you to build a complete picture from the CSV output about what actions were actually taken with that privileged access. It transforms vague PIM justifications into concrete evidence of what someone did—or didn't do—during their elevated session.
Many people won't appreciate this level of auditing because they've grown comfortable operating behind the scenes, thinking their activities blend into the noise of system logs. This tool changes that dynamic by cutting through the noise and highlighting exactly what human-initiated changes occurred, when, and by whom.
The Problem: Noise in Audit Logs
I recently found myself struggling with this exact challenge. While Azure AD audit logs capture everything, they're filled with noise that makes it difficult to identify genuine permission changes. The logs include:
- System-initiated activities that happen automatically
- PIM activations/deactivations that are just users exercising existing permissions
- Read-only operations like viewing groups or applications
- Routine activities that don't actually change permissions
What I needed was a way to focus specifically on human-initiated permission changes - the activities that actually modify who can access what in my environment.
Lets walkthrough the solution logic using Microsoft Graph API to query audit logs with intelligent filtering. The script focuses on permission-related activities while excluding the noise.
Prerequisites and Permissions
To use this script, you'll need an Azure AD app registration with the following API permissions:
# Required Microsoft Graph permissions
AuditLog.Read.All # Read audit log data
Directory.Read.All # Read directory information
Policy.Read.All # Read policy information
User.Read # Read user profiles
UserAuthenticationMethod.Read.All # Read auth methods
Categories Monitored
The script monitors several key categories of activities:
# Main categories checked
$categories = @(
"GroupManagement", # Group membership changes
"RoleManagement", # Role assignments and permissions
"ApplicationManagement", # App registrations and enterprise apps
"Policy", # Policy changes
"UserManagement", # User-related changes
"DeviceManagement", # Intune device management
"ResourceManagement" # Azure resource permissions
)
Intelligent Filtering Logic
The core of the script lies in its filtering logic, which includes activities while excluding noise:
# Include actual permission changes
$targetActivities = @(
"Add member to group",
"Remove member from group",
"Create application",
"Delete application",
"Add app role assignment to user",
"Remove app role assignment from user",
"Register application",
"Unregister application",
"Create device compliance policy",
"Update app protection policy"
# ... and many more
)
# Exclude routine activities and noise
$excludeActivities = @(
"Activate role", # PIM activation (using existing permission)
"Deactivate role", # PIM deactivation
"GroupsODataV4_Get", # Read-only operations
"Get group",
"Get user",
"List applications"
)
The script also uses pattern matching to catch activities it might not explicitly list:
# Pattern-based filtering
($_.ActivityDisplayName -like "*permission*" -and $_.ActivityDisplayName -notlike "*Get*") -or
($_.ActivityDisplayName -like "*application*" -and $_.ActivityDisplayName -notlike "*Get*") -or
($_.ActivityDisplayName -match "(Create|Delete|Add|Remove|Update|Assign).*")
Usage and Command Line Options
The script accepts several parameters to customize the audit scope:
.\entra-audit-permissions.ps1 -UPN "lee@bythepowerofgreyskull.com" -TimeFrame "7days"
Available Time Frames
The script provides the option of 1 day, 7 days or 30 days (the default is 7 days if not specified)
.\entra-audit-permissions.ps1 -UPN "lee@bythepowerofgreyskull.com" -TimeFrame "1day"
.\entra-audit-permissions.ps1 -UPN "lee@bythepowerofgreyskull.com" -TimeFrame "7days"
.\entra-audit-permissions.ps1 -UPN "lee@bythepowerofgreyskull.com
" -TimeFrame "30days"
Connection and Authentication
The script uses app registration credentials for authentication:
# App Registration Connection
$tenantId = "your-tenant-id"
$clientId = "your-app-id"
$clientSecret = "your-app-secret"
# Connect using app registration
$secureSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($clientId, $secureSecret)
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential
Output and Reporting
The script provides both console output and CSV export for further analysis:
Console Output
The script displays results with color-coded sections:
- User resolution and validation
- Category-by-category search progress
- Detailed activity listings with timestamps, targets, and context
- Summary statistics by activity type
CSV Export
Each run automatically generates a CSV file when changes are detected which is then shown as the below, meaning you can then review the data in that CSV file.
"DateTime","Activity","Category","Result","InitiatedBy","TargetResources","PIMRelated","AdditionalDetails"
"11/08/2025 16:42:24","Update policy","Policy","success","System","Script Test : Did you catch this?","False","User-Agent: Microsoft Azure Graph Client Library 1.0"
"11/08/2025 16:42:24","Update conditional access policy","Policy","success","Lee Croucher (Lee.Croucher@bythepowerofgreyskull.com)","Script Test : Did you catch this?","False","Category: Conditional Access"Example CSV filename: PermissionAuditLog_Lee_Croucher_20250811_132041.csv
.
Conclusion
This script has transformed how I approach permission auditing in Azure AD environments. By focusing on signal over noise, it enables security teams to efficiently monitor and investigate actual permission changes while filtering out the routine operational activities that typically clutter audit logs.
The automated CSV export also makes it easy to incorporate into existing security workflows, whether that's regular compliance reporting or incident investigation processes.