Managing role assignments and Privileged Identity Management (PIM) across a large Azure environment quickly becomes opaque — especially when you have 10+ subscriptions, an ALZ hierarchy, and a mix of legacy static assignments alongside newer PIM eligible setups. This post walks through a PowerShell script I built to get a full picture of who has what access, and where the cleanup backlog is.
In a mature Azure tenant you typically end up with:
Getting a clear answer to "who has what" requires querying multiple APIs across Entra ID, Azure Resource Manager, and potentially Management Groups — and then correlating the results. That's what this script does.
Get-AzurePIMReport.ps1 performs a full audit sweep across a single tenant and produces two output files: an interactive HTML report and a CSV for further processing.
Data collected:
Get-AzRoleAssignment)For each assignment the script resolves:
directoryObjects/{id} — a single Graph call that returns the type and name regardless of whether the object is a user, group, or service principalThe script categorises every assignment into one of four risk levels:
| Level | Condition |
|---|---|
| 🔴 HIGH | Static assignment on a privileged role (Owner, Contributor, Global Administrator, etc.) |
| 🟠 MEDIUM | Static assignment on any other role — candidate for conversion to PIM |
| 🟢 LOW | PIM eligible on a privileged role — correct setup, worth documenting |
| ✅ OK | PIM eligible on a standard role |
The HIGH list is your immediate cleanup backlog. The MEDIUM list is your longer-term PIM conversion backlog.
The output HTML file is fully self-contained — no server, no dependencies, just open it in a browser. All filtering and sorting happens client-side in JavaScript.
Filters available:
Hovering a truncated cell shows the full text as a tooltip — useful for long scope paths and group member lists.
PowerShell 7 (not compatible with Windows PowerShell 5.1 / ISE due to use of null-coalescing ?? syntax).
Modules:
Install-Module Az.Accounts -Scope CurrentUser
Install-Module Az.Resources -Scope CurrentUser
Install-Module Microsoft.Graph -Scope CurrentUser
Permissions required:
| Scope | Permission |
|---|---|
| Entra ID | Privileged Role Administrator or Global Reader |
| Microsoft Graph | RoleManagement.Read.All, PrivilegedAccess.Read.AzureAD, Directory.Read.All |
| Azure Subscriptions | Reader on all subscriptions |
| Management Groups (optional) | Reader on all management groups |
The script requests the Graph scopes automatically — a browser login window will appear.
# Standard run — two browser logins (Az + Graph)
.\Get-AzurePIMReport.ps1 -TenantId "contoso.onmicrosoft.com"
# Include Management Group scope
.\Get-AzurePIMReport.ps1 -TenantId "contoso.onmicrosoft.com" -IncludeManagementGroups
# Custom output path
.\Get-AzurePIMReport.ps1 -TenantId "contoso.onmicrosoft.com" -OutputPath "C:\Audit\PIM_March2026"
# Skip group member expansion (faster on large tenants)
.\Get-AzurePIMReport.ps1 -TenantId "contoso.onmicrosoft.com" -SkipGroupExpansion
The -TenantId parameter is mandatory — this ensures you always log in to the correct tenant and never accidentally audit the wrong one. Each run performs a fresh login and clears the token cache.
PIM eligible assignments on subscriptions may return 401. The roleEligibilityScheduleInstances REST endpoint requires an active ARM role on the subscription. If your account only has an eligible (not yet activated) PIM assignment, the call is rejected. The script logs this as [WARN] and continues — static assignments are always collected successfully. To collect PIM eligible data, activate your PIM role before running, or use a service principal with a permanent Reader role.
Role-assignable group members are not enumerable. Groups with isAssignableToRole = true — the groups used to assign PIM roles — block the transitiveMembers endpoint by Microsoft design. This is an intentional security measure. The script detects these automatically and labels them [role-assignable group: member list restricted by Microsoft] rather than throwing an error.
Deleted or cross-tenant principals show as Deleted/External. If a role assignment references an account that has been deleted, or a service principal from another tenant, it cannot be resolved to a display name. These are surfaced in the report with type Deleted/External and are good candidates for immediate cleanup — the principal no longer exists but the assignment is still consuming a role slot.
The zip contains:
Get-AzurePIMReport.ps1 — the audit scriptGet-AzurePIMReport.md — full documentation (also suitable as a repo README)Download Get-AzurePIMReport.zip{.button .button-primary}
Once you have the report, a practical approach:
Re-run the script after each cleanup round to track progress.