Skip to main content
This is not an official U.S. federal government website. The Federal Modernization Workbook is independent technical analysis. It does not represent the position of, and has not been reviewed or endorsed by, any federal agency.

Federal Modernization Workbook

Chapter 01 — Privileged Inventory as Code

Read every privileged assignment in the estate as code, starting from Learn’s own samples for listing Microsoft Entra role assignments (pinned in the repository) and adding what they leave to you: eligible as well as active assignments, role-assignable groups expanded to their members, Azure role assignment and eligibility schedules through Resource Manager, a tier for every row, findings for standing privilege, and a manifest the ISSO can check, with labs against fixtures and a test tenant.

You can’t make privilege just-in-time until you can list it: who holds which role, at which scope, standing or eligible, directly or through a group, and in which tier.

NoteIn this chapter

The read behind the project plan’s Gate 0, “standing administrative privilege inventoried and classified by tier.” You start with Learn’s own samples for listing Microsoft Entra role assignments, which the repository keeps as a pinned copy, and run them against stand-ins to see what they return and what they miss. Then you build examples/privileged-access for what they leave to you: eligible assignments, group members, Azure role assignment and eligibility schedules, a tier for every row, findings, and a manifest. Lab 1 runs on a laptop with no tenant; Lab 2 uses a test tenant. A table at the end maps each output to the federal requirement it supports.

TipNarrated tour
Building Privileged Access, Chapter 01 — Privileged Inventory as Code: narrated tour · 11 minutes · captions from the player's CC button · Download MP4 · All videos
Jump to a slide (18)

Deck · Narrated deck (about 11 minutes, captions) · Script · Captions. Recorded on November 2, 2026 from the approved chapter.

Goal and audience

Goal. By the end of this chapter you can produce, from code, the inventory the plan’s tasks A0.1 and A0.2 ask for: every Microsoft Entra directory role assignment, active and eligible, and every Azure role assignment and eligibility on the scopes you name, each classified by the program’s tiers, with the findings a reviewer acts on and a manifest that says which calls produced it. You can say, for each rule in the code, whether it is Microsoft’s contract, Microsoft’s sample, or the program’s own design.

Audience. Developers who build the project’s read path, and the identity engineers and ISSOs who review it. You should read PowerShell 7 and know what a role assignment is. You don’t need a tenant until Lab 2.

What the inventory has to hold

A role reaches a person in four ways that look the same on an admin’s screen and very different to an attacker. Learn names them: permanent eligible, permanent active, time-bound eligible and time-bound active, and says to keep zero permanently active assignments for roles other than the emergency access accounts.1 An eligible assignment the holder has activated shows up as active for the activation window. And a role assigned to a role-assignable group reaches every member of it, which is how Book 04 wants it to work: the program assigns roles to PRV-<tier>-<role>[-<scope>] groups, never to a person (Book 04, Chapter 04).

So the inventory reads three Graph collections and two Resource Manager ones:

What Where Why this one
Role definitions GET /v1.0/roleManagement/directory/roleDefinitions Names and template IDs, including custom roles2
Active assignments GET /v1.0/roleManagement/directory/roleAssignmentScheduleInstances Every active assignment, whether made through PIM or directly, with assignmentType (Assigned or Activated), memberType (Direct, Group or Inherited) and endDateTime3 4
Eligible assignments GET /v1.0/roleManagement/directory/roleEligibilitySchedules Every eligibility, with its expiration5
Group members GET /v1.0/groups/{id}/members Who a role-assignable group passes its role to6
Azure assignments and eligibility GET {scope}/providers/Microsoft.Authorization/roleAssignmentSchedules and roleEligibilitySchedules, api-version=2020-10-01 Active and eligible Azure roles; Learn’s PowerShell for the same list uses the eligibility and assignment schedule cmdlets7 8

The tier comes from the program, not from Microsoft. Microsoft marks some Entra roles PRIVILEGED, but the label is a preview and listing it through Graph uses the beta endpoint;9 Book 04 treats it as an input to tiering, not the tier itself. The example keeps Book 04’s tiers in config/tiers.json, keyed by role template ID, which is the same in every tenant (Appendix B). A role that isn’t in the file is reported as unclassified, never assumed safe. Design guidance.

Which cloud, for this read

For the target estate, Microsoft 365 GCC with commercial Azure, every host is a worldwide one: Graph at graph.microsoft.com and Resource Manager at management.azure.com. GCC High uses graph.microsoft.us, DoD dod-graph.microsoft.us, and both use Azure Government’s management.usgovcloudapi.net.10 11 Microsoft Entra ID P1 and P2 for GCC are hosted in the Azure commercial environment,12 and Microsoft Entra ID Governance, which PIM needs, is available in GCC, GCC High and DoD.13 Every Graph call in this chapter lists the global service, US Government L4 and US Government L5 in its national cloud table.14 15 16 17 The code takes gcc as an explicit cloud name and refuses any name it doesn’t know, the pattern the first workbook’s Chapter 02 sets.

Start with Microsoft’s sample

What Microsoft gives you

Learn’s page List Microsoft Entra role assignments is Microsoft’s procedure for this read. It covers the admin center, Microsoft Graph PowerShell and the Graph API: assignments for one role, across all roles, for a principal (direct and, through a beta API, transitive), for a group, and at administrative unit scope.18 Its all-roles PowerShell sample reads every role definition with Get-MgRoleManagementDirectoryRoleDefinition and then lists each role’s assignments with Get-MgRoleManagementDirectoryRoleAssignment. The admin center also downloads all active assignments as a CSV through a bulk operation, which Learn says runs for up to an hour and has limits in large tenants.19

Learn’s Azure page for the same question, Eligible and time-bound role assignments in Azure RBAC, says there’s no single PowerShell command for both kinds and lists eligible assignments with Get-AzRoleEligibilitySchedule and active ones with Get-AzRoleAssignmentSchedule.20 The example’s Resource Manager calls are the REST form of those two cmdlets.

The pinned copy

The samples exist only inside the Learn page, so the repository pins the page’s source, unmodified, in examples/third_party/entra-role-assignments-page: view-assignments.md from MicrosoftDocs/entra-docs at commit 07cb73d (October 30, 2026), retrieved October 30, 2026, with the repository’s MIT licenses.21 snapshot.json records each file’s SHA-256, checked against the upstream git blob hashes, and a unit test checks them. The weekly source watch reports new commits to the page. Updating the copy is a reviewed pull request.

tests/LearnSampleHarness.ps1 extracts the PowerShell samples from the pinned page at test time and runs them unmodified against stand-ins for Connect-MgGraph and the two cmdlets. The stand-ins record their parameters and answer from the fixture tenant’s active assignments, which is what the List unifiedRoleAssignments API returns.22 Nothing is installed and nothing signs in.

What it leaves to you

Running the samples against the fixture tenant shows what they do and what they leave out. Each row is pinned by a test: in tests/MicrosoftSamples.Tests.ps1 for the sample, and in tests/PrivilegedInventory.Tests.ps1 for the example.

Learn’s sample, as published Why it matters What examples/privileged-access adds
Lists every active assignment across all roles — Kept as the model, through roleAssignmentScheduleInstances, which returns the same active assignments with assignmentType and endDateTime23
Sees no eligible assignment: the fixture’s eligible-only holders are missing Under PIM almost every human holder is eligible, so an active-only list shrinks as the project succeeds and stops being an inventory Reads roleEligibilitySchedules and reports permanent eligibility (PA-INV-06)
One assignment call per role definition, plus one for the definitions More than a hundred calls for one list, each a chance to be throttled One paged call per collection
Passes neither -All nor -PageSize The cmdlet’s reference lists -All as the switch that lists all pages, off by default,24 so each call can stop at the first page Follows @odata.nextLink and Resource Manager’s nextLink, only on the cloud’s own host, as Learn’s paging guidance asks25
Returns a role-assignable group as one principal and never reads its members The people who hold the role are invisible Expands each group once, marks member rows Via Group, and never drops a member it can’t name (PA-INV-09)
Prints role definition and principal IDs; no names, no tier A reviewer can’t act on a GUID Names from the definitions and the expanded principal, a tier from config/tiers.json, and Unclassified for anything else (PA-INV-05)
Signs in with Connect-MgGraph and no environment or scopes The worldwide cloud and whatever scopes the session already has An explicit cloud, the call list in config/calls.json with each call’s least-privileged permission, and a refusal of any call the task doesn’t declare
Reads transitive assignments from beta, on a hard-coded graph.microsoft.com Beta can change;26 the host is wrong for GCC High and DoD v1.0 only, with the host from the cloud table
Nothing for Azure Owner, User Access Administrator and Contributor are privileged too27 Resource Manager schedules by scope, placed in tiers by role and scope, with PA-INV-07 and PA-INV-08

The verdict. Use Learn’s page as the model for what to read and as the admin center’s cross-check (Lab 2 compares the two). Don’t use its all-roles loop for the inventory: it can’t see eligibility, which is the state the project is moving every human assignment into.

The code: examples/privileged-access

examples/privileged-access is new. At this chapter it is one PowerShell module, powershell/PrivilegedAccess/PrivilegedAccess.psm1, two entry scripts, the call list and the tier map; later chapters add their own modules. This chapter’s entry script is Invoke-PaInventory.ps1.

The reader

Every call goes through one reader, built for one task in one cloud: New-PaReader -Cloud gcc -Task inventory -Invoke <script block>. The script block is the only thing that touches the network: New-PaLiveInvoker for a real run, which uses Invoke-MgGraphRequest and Invoke-AzRestMethod and refuses to start if either session is signed in to another cloud, or New-PaFixtureInvoker for labs and tests. The reader enforces four rules:

  • Only declared calls. config/calls.json lists each call with its method, version, path, least-privileged permission (or Azure role), stage and Learn page. A call the task doesn’t declare throws before anything is sent.
  • GET only. The reader refuses any other method, and so does the fake.
  • One host. Every URL, including every next link, must start with the cloud’s own Graph or Resource Manager host, or the token isn’t sent.
  • Every page. Collections are followed to the end. The fake serves pages of two by default, so the tests exercise paging on every collection.

The inventory’s permissions are the union of its calls:

Call Least-privileged permission Delegated role
List roleDefinitions RoleManagement.Read.Directory28 Directory Readers, Global Reader or Privileged Role Administrator
List roleAssignmentScheduleInstances RoleAssignmentSchedule.Read.Directory29 Global Reader (read)
List roleEligibilitySchedules RoleEligibilitySchedule.Read.Directory30 Global Reader (read)
List group members GroupMember.ReadBasic.All, with User.ReadBasic.All so user members come back with names31 32 Group owners or members, or a role with members read
Azure schedules Reader on each scope (*/read)33 —

The User.ReadBasic.All row matters. Learn says that when an app can read a group’s membership but not a member’s object type, the member comes back with only its id and @odata.type.34 The example asks for the extra permission, and if a member still comes back unnamed it keeps the row and raises PA-INV-09 rather than dropping someone who holds a role.

The rows

Get-PaPrivilegedInventory returns one row per assignment: plane (Entra or Azure), role, tier, scope, state (ActivePermanent, ActiveTimeBound, Activated, Eligible or EligiblePermanent), end date, principal, and whether it came directly or through a group. Active instances with memberType Group are skipped, because the example expands the group itself and would otherwise count its members twice.35 Azure schedules are read for each scope you list and de-duplicated by ID, because a list at one scope also returns schedules above and below it; schedules with memberType Inherited are skipped for the same reason (research gaps, row 3).

Azure rows get their tier from Book 04, Chapter 04’s placement table: a role that can assign roles at root or management group scope is T0, any privileged administrator role on a subscription you list as control plane (identity, PIM evidence, the log workspace) is T0, Owner or Contributor on a workload subscription is T1, other roles are T2, and Reader is read-only. Learn’s privileged administrator roles are Owner, Contributor, Reservations Administrator, Role Based Access Control Administrator and User Access Administrator.36 Design guidance for the placements.

The findings

Test-PaInventory turns the rows into findings. Two thresholds are Learn’s; the rest are the program’s.

Rule Severity What it flags Source
PA-INV-01 Finding (Info when on the exceptions list) Permanent active T0 or T1, in Entra or Azure, outside exceptions.csv; an exception with no owner doesn’t count Learn: zero permanently active assignments except the emergency accounts37
PA-INV-02 Warn An Entra role assigned straight to a user Design guidance (roles reach people through PRV groups); Learn recommends groups for role assignments38
PA-INV-03 Warn A group holding a role that isn’t named PRV-<tier>-... for that tier Design guidance
PA-INV-04 Finding Five or more principals with Global Administrator, active or eligible, group members counted; or fewer than two users Learn: fewer than five;39 CISA’s MS.AAD.7.1v1: at least two40
PA-INV-05 Warn A role that isn’t in the tier map Design guidance
PA-INV-06 Warn Eligibility with no end date Design guidance (Appendix B bounds eligibility by tier)
PA-INV-07 Finding User Access Administrator at root scope (/), which is what elevate access grants Learn: a Global Administrator who elevates gets User Access Administrator on all subscriptions;41 Book 04 treats it as a Tier 0 event
PA-INV-08 Finding More than three Owners on a subscription Learn: a maximum of three subscription owners42
PA-INV-09 Warn A group member returned with ID and type only Learn: limited information for inaccessible member objects43

The run and its manifest

Invoke-PaInventory.ps1 reads, classifies, finds, and writes inventory.json, inventory.csv, findings.json and manifest.json. The manifest records the task, the mode (Fixture or Live), the cloud, the tenant ID if you give one, the permissions the task needs, every request with its time, and the SHA-256 of each file. It names no one; the inventory does, so treat the run’s folder as sensitive.

The tests

Pester 5 under PowerShell 7; the kit runs 330 tests, none needing a module, a tenant or a network. This chapter’s are:

  • tests/MicrosoftSamples.Tests.ps1, 9 tests in the Chapter 01 block: Learn’s samples, run as published.
  • tests/PrivilegedInventory.Tests.ps1, 33 tests: clouds and the call list, refused calls and hosts, paging with page sizes of 1, 2 and 50, eligible and activated states, group expansion, tiers, Azure de-duplication, each finding, the whole fixture’s counts, and the manifest’s hashes.

The other 7 sample tests and the 29 in tests/EmergencyAccess.Tests.ps1 are Chapter 02’s.

ImportantIllustrative code

The commands below run Learn’s pinned samples against stand-ins, and the example against a fictional tenant served by a fake. They show what the code does, not that a tenant returns the same shapes. Review the code, and run Lab 2 in a test tenant, before it reads a real one.

Lab 1: fixtures, no tenant

You need PowerShell 7.1 or later with Pester 5. Work in examples/privileged-access.

NoteEvery command in Lab 1

Every command in this lab, in order, from examples/privileged-access. Each step below says what it does and what to look for.

# Step 1: Run Learn's samples against the stand-ins
Invoke-Pester -Path tests/MicrosoftSamples.Tests.ps1 -FullNameFilter '*Chapter 01*' -Output None -PassThru |
    Select-Object PassedCount, FailedCount, NotRunCount

# Step 2: List the permissions the inventory needs
Import-Module ./powershell/PrivilegedAccess/PrivilegedAccess.psm1
(Get-PaRequiredPermission -Task inventory).GraphPermissions -join ', '

# Step 3: Run the inventory against the fictional tenant
./Invoke-PaInventory.ps1 -FixturePath tests/fixtures/tenant.json -OutputPath out/inventory `
    -AzureScope /subscriptions/11111111-1111-4111-8111-111111111111, /providers/Microsoft.Management/managementGroups/contoso-root, /subscriptions/22222222-2222-4222-8222-222222222222 `
    -ExceptionsPath tests/fixtures/inputs/exceptions.csv -ControlPlaneSubscriptionId 22222222-2222-4222-8222-222222222222

# Step 4: Read the findings
Get-Content out/inventory/findings.json -Raw | ConvertFrom-Json | Where-Object Severity -eq 'Finding' | Format-Table Rule, Object

# Step 5: See the reader refuse
$r = New-PaReader -Cloud gcc -Task inventory -Invoke (New-PaFixtureInvoker -Path tests/fixtures/tenant.json).Invoke
Invoke-PaList -Reader $r -CallId ca-policies
Get-PaCloud GCCHigh

# Step 6: Run the tests
Invoke-Pester -Path tests

# After the steps
Remove-Item -Recurse -Force out
  1. Run Learn’s samples against the stand-ins:

    Invoke-Pester -Path tests/MicrosoftSamples.Tests.ps1 -FullNameFilter '*Chapter 01*' -Output None -PassThru |
        Select-Object PassedCount, FailedCount, NotRunCount

    Terminal window showing Invoke-Pester -Path tests/MicrosoftSamples.Tests.ps1 -FullNameFilter '*Chapte… and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    PassedCount 9, FailedCount 0, NotRunCount 7 (the seven are Chapter 02’s). Open the test file and read the second test: the fixture’s eligible-only holders never appear in the sample’s output.

  2. List the permissions the inventory needs:

    Import-Module ./powershell/PrivilegedAccess/PrivilegedAccess.psm1
    (Get-PaRequiredPermission -Task inventory).GraphPermissions -join ', '

    Terminal window showing Import-Module ./powershell/PrivilegedAccess/PrivilegedAccess.psm1 and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    GroupMember.ReadBasic.All, RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory, User.ReadBasic.All. Nothing ends in ReadWrite.

  3. Run the inventory against the fictional tenant:

    ./Invoke-PaInventory.ps1 -FixturePath tests/fixtures/tenant.json -OutputPath out/inventory `
        -AzureScope /subscriptions/11111111-1111-4111-8111-111111111111, /providers/Microsoft.Management/managementGroups/contoso-root, /subscriptions/22222222-2222-4222-8222-222222222222 `
        -ExceptionsPath tests/fixtures/inputs/exceptions.csv -ControlPlaneSubscriptionId 22222222-2222-4222-8222-222222222222
    Assignments: 30 rows (Azure T0=4, Azure T1=5, Azure T2=1, Entra T0=8, Entra T1=2, Entra T2=2, Entra Unclassified=1)
    Findings: 10 Finding, 7 Warn, 1 Info
    Written to out/inventory (manifest.json lists every request and file hash)

    Terminal window showing ./Invoke-PaInventory.ps1 -FixturePath tests/fixtures/tenant.json -OutputPath … and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)
  4. Read the findings:

    Get-Content out/inventory/findings.json -Raw | ConvertFrom-Json | Where-Object Severity -eq 'Finding' | Format-Table Rule, Object

    Terminal window showing Get-Content out/inventory/findings.json -Raw | ConvertFrom-Json | Where-Objec… and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    Seven PA-INV-01 rows (a standing Global Administrator, the mail automation service principal whose exception has no owner, two standing subscription Owners, a standing User Access Administrator at the management group, the same Global Administrator’s User Access Administrator at root, and a log-export service principal with standing Contributor on the control-plane subscription), PA-INV-07 for the root-scope elevation, PA-INV-04 (five Global Administrators, one of them through PRV-T0-GlobalAdmin), and PA-INV-08 (five Owners on the workload subscription). Azure rows show object IDs, because Resource Manager returns IDs, not names. The emergency account ea01 is Info, not a Finding: it’s on the exceptions list with an owner.

  5. See the reader refuse:

    $r = New-PaReader -Cloud gcc -Task inventory -Invoke (New-PaFixtureInvoker -Path tests/fixtures/tenant.json).Invoke
    Invoke-PaList -Reader $r -CallId ca-policies
    Get-PaCloud GCCHigh

    Terminal window showing $r = New-PaReader -Cloud gcc -Task inventory -Invoke (New-PaFixtureInvoker -P… and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    Two errors: Undeclared call 'ca-policies' for task 'inventory'; add it to config/calls.json with its permission. and Unknown cloud 'GCCHigh'. Use global, gcc, gcchigh or dod.

  6. Run the tests:

    Invoke-Pester -Path tests

    Terminal window showing Invoke-Pester -Path tests and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

Expected result: 330 tests passing, every row of the gap table seen with your own eyes, and out/inventory/manifest.json listing 24 GET requests.

When you’re done, delete out:

Remove-Item -Recurse -Force out

Terminal window showing Remove-Item -Recurse -Force out and what it printed

What the terminal shows, run on the kit’s fixtures (the same output as text)

Lab 2: test tenant

Use a test tenant in the worldwide or GCC cloud with a test Azure subscription, never production, with fictional people. The run only reads. Work in examples/privileged-access. You’ll replace: <tenant-id> (the test tenant’s ID), <app-id> (the application (client) ID of the app you register in step 1), <thumbprint> (the thumbprint of the app’s certificate, installed in your certificate store) and <test-subscription-id> (the test subscription’s ID).

NoteEvery command in Lab 2

Every command in this lab, in order, from examples/privileged-access. Each step below says what it does and what to look for.

# Step 4: Sign in and run
Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint>
Connect-AzAccount -ServicePrincipal -Environment AzureCloud -Tenant <tenant-id> -ApplicationId <app-id> -CertificateThumbprint <thumbprint>
./Invoke-PaInventory.ps1 -Cloud gcc -OutputPath out/inventory-test -AzureScope /subscriptions/<test-subscription-id> -TenantId <tenant-id>

# Step 5: Cross-check against the admin center
Import-Csv out/inventory-test/inventory.csv | Where-Object State -Like 'Active*' | Format-Table RoleName, PrincipalName, State
Import-Csv out/inventory-test/inventory.csv | Where-Object State -Like 'Eligible*' | Format-Table RoleName, PrincipalName, State, Via

# Step 6: Check the two things the fixture assumed
$rows = Get-Content out/inventory-test/inventory.json -Raw | ConvertFrom-Json
$rows | Where-Object { $_.Plane -eq 'Entra' -and $_.PrincipalType -eq 'unknown' } | Format-Table RoleName, State, PrincipalId
$rows | Where-Object Plane -EQ 'Azure' | Group-Object RoleId, Scope, State, PrincipalId | Where-Object Count -GT 1 | Format-Table Count, Name

# Step 7: Back it out
Remove-Item -Recurse -Force out/inventory-test
  1. Register the app in the tenant’s own cloud. For a GCC tenant that’s the worldwide Microsoft Entra admin center. Add a certificate credential, not a secret.

    1. Open Entra ID > App registrations > New registration, name the app and select Register.
    2. In the app, open Certificates & secrets > Certificates > Upload certificate and upload the certificate’s public key.
  2. Grant the five application permissions from Lab 1, step 2, and grant admin consent. Nothing else.

    1. In the app, open API permissions > Add a permission > Microsoft Graph > Application permissions.
    2. Select GroupMember.ReadBasic.All, RoleAssignmentSchedule.Read.Directory, RoleEligibilitySchedule.Read.Directory, RoleManagement.Read.Directory and User.ReadBasic.All, then Add permissions.
    3. Select Grant admin consent for your tenant.
  3. Grant Reader to the app’s service principal on the test subscription. In the Azure portal, open Subscriptions > the test subscription > Access control (IAM) > Add > Add role assignment, select Reader, then on Members select the app, and Review + assign.

  4. Sign in and run:

    Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint>
    Connect-AzAccount -ServicePrincipal -Environment AzureCloud -Tenant <tenant-id> -ApplicationId <app-id> -CertificateThumbprint <thumbprint>
    ./Invoke-PaInventory.ps1 -Cloud gcc -OutputPath out/inventory-test -AzureScope /subscriptions/<test-subscription-id> -TenantId <tenant-id>
  5. Cross-check against the admin center. On Roles and administrators, select Download assignments for all roles, as Learn’s procedure describes.44 Every active row in the CSV should be in inventory.csv, and inventory.csv should hold the eligible rows the CSV doesn’t. These list the inventory’s active and eligible rows side by side:

    Import-Csv out/inventory-test/inventory.csv | Where-Object State -Like 'Active*' | Format-Table RoleName, PrincipalName, State
    Import-Csv out/inventory-test/inventory.csv | Where-Object State -Like 'Eligible*' | Format-Table RoleName, PrincipalName, State, Via
  6. Check the two things the fixture assumed. In inventory.json, rows read with $expand=principal should have a principal type (research gaps, row 2), and each Azure schedule should appear once. The first command lists Entra rows whose principal type came back unknown (what the example writes when it can’t read one), the second any Azure row that appears more than once; both should print nothing:

    $rows = Get-Content out/inventory-test/inventory.json -Raw | ConvertFrom-Json
    $rows | Where-Object { $_.Plane -eq 'Entra' -and $_.PrincipalType -eq 'unknown' } | Format-Table RoleName, State, PrincipalId
    $rows | Where-Object Plane -EQ 'Azure' | Group-Object RoleId, Scope, State, PrincipalId | Where-Object Count -GT 1 | Format-Table Count, Name
  7. Back it out. Remove the Reader assignment (Access control (IAM) > Role assignments, select the app’s row, Remove) and the five permissions, delete the app registration (Entra ID > App registrations > the app > Delete) and its certificate, and delete out/inventory-test: it holds the tenant’s privileged holders.

    Remove-Item -Recurse -Force out/inventory-test

Verified facts

Item Value US Government clouds Stage Source
List roleDefinitions GET /roleManagement/directory/roleDefinitions; RoleManagement.Read.Directory L4 and L5 v1.0 45
List roleAssignmentScheduleInstances Active assignments made through PIM or directly; RoleAssignmentSchedule.Read.Directory; Global Reader for a delegated read L4 and L5 v1.0 46
Active instance properties assignmentType Assigned or Activated; memberType Inherited, Direct or Group; endDateTime — v1.0 47
List roleEligibilitySchedules RoleEligibilitySchedule.Read.Directory; scheduleInfo.expiration L4 and L5 v1.0 48
List unifiedRoleAssignments (Learn’s sample) Active assignments; RoleManagement.Read.Directory L4 and L5 v1.0 49
Transitive role assignments (Learn’s sample) /beta/roleManagement/directory/transitiveRoleAssignments — Beta 50 51
List group members GroupMember.ReadBasic.All; members of unreadable types return id and @odata.type only; User.ReadBasic.All for user members’ basic properties; known issue: v1.0 doesn’t return service principal members L4 and L5 v1.0 52 53 54
Privileged label isPrivileged on role definitions, listed through the beta endpoint — Preview 55
Global Administrators Fewer than five — — 56
Permanent active assignments Zero, except the emergency access accounts — — 57
Azure eligibility and active schedules Get-AzRoleEligibilitySchedule, Get-AzRoleAssignmentSchedule; no single command lists both — GA 58
Azure PIM REST types Role assignment and eligibility schedules GA in 2020-10-01 Azure Government host management.usgovcloudapi.net GA 59 60
Azure privileged administrator roles Owner, Contributor, Reservations Administrator, Role Based Access Control Administrator, User Access Administrator, with their IDs — — 61
Subscription owners A maximum of three — — 62
Elevate access A Global Administrator who elevates gets User Access Administrator on all subscriptions — — 63
Licensing and clouds Entra ID P1 and P2 for GCC hosted in Azure commercial; Entra ID Governance in GCC, GCC High and DoD GCC High and DoD in Azure Government — 64 65

No two Learn pages read for this chapter disagree on a fact the code depends on. The paging question first raised on October 30, 2026 (research gaps, row 1) is narrower now: the full reference page for Get-MgRoleManagementDirectoryRoleAssignment lists both -PageSize and -All, and describes -All as listing all pages, with a default of off.66 Learn doesn’t state in words what a call without -All returns; the switch’s default is the evidence that the sample’s loop can stop at the first page of each role. The example doesn’t depend on the answer, because it reads through Graph and follows the next links itself.

One Learn caveat touches the group expansion. The List group members page carries a known issue: on v1.0, the call doesn’t return service principals that are members of the group, and Learn’s workarounds are the beta endpoint or /groups/{id}?$expand=members.67 68 The example reads members through v1.0, so a service principal placed in a role-assignable group isn’t in the inventory and PA-INV-09 doesn’t fire for it. Neither workaround fits the call list as it stands (beta isn’t allowed, and Learn’s query limits say $expand on directory-object relationships returns at most 20 objects with no next link69), so the gap is recorded rather than closed here (research gaps, row 91).

Federal requirements and evidence

The inventory is the evidence for the plan’s Gate 0, “standing administrative privilege inventoried and classified by tier.” Each row names the command or file that shows it. Every control named is in the NIST SP 800-53B Moderate baseline.70

What the output proves Requirement it supports KSI it aligns to Who keeps it
Learn’s sample misses eligible holders, so the kit doesn’t use it as the inventory, and the kit’s own code passes its tests: Lab 1, steps 1 and 6 (Invoke-Pester) CM-3(2), testing and validation of changes KSI-CMT-VTD The repository’s test runs
The reader asks for five read permissions and nothing that writes: Lab 1, step 2 (Get-PaRequiredPermission -Task inventory) AC-6, least privilege for the identity that reads KSI-IAM-ELP Identity engineering, with the app’s consent record
Every Entra and Azure role assignment, active and eligible, direct or through a group, with a tier: inventory.json and inventory.csv from Lab 1, step 3 AC-2, account management (who holds which role), and AC-6(5), privileged accounts restricted to defined roles KSI-IAM-JIT The ISSO; the folder names people, so it goes to the access-controlled evidence store
Standing Tier 0 and Tier 1 privilege outside an owned exception, and root-scope elevation: PA-INV-01 and PA-INV-07 in findings.json, Lab 1, steps 3 and 4 AC-6 and AC-6(5); CISA BOD 25-01’s MS.AAD.7.4v1, no permanent active assignment to a highly privileged role, which allows emergency access accounts as an exception71 72 KSI-IAM-JIT, KSI-IAM-ELP The ISSO; exceptions.csv stays with the governance office
Five or more Global Administrators, or fewer than two users, group members counted: PA-INV-04 in findings.json AC-6(5), privileged accounts restricted to defined roles; MS.AAD.7.1v1, at least two and at most eight Global Administrators (BOD 25-01);73 the code’s ceiling is Learn’s fewer than five, which is stricter; its floor is the baseline’s two KSI-IAM-JIT The ISSO
Roles held outside PRV groups, unclassified roles, eligibility with no end, more than three subscription Owners, and members returned without a name: PA-INV-02, 03, 05, 06, 08 and 09 in findings.json AC-2 and AC-6(5); the PRV-group rule and the tiers are the program’s (design guidance) KSI-IAM-JIT Identity engineering, who owns each decision
Which calls produced the files, when, with which permissions, and the SHA-256 of each file: manifest.json from Lab 1, step 3 AU-9, protection of audit information; using hashes to show nothing changed after the run is design guidance KSI-MLA-OSM The ISSO, beside the files it lists
The reader refuses an undeclared call and an unknown cloud: the two errors in Lab 1, step 5 AC-6: the read identity can only make the calls the task declares KSI-IAM-ELP The repository’s test runs
A live read of a test tenant, signed in with a certificate, not a secret: Lab 2, step 4 (Connect-MgGraph -CertificateThumbprint, then Invoke-PaInventory.ps1 -Cloud gcc), with Mode Live and the tenant ID in out/inventory-test/manifest.json IA-5(2), public key-based authentication, for the reader app KSI-IAM-SNU Identity engineering
The inventory holds every active row the admin center downloads, plus the eligible rows it doesn’t: Lab 2, step 5 (Import-Csv out/inventory-test/inventory.csv) AC-2, review of accounts for compliance KSI-IAM-JIT The ISSO, with the admin center’s CSV
No Entra row lacks a principal type and no Azure schedule is counted twice: Lab 2, step 6, both commands printing nothing AC-2: a complete inventory with no double counts KSI-IAM-JIT Identity engineering
The lab copies are deleted because they name every privileged holder: Remove-Item after Lab 1 and in Lab 2, step 7 No control; handling the folder as sensitive is design guidance — —

AC-2(7), which asks for privileged accounts under a role-based or attribute-based scheme, isn’t in the Moderate baseline;74 an agency that tailors it in can point to the same rows. KSIs are FedRAMP 20x indicators for cloud service providers.75 The agency authorizes its own system and lines this evidence up with what its providers report against those indicators; it doesn’t report KSIs itself (Book 00, Appendix B). Book 04, Chapter 10 carries the control-by-control mapping.

Dangers

Danger What happens The guard
An active-only inventory As holders become eligible, the list shrinks and Gate 0 passes on an empty table Eligibility read and reported; a test pins that the sample misses it
A group counted as one holder The people behind PRV-T0-GlobalAdmin aren’t reviewed or counted Groups expanded, members marked Via Group, counted in PA-INV-04
A member dropped because its name wasn’t readable Someone holds a role and isn’t in the inventory Rows kept with the ID; PA-INV-09 says which permission is missing. Not guarded: a service principal member, which v1.0 doesn’t return at all (see Verified facts)
A custom role treated as harmless A custom role with privileged permissions sits outside every tier Unclassified, PA-INV-05, never a default tier
An exception list nobody owns Standing privilege hides behind an “exception” An exception without an owner doesn’t count (PA-INV-01)
Reading only some Azure scopes A management group or subscription you didn’t list isn’t read The manifest lists every request, so the reviewer sees the scopes read; discovering every scope is open (research gaps, row 4)
Counting an Azure assignment twice A schedule returned at two scopes inflates Owner counts De-duplicated by schedule ID; inherited rows skipped
The wrong cloud A GCC High run reads graph.microsoft.com, or the token goes to another host Explicit cloud, no default, host checked on every page, live sessions checked before the run
A broad permission Directory.Read.All or a write permission granted “to make it work” The call list; the test pins the task’s exact permissions, none ReadWrite
The inventory as a leak The folder names every privileged holder The manifest names no one; Lab 2 deletes the output

Checklist

Back to top

Footnotes

  1. Microsoft Learn, “Plan a Privileged Identity Management deployment,” understand PIM, type of assignments, https://learn.microsoft.com/entra/id-governance/privileged-identity-management/pim-deployment-plan (accessed Oct 30, 2026).↩︎

  2. Microsoft Learn, “List roleDefinitions,” https://learn.microsoft.com/graph/api/rbacapplication-list-roledefinitions?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  3. Microsoft Learn, “List roleAssignmentScheduleInstances,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignmentscheduleinstances?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  4. Microsoft Learn, “unifiedRoleAssignmentScheduleInstance resource type,” properties, https://learn.microsoft.com/graph/api/resources/unifiedroleassignmentscheduleinstance?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  5. Microsoft Learn, “List roleEligibilitySchedules,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleeligibilityschedules?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  6. Microsoft Learn, “List group members,” permissions and the known issue for service principals in v1.0, https://learn.microsoft.com/graph/api/group-list-members?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  7. Microsoft Learn, “Eligible and time-bound role assignments in Azure RBAC,” how to list eligible and time-bound role assignments, https://learn.microsoft.com/azure/role-based-access-control/pim-integration (accessed Oct 30, 2026).↩︎

  8. Microsoft Learn, “API versions of Azure RBAC REST APIs,” release notes for 2020-10-01, https://learn.microsoft.com/rest/api/authorization/versions (accessed Oct 30, 2026).↩︎

  9. Microsoft Learn, “Privileged roles and permissions in Microsoft Entra ID (preview),” https://learn.microsoft.com/entra/identity/role-based-access-control/privileged-roles-permissions (accessed Oct 30, 2026).↩︎

  10. Microsoft Learn, “Microsoft Graph national cloud deployments,” https://learn.microsoft.com/graph/deployments (accessed Oct 30, 2026).↩︎

  11. Microsoft Learn, “Compare Azure Government and global Azure,” guidance for developers, https://learn.microsoft.com/azure/azure-government/compare-azure-government-global-azure (accessed Oct 30, 2026; read for the first workbook’s Chapter 02).↩︎

  12. Microsoft Learn, “Enterprise Mobility + Security for US Government service description,” EMS for US GCC customers, https://learn.microsoft.com/enterprise-mobility-security/solutions/ems-govt-service-description (accessed Oct 30, 2026).↩︎

  13. Microsoft Learn, “Microsoft Entra feature availability,” other Microsoft Entra products, https://learn.microsoft.com/entra/identity/authentication/feature-availability (accessed Oct 30, 2026).↩︎

  14. Microsoft Learn, “List roleAssignmentScheduleInstances,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignmentscheduleinstances?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  15. Microsoft Learn, “List roleEligibilitySchedules,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleeligibilityschedules?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  16. Microsoft Learn, “List roleDefinitions,” https://learn.microsoft.com/graph/api/rbacapplication-list-roledefinitions?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  17. Microsoft Learn, “List unifiedRoleAssignments,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignments?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  18. Microsoft Learn, “List Microsoft Entra role assignments,” https://learn.microsoft.com/entra/identity/role-based-access-control/view-assignments (accessed Oct 30, 2026).↩︎

  19. Microsoft Learn, “List Microsoft Entra role assignments,” https://learn.microsoft.com/entra/identity/role-based-access-control/view-assignments (accessed Oct 30, 2026).↩︎

  20. Microsoft Learn, “Eligible and time-bound role assignments in Azure RBAC,” how to list eligible and time-bound role assignments, https://learn.microsoft.com/azure/role-based-access-control/pim-integration (accessed Oct 30, 2026).↩︎

  21. Microsoft, MicrosoftDocs/entra-docs, docs/identity/role-based-access-control/view-assignments.md at commit 07cb73d6c6380414eecd040ba5e14c1c94d48b66 (October 30, 2026; the page last changed February 24, 2026), MIT License, https://github.com/MicrosoftDocs/entra-docs/blob/07cb73d6c6380414eecd040ba5e14c1c94d48b66/docs/identity/role-based-access-control/view-assignments.md (accessed Oct 30, 2026); pinned copy in examples/third_party/entra-role-assignments-page.↩︎

  22. Microsoft Learn, “List unifiedRoleAssignments,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignments?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  23. Microsoft Learn, “List roleAssignmentScheduleInstances,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignmentscheduleinstances?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  24. Microsoft Learn, “Get-MgRoleManagementDirectoryRoleAssignment,” syntax and the -All and -PageSize parameters, https://learn.microsoft.com/powershell/module/microsoft.graph.identity.governance/get-mgrolemanagementdirectoryroleassignment (accessed Oct 31, 2026).↩︎

  25. Microsoft Learn, “Page through a collection using the Microsoft Graph SDKs,” https://learn.microsoft.com/graph/sdks/paging (accessed Oct 30, 2026; read for the first workbook’s Chapter 02).↩︎

  26. Microsoft Learn, “Versioning, support, and breaking change policies for Microsoft Graph,” https://learn.microsoft.com/graph/versioning-and-support (accessed Oct 30, 2026; read for the first workbook’s Chapter 02).↩︎

  27. Microsoft Learn, “Azure built-in roles,” privileged and general, https://learn.microsoft.com/azure/role-based-access-control/built-in-roles, and “Azure built-in roles for Privileged,” https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/privileged (accessed Oct 30, 2026).↩︎

  28. Microsoft Learn, “List roleDefinitions,” https://learn.microsoft.com/graph/api/rbacapplication-list-roledefinitions?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  29. Microsoft Learn, “List roleAssignmentScheduleInstances,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignmentscheduleinstances?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  30. Microsoft Learn, “List roleEligibilitySchedules,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleeligibilityschedules?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  31. Microsoft Learn, “List group members,” permissions and the known issue for service principals in v1.0, https://learn.microsoft.com/graph/api/group-list-members?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  32. Microsoft Learn, “Overview of Microsoft Graph permissions,” limited information returned for inaccessible member objects, https://learn.microsoft.com/graph/permissions-overview (accessed Oct 30, 2026).↩︎

  33. Microsoft Learn, “Azure built-in roles,” privileged and general, https://learn.microsoft.com/azure/role-based-access-control/built-in-roles, and “Azure built-in roles for Privileged,” https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/privileged (accessed Oct 30, 2026).↩︎

  34. Microsoft Learn, “Overview of Microsoft Graph permissions,” limited information returned for inaccessible member objects, https://learn.microsoft.com/graph/permissions-overview (accessed Oct 30, 2026).↩︎

  35. Microsoft Learn, “unifiedRoleAssignmentScheduleInstance resource type,” properties, https://learn.microsoft.com/graph/api/resources/unifiedroleassignmentscheduleinstance?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  36. Microsoft Learn, “Azure built-in roles,” privileged and general, https://learn.microsoft.com/azure/role-based-access-control/built-in-roles, and “Azure built-in roles for Privileged,” https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/privileged (accessed Oct 30, 2026).↩︎

  37. Microsoft Learn, “Plan a Privileged Identity Management deployment,” understand PIM, type of assignments, https://learn.microsoft.com/entra/id-governance/privileged-identity-management/pim-deployment-plan (accessed Oct 30, 2026).↩︎

  38. Microsoft Learn, “Best practices for Microsoft Entra roles,” sections 5 to 7, https://learn.microsoft.com/entra/identity/role-based-access-control/best-practices (accessed Oct 30, 2026).↩︎

  39. Microsoft Learn, “Best practices for Microsoft Entra roles,” sections 5 to 7, https://learn.microsoft.com/entra/identity/role-based-access-control/best-practices (accessed Oct 30, 2026).↩︎

  40. Cybersecurity and Infrastructure Security Agency, ScubaGear Microsoft Entra ID secure configuration baseline (aad.md), MS.AAD.7.1v1 (two to eight users, active or eligible, group members counted), MS.AAD.7.4v1 and its note that emergency access accounts needing perpetual access are an exception, and the list of highly privileged roles, https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/aad.md (accessed Nov 2, 2026).↩︎

  41. Microsoft Learn, “Azure roles, Microsoft Entra roles, and classic subscription administrator roles,” do Azure roles and Microsoft Entra roles overlap, https://learn.microsoft.com/azure/role-based-access-control/rbac-and-directory-admin-roles (accessed Oct 30, 2026).↩︎

  42. Microsoft Learn, “Best practices for Azure RBAC,” limit the number of subscription owners, https://learn.microsoft.com/azure/role-based-access-control/best-practices (accessed Oct 30, 2026).↩︎

  43. Microsoft Learn, “Overview of Microsoft Graph permissions,” limited information returned for inaccessible member objects, https://learn.microsoft.com/graph/permissions-overview (accessed Oct 30, 2026).↩︎

  44. Microsoft Learn, “List Microsoft Entra role assignments,” https://learn.microsoft.com/entra/identity/role-based-access-control/view-assignments (accessed Oct 30, 2026).↩︎

  45. Microsoft Learn, “List roleDefinitions,” https://learn.microsoft.com/graph/api/rbacapplication-list-roledefinitions?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  46. Microsoft Learn, “List roleAssignmentScheduleInstances,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignmentscheduleinstances?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  47. Microsoft Learn, “unifiedRoleAssignmentScheduleInstance resource type,” properties, https://learn.microsoft.com/graph/api/resources/unifiedroleassignmentscheduleinstance?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  48. Microsoft Learn, “List roleEligibilitySchedules,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleeligibilityschedules?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  49. Microsoft Learn, “List unifiedRoleAssignments,” https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignments?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  50. Microsoft Learn, “List Microsoft Entra role assignments,” https://learn.microsoft.com/entra/identity/role-based-access-control/view-assignments (accessed Oct 30, 2026).↩︎

  51. Microsoft Learn, “Versioning, support, and breaking change policies for Microsoft Graph,” https://learn.microsoft.com/graph/versioning-and-support (accessed Oct 30, 2026; read for the first workbook’s Chapter 02).↩︎

  52. Microsoft Learn, “List group members,” permissions and the known issue for service principals in v1.0, https://learn.microsoft.com/graph/api/group-list-members?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  53. Microsoft Learn, “Overview of Microsoft Graph permissions,” limited information returned for inaccessible member objects, https://learn.microsoft.com/graph/permissions-overview (accessed Oct 30, 2026).↩︎

  54. Microsoft Learn, “Known issues in Microsoft Graph,” GET /groups/{id}/members doesn’t return service principals in v1.0, and query parameter limitations for $expand, https://learn.microsoft.com/graph/known-issues#get-groupsidmembers-doesnt-return-service-principals-in-v10 (accessed Oct 31, 2026).↩︎

  55. Microsoft Learn, “Privileged roles and permissions in Microsoft Entra ID (preview),” https://learn.microsoft.com/entra/identity/role-based-access-control/privileged-roles-permissions (accessed Oct 30, 2026).↩︎

  56. Microsoft Learn, “Best practices for Microsoft Entra roles,” sections 5 to 7, https://learn.microsoft.com/entra/identity/role-based-access-control/best-practices (accessed Oct 30, 2026).↩︎

  57. Microsoft Learn, “Plan a Privileged Identity Management deployment,” understand PIM, type of assignments, https://learn.microsoft.com/entra/id-governance/privileged-identity-management/pim-deployment-plan (accessed Oct 30, 2026).↩︎

  58. Microsoft Learn, “Eligible and time-bound role assignments in Azure RBAC,” how to list eligible and time-bound role assignments, https://learn.microsoft.com/azure/role-based-access-control/pim-integration (accessed Oct 30, 2026).↩︎

  59. Microsoft Learn, “API versions of Azure RBAC REST APIs,” release notes for 2020-10-01, https://learn.microsoft.com/rest/api/authorization/versions (accessed Oct 30, 2026).↩︎

  60. Microsoft Learn, “Compare Azure Government and global Azure,” guidance for developers, https://learn.microsoft.com/azure/azure-government/compare-azure-government-global-azure (accessed Oct 30, 2026; read for the first workbook’s Chapter 02).↩︎

  61. Microsoft Learn, “Azure built-in roles,” privileged and general, https://learn.microsoft.com/azure/role-based-access-control/built-in-roles, and “Azure built-in roles for Privileged,” https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/privileged (accessed Oct 30, 2026).↩︎

  62. Microsoft Learn, “Best practices for Azure RBAC,” limit the number of subscription owners, https://learn.microsoft.com/azure/role-based-access-control/best-practices (accessed Oct 30, 2026).↩︎

  63. Microsoft Learn, “Azure roles, Microsoft Entra roles, and classic subscription administrator roles,” do Azure roles and Microsoft Entra roles overlap, https://learn.microsoft.com/azure/role-based-access-control/rbac-and-directory-admin-roles (accessed Oct 30, 2026).↩︎

  64. Microsoft Learn, “Enterprise Mobility + Security for US Government service description,” EMS for US GCC customers, https://learn.microsoft.com/enterprise-mobility-security/solutions/ems-govt-service-description (accessed Oct 30, 2026).↩︎

  65. Microsoft Learn, “Microsoft Entra feature availability,” other Microsoft Entra products, https://learn.microsoft.com/entra/identity/authentication/feature-availability (accessed Oct 30, 2026).↩︎

  66. Microsoft Learn, “Get-MgRoleManagementDirectoryRoleAssignment,” syntax and the -All and -PageSize parameters, https://learn.microsoft.com/powershell/module/microsoft.graph.identity.governance/get-mgrolemanagementdirectoryroleassignment (accessed Oct 31, 2026).↩︎

  67. Microsoft Learn, “List group members,” permissions and the known issue for service principals in v1.0, https://learn.microsoft.com/graph/api/group-list-members?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  68. Microsoft Learn, “Known issues in Microsoft Graph,” GET /groups/{id}/members doesn’t return service principals in v1.0, and query parameter limitations for $expand, https://learn.microsoft.com/graph/known-issues#get-groupsidmembers-doesnt-return-service-principals-in-v10 (accessed Oct 31, 2026).↩︎

  69. Microsoft Learn, “Known issues in Microsoft Graph,” GET /groups/{id}/members doesn’t return service principals in v1.0, and query parameter limitations for $expand, https://learn.microsoft.com/graph/known-issues#get-groupsidmembers-doesnt-return-service-principals-in-v10 (accessed Oct 31, 2026).↩︎

  70. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-6, AC-6(5), AU-9, CM-3(2) and IA-5(2); AC-2(7) isn’t selected), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (accessed Nov 2, 2026).↩︎

  71. Cybersecurity and Infrastructure Security Agency, “BOD 25-01: Implementing Secure Practices for Cloud Services Required Configurations,” Microsoft Entra ID policies MS.AAD.7.1v1 and MS.AAD.7.4v1, https://www.cisa.gov/resources-tools/services/bod-25-01-implementing-secure-practices-cloud-services-required-configurations (accessed Nov 2, 2026).↩︎

  72. Cybersecurity and Infrastructure Security Agency, ScubaGear Microsoft Entra ID secure configuration baseline (aad.md), MS.AAD.7.1v1 (two to eight users, active or eligible, group members counted), MS.AAD.7.4v1 and its note that emergency access accounts needing perpetual access are an exception, and the list of highly privileged roles, https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/aad.md (accessed Nov 2, 2026).↩︎

  73. Cybersecurity and Infrastructure Security Agency, “BOD 25-01: Implementing Secure Practices for Cloud Services Required Configurations,” Microsoft Entra ID policies MS.AAD.7.1v1 and MS.AAD.7.4v1, https://www.cisa.gov/resources-tools/services/bod-25-01-implementing-secure-practices-cloud-services-required-configurations (accessed Nov 2, 2026).↩︎

  74. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-6, AC-6(5), AU-9, CM-3(2) and IA-5(2); AC-2(7) isn’t selected), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (accessed Nov 2, 2026).↩︎

  75. FedRAMP, “Key Security Indicators,” FedRAMP Consolidated Rules for 2026, indicator statements and “Related SP 800-53 Controls” for KSI-CMT-VTD, KSI-IAM-ELP, KSI-IAM-JIT, KSI-IAM-SNU and KSI-MLA-OSM, https://www.fedramp.gov/2026/reference/key-security-indicators/ (accessed Nov 2, 2026).↩︎