Simon Vedder

Cloud Engineer · Zürich

Read-only audit · HTML report

RiskyRolesAnalyzer

See who holds privileged access across Azure and Entra ID, including the assignments hidden behind nested groups, custom roles and dormant apps. One file, read-only, one HTML report.

iwr https://raw.githubusercontent.com/simon-vedder/risky-roles-analyzer/main/dist/Invoke-RiskyRolesAudit.ps1 -OutFile Invoke-RiskyRolesAudit.ps1

licenseMITPowerShell7.2+accessread-onlyinstallone file, nothing to installcoversAzure RBAC + Entra ID

pwsh · one file, nothing installed
# One file. Read it first if you like.
iwr https://raw.githubusercontent.com/simon-vedder/risky-roles-analyzer/main/dist/Invoke-RiskyRolesAudit.ps1 -OutFile Invoke-RiskyRolesAudit.ps1
# Sign in with read scopes only, read, write the report.
./Invoke-RiskyRolesAudit.ps1
Critical  9.0  Entra  Global Administrator   Legacy Deploy App   NoValidCredential
High      8.5  Azure  Owner (MG)            Bob Leaver via Cloud Admins   Disabled
Low       3.9  Entra  Directory Readers     Anna Keller   Disabled
14 finding(s) in 41 s
Report: ./RiskyRolesAnalyzer-report-20260907-131244.html

Quick start

Download it, run it, read the report

From a workstation with PowerShell 7.2. The script reads Graph and Azure and writes one HTML file. It changes nothing in your tenant, and there is no write path in it at all.

Needs four Graph read scopes and Reader on the subscriptions · Prerequisites

  1. Get the script

    One file, roughly 2,400 lines, and it is all there. Read it first if you like.

    iwr 'https://raw.githubusercontent.com/simon-vedder/risky-roles-analyzer/main/dist/Invoke-RiskyRolesAudit.ps1' -OutFile 'Invoke-RiskyRolesAudit.ps1'
  2. Run it

    It signs you in with read scopes, collects, scores, and writes the report next to itself.

    ./Invoke-RiskyRolesAudit.ps1
  3. Name your break-glass accounts

    Named accounts are marked Protected instead of being listed as work to do.

    ./Invoke-RiskyRolesAudit.ps1 -BreakGlassAccount 'breakglass@contoso.com'
  4. Trim it to your tenant

    -SkipAzure for an Entra-only tenant, -SkipPim where there is no Entra ID P2.

    ./Invoke-RiskyRolesAudit.ps1 -SkipAzure -SkipPim

Every parameter, with the permissions it needs

What it does

What it does

Nested groups expanded

A person who is Owner through two groups appears as a person, not as a group. Cycles included.

Custom roles rated by effect

A custom role is judged by the actions it grants, not by its name. Risky actions come from a data file you can read.

Dormant principals surfaced

An app registration whose last credential expired still holds Global Administrator. So does a disabled user.

PIM told apart

Eligible, activated and permanent are three different things, and the score treats them that way.

A score you can argue with

Base per role, multiplied by scope breadth, adjusted for PIM, principal type and sign-in ability. Every input is visible.

The fix in the row

Each finding carries the command that would remove it. What must not be touched says so instead, with the reason.

Under the hood

See the people behind privileged access

Collectors talk to Graph and Az. Rules are pure functions with a test on a fixture. The catalog of roles, risky actions and weights is one data file you can read.

  1. Connect and collect

    Four read scopes on Graph, Reader on Azure, and no write path in the script at all. It reads app registrations with their credential state, custom role definitions with their actions, every assignment at subscription scope (inherited ones once), permanent and eligible directory roles, and PIM activations from the schedules.

  2. Resolve

    Each principal once — user, group, managed identity, app registration or enterprise app — with whether it can sign in at all. Groups are walked to their members, cycles included.

  3. Score

    A base per role, multiplied by scope breadth, lowered for PIM eligibility and for principals that cannot sign in, raised a little for applications. Critical from 9, High from 7, Medium from 5.

  4. Protect

    Inherited through a group, PIM eligible or activated, your break-glass accounts and the identity running the audit are marked Protected. Reported, never removed.

Proof

What it shows

one file, no external resources
The HTML report: summary cards, filters, sortable findings with severity, activity status, protection reason and the removal command builder

FAQ

Questions people ask first

Is it read-only?

Yes. There is no write path in the script at all. It asks for RoleManagement.Read.Directory, Directory.Read.All, Group.Read.All and Application.Read.All on Graph, and Reader on Azure. The report hands you commands; running them is a separate, deliberate act.

What counts as privileged?

The built-in roles from the catalog (Owner, User Access Administrator, Global Administrator, Privileged Role Administrator and the usual list, plus the roles BloodHound treats as dangerous), custom roles that grant a risky action, and anything you add with -AdditionalAzureRole or -AdditionalEntraRole.

How is the score built?

A base per role (critical, high, medium, custom-with-risky-actions), multiplied by scope breadth (root and tenant 1.0, management group 0.95, subscription 0.85, resource group 0.65, resource 0.45), minus 1.5 for PIM eligible, plus 0.5 for applications and managed identities, minus 2.0 for disabled principals. Critical from 9, High from 7, Medium from 5, Low from 3.

What does it never remove?

Assignments inherited through a group (the fix is the group's assignment or the membership, both yours), PIM eligibility and activations (manage them in PIM), the accounts you name with -BreakGlassAccount, and the identity running the audit. They show as Protected with the reason.

Does it need Entra ID P2?

Only for the PIM parts. Without P2 the eligible and activated queries fail, the audit warns once and continues with permanent assignments; -SkipPim silences the warning.

Need the list with the context behind it?

I run this as the first step of an identity review — the findings walked through with the people who own them, the break-glass and PIM design checked, the cleanup done together.

Get in touch