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 — Non-Person Identity Inventory as Code

Read every non-person identity into one register as code, starting from Learn’s own commands for finding service accounts and service principals and its script for exporting app registrations’ secrets and certificates (pinned in the repository), and adding what they leave to you: one read of Active Directory’s user-based service accounts, MSAs, gMSAs and dMSAs, app registrations, service principals by type, managed identities and their federated credentials, Graph application permissions and consent grants, each row with an owner count, credential kind, risk, candidate target and stale lead, SQL Server accounts handed to the Data Platform plan, and a manifest with a SHA-256 per file and the tenant ID, with labs against fixtures and a test tenant.

No account can be given an owner, a decision or a new credential until it is on one list, and Microsoft’s own commands give you five lists that don’t agree.

NoteIn this chapter

The read behind the project plan’s Gate 0, “non-person identities inventoried.” You start with Learn’s own commands for finding user accounts used as service accounts, managed service accounts and service principals, and its script for exporting app registrations’ secrets and certificates, which the repository keeps as pinned copies, and run them against stand-ins to see what they return and what they miss. Then you build examples/non-person-identity for what they leave to you: one register of every service account, MSA, gMSA, dMSA, app registration, service principal and managed identity, with owners, credentials, permissions, a risk, a candidate target and a manifest. Lab 1 runs on a laptop with no tenant and no domain; Lab 2 uses a test tenant and a test domain.

TipNarrated tour
Building Non-Person Identity, Chapter 01 — Non-Person Identity Inventory as Code: narrated tour · 12 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 register the plan’s tasks NP0.1, NP0.2, NP0.4 and NP0.5 ask for: “type, host, workload location, risk and candidate target for every identity”, with HIGH-risk accounts listed, stale leads marked for confirmation against sign-in logs, and SQL Server accounts tagged for the Data Platform plan. 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, the identity and Active Directory engineers who run it, and the ISSOs who review it. You should read PowerShell 7 and know what a service principal is. You don’t need a tenant or a domain until Lab 2.

What the register has to hold

The plan’s Gate 0 asks for “every AD service account, MSA, gMSA and dMSA, every app registration, service principal and managed identity, and every AI use case in the current vintage in one register with type, risk and candidate target.” Book 02 gives the types and their targets: a traditional account (a user object, often with an svc- prefix and a password that never expires), a managed service account bound to one server, a group managed service account usable by several, and on Windows Server 2025 domains a delegated managed service account, each with its own path to Entra ID (Book 02, Chapter 09). Learn adds that no user attribute marks an account as a service account: it recommends a naming convention, and finds candidates by three criteria, an SPN, trust for delegation and a password that never expires, warning that “this approach might not find accounts.”1 A dMSA replaces a traditional account, disables the original account’s password, and ties authentication to the machine identities mapped to it.2

On the Entra side, consent and Microsoft’s own services create service principals, so the register is always longer than the list of apps anyone asked for (Book 04, Chapter 05). Learn’s schema gives each service principal a servicePrincipalType: Application, ManagedIdentity, Legacy (an app with credentials but no app registration, usable only in the tenant that created it), ServiceIdentity for an agent identity, and SocialIdp for internal use.3 A managed identity’s service principal carries its Azure resource ID in alternativeNames,4 and a user-assigned identity can hold federated credentials in Resource Manager.5 List applications also returns agent identity blueprints, marked by @odata.type.6

So the register reads two Active Directory collections, eight Graph collections and one Resource Manager one:

What Where Why this one
User accounts used as services Get-ADUser -LDAPFilter with Learn’s three criteria and the naming convention One server-side query instead of three reads of every user7 8
MSAs, gMSAs and dMSAs Get-ADServiceAccount -Filter *, with who may retrieve each password Learn’s command for managed service accounts9 10
App registrations, owners, federated credentials GET /v1.0/applications, /applications/{id}/owners, /applications/{id}/federatedIdentityCredentials Credentials and owners live on the registration11 12 13
Service principals by type GET /v1.0/servicePrincipals; owners of legacy ones Every application, managed identity, legacy app and agent identity14 15
Microsoft Graph application permissions GET /v1.0/servicePrincipals(appId='00000003-…') for its app roles, then /servicePrincipals/{id}/appRoleAssignedTo Which service principal holds which Graph permission, by name16
Delegated consent grants GET /v1.0/oauth2PermissionGrants Tenant-wide consent (AllPrincipals) shows here17
A user-assigned identity’s federated credentials GET {resourceId}/federatedIdentityCredentials?api-version=2024-11-30 They live in Resource Manager, not Graph18

The risk, the candidate target and the stale lead come from the program, not from Microsoft. The plan defines HIGH as “password never expires, over-privileged”; Book 02 adds a password unchanged for more than 365 days and puts the stale threshold at 90 days. The example keeps those rules in config/register.json, and reads “over-privileged” as membership of a privileged Active Directory group, unconstrained delegation, or a high-privilege Microsoft Graph application permission. Design guidance. A stale account is a lead only: Learn’s lastLogonTimestamp is updated only when the stored value is older than the sync interval,19 and the plan’s risk register says to “confirm stale leads against domain controller and Entra ID sign-in logs before retiring anything.”

Which cloud and which directory, 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.20 21 The Graph calls whose pages show a national cloud table (list applications, application owners, federated credentials, delegated permission grants) list the global service, US Government L4 and US Government L5.22 23 24 25 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. The Active Directory reads go to the domain named with -Domain (here corp.contoso.example), never to whatever domain the machine happens to be in.

Start with Microsoft’s sample

What Microsoft gives you

Learn’s service account series is Microsoft’s procedure for this read. Secure user-based service accounts in Active Directory gives three PowerShell commands: Get-ADObject for accounts trusted for delegation, and Get-ADUser -Filter * with a client-side filter for accounts with SPNs and for passwords that never expire.26 Secure group managed service accounts gives Get-ADServiceAccount -Filter *, then the same filtered to gMSAs by object class.27 Securing service principals in Microsoft Entra ID lists every service principal with Get-MgServicePrincipal -All:$true, and offers a filter on the tag WindowsAzureActiveDirectoryIntegratedApp “to find multi-tenant apps.”28 Govern on-premises service accounts says what to collect for each account: owner, purpose, permissions, CMDB links, risk and lifetime.29

For credentials, Learn’s script Export secrets and certificates for app registrations signs in with Application.Read.All, reads every app registration with Get-MgApplication -All, and writes one CSV row per secret and per certificate with its start and end dates and the app’s owners from Get-MgApplicationOwner.30

The pinned copies

The commands and the script exist only inside the Learn pages, so the repository pins the pages’ source, unmodified, in two folders: examples/third_party/entra-service-account-pages (the three service account pages) and examples/third_party/entra-app-credentials-export-page (the script page), both from MicrosoftDocs/entra-docs at commit 7f78b50 (October 30, 2026), retrieved October 30, 2026, with the repository’s MIT licenses.31 32 Each snapshot.json records every 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 each of the four pages. Updating a copy is a reviewed pull request.

tests/LearnSampleHarness.ps1 extracts the commands and the script from the pinned pages at test time and runs them unmodified against stand-ins for Get-ADObject, Get-ADUser, Get-ADServiceAccount, Get-MgServicePrincipal, Connect-MgGraph, Get-MgApplication, Get-MgApplicationOwner and Read-Host, served from the fixture estate. The stand-in for Get-ADObject evaluates only the delegation filter Learn’s sample passes and throws on any other, so a new upstream filter fails a test instead of being half-read. Nothing is installed and nothing signs in.

What it leaves to you

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

Learn’s sample, as published Why it matters What examples/non-person-identity adds
Finds user accounts with SPNs, accounts trusted for delegation and passwords that never expire — Kept as the model: the same three criteria, plus the naming convention Learn recommends for them33 and the description attribute Learn suggests for on-premises service accounts34
Reads every user with -Filter * and filters on the client, in three separate queries Three full reads of the domain, and an account in two lists appears twice; no list is a register One Get-ADUser -LDAPFilter query, one row per account, DetectedBy saying which criteria matched
The password-never-expires query returns a person, with nothing to tell them apart A person’s account lands on a service account list, or a service account is dismissed as a person Kept, marked “detected only by a password that never expires” (NPI-INV-08), so someone confirms before Chapter 02 applies the ownership rule
The delegation query returns computer accounts A domain-joined server sits among the service accounts User objects only; computer accounts are out of scope for the register, and Chapter 06 reads only traditional accounts, MSAs, gMSAs and dMSAs (research gaps, row 8)
Reads no last logon, password age or group membership, though the page says to assess privileged group membership Neither a stale lead nor “over-privileged” can be computed LastLogonDate, PasswordLastSet, MemberOf and the delegation flags, read in the same query
Get-ADServiceAccount -Filter * names two object classes; a dMSA comes back unlabeled, and the gMSA filter drops it dMSAs, the type Book 02 keeps, go unclassified MSA, gMSA and dMSA by object class; any other class reported as unknown (NPI-INV-09; research gaps, row 1)
Reads no PrincipalsAllowedToRetrieveManagedPassword A gMSA’s blast radius is the hosts that can retrieve its password (the implementation guide, step 1a) Recorded in Hosts
Get-MgServicePrincipal -All:$true lists every service principal — Kept as the model, through GET /servicePrincipals with $select, every page followed
The tag filter it offers for multitenant apps returns single-tenant apps; the Entra PowerShell reference uses the same tag to list all enterprise apps35 A single-tenant app is treated as a third party’s, or the reverse appOwnerOrganizationId compared with the tenant ID, and signInAudience recorded (research gaps, row 6)
The export script lists every secret and certificate on every app registration — Kept as the model: secrets, certificates, expired credentials and the next expiry on each row
Prompts for the output path with Read-Host It can’t run unattended or on a schedule Every input is a parameter; the run writes to -OutputPath
Calls Get-MgApplication again for every app, and Get-MgApplicationOwner once per credential, without -All One extra call per app and per credential; whether every owner page is read isn’t stated One paged list with $select, owners read once per app, every page followed, as Learn’s paging guidance asks,36 and on the cloud’s own host (the program’s rule, Design guidance)
Reads no owners for an app with no credential, and reports a guest owner like any other; counts nothing An ownerless federated app is invisible, and Chapter 02 has nothing to count OwnerCount, OwnerIds and OwnerTypes for every app registration and legacy service principal
Flags no expired credential, though the page’s explanation says the admin “is asked about the expiration date” The page and its code disagree, and an expired credential left on an app is noise an attacker can hide in ExpiredCredentials and NPI-INV-05
Reads no federated credentials, service principals, managed identities, permissions or consent grants Most of Gate 0’s register is missing All of them, including a user-assigned identity’s federated credentials from Resource Manager
Signs in with Connect-MgGraph -Scopes 'Application.Read.All' and no environment The worldwide cloud, whatever the tenant An explicit cloud, the call list in config/calls.json with each call’s least-privileged permission, and a session check against the tenant ID

The verdict. Use Learn’s commands as the definition of what to look for, and the export script as the model for the credential columns. Don’t run them as the register: they read the domain three times, can’t tell a person from a service account, miss dMSAs, and see only one of the many kinds of workload identity Gate 0 asks for.

Book 02’s own discovery function, Get-FMPServiceAccountInventory, is the program’s earlier code for the Active Directory half (Book 02, Chapter 09). The register keeps its rules (disabled and stale first, then the risk checks; 90 and 365 days) and adds dMSAs, delegation, group membership, the Entra side and the manifest. Book 02’s function stays where it’s maintained.

The code: examples/non-person-identity

examples/non-person-identity is new. This chapter’s code is powershell/NpiCore/NpiCore.psm1 (the reader, shared by every chapter), powershell/NpiInventory/NpiInventory.psm1, config/calls.json, config/register.json and the entry script Invoke-NpiInventory.ps1.

The reader

Every call goes through one reader, built for one task in one cloud: New-NpiReader -Cloud gcc -Task inventory -Invoke <script block> -AdInvoke <script block>. The script blocks are the only things that touch the network: New-NpiLiveInvoker and New-NpiLiveAdInvoker for a real run, or New-NpiFixtureInvoker and New-NpiFixtureAdInvoker for labs and tests. The reader enforces five rules:

  • Only declared calls. config/calls.json lists each call with its method (or cmdlet), version, path, least-privileged permission, Azure role or Active Directory right, stage and Learn page, and declares what each task needs. A call the task doesn’t declare throws before anything is sent, and a test keeps each task’s declaration equal to the union of its calls.
  • Reads only. The reader sends GET and runs only declared Get-AD* cmdlets; the cmdlet name comes from the call list, never from the caller. A task declared as a write can’t have a reader at all.
  • 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.
  • One tenant. The live invoker refuses to start if the Graph or Azure session is in another cloud or signed in to a tenant other than -TenantId.
  • 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 needs are the union of its calls:

Call Least-privileged permission, role or right Delegated role
Get-ADUser, Get-ADServiceAccount The default read of an authenticated domain user (research gaps, row 2)37 38 —
List applications, owners, federated credentials Application.Read.All39 40 41 Applications and owners: Directory Readers, Global Reader; federated credentials: the app’s owner, Cloud Application Administrator or Application Administrator (Learn lists neither Directory Readers nor Global Reader for them)
List service principals, owners, app role assignments Application.Read.All42 43 44 Directory Readers, Global Reader; app role assignments: Directory Readers (Learn doesn’t list Global Reader for them)
List delegated permission grants Directory.Read.All45 Global Reader, Directory Readers
A user-assigned identity’s federated credentials Reader on the identity —

Two rows matter. Learn’s least-privileged permission for listing delegated permission grants is Directory.Read.All, the broadest read the inventory holds and the only reason it holds it (research gaps, row 5). And without permission to read users, an application’s owners come back with id and @odata.type only,46 which is all a count needs; Chapter 02 reads the people with its own, separate permission.

The register

Get-NpiRegister returns one row per identity with the same columns for every type, so the register writes to one CSV: the identity (ad:<objectGUID>, app:<appId>, sp:<id> or mi:<id>), source, type and name; how it was found; where the workload runs and which hosts can use it; password age, last logon, SPNs, delegation and privileged groups for Active Directory; home tenant, sign-in audience, owner count and types, serviceManagementReference, secrets, certificates, expired credentials, the next expiry and the credential kind for Entra; federated credentials with issuer and subject; Graph application permissions, the high-privilege ones among them, delegated grants and tenant-wide consent; and the risk, its reasons, the candidate target and any hand-off.

An app’s permissions are reported once, on its app registration row; its own service principal row says so, so a HIGH app isn’t counted twice. Service principals of other tenants are typed ExternalServicePrincipal: their credentials are in their home tenant. Microsoft Graph’s own service principal is read for its app roles and left out of the register.

Risk, targets, stale leads and the hand-off

Get-NpiRisk applies the plan’s rule to enabled identities: HIGH for a password that never expires, a password unchanged for more than 365 days, a privileged AD group, unconstrained delegation or a high-privilege Graph permission; MEDIUM for any other enabled traditional account, MSA, gMSA or legacy service principal, or anything holding a client secret; LOW otherwise. Book 02’s table rates MSAs and gMSAs Medium while its function sets them LOW; the code follows the table (research gaps, row 9). Get-NpiCandidateTarget is Book 02’s migration path matrix: a Kerberos service account goes to a gMSA, or a dMSA on a Windows Server 2025 domain; an MSA is upgraded to a gMSA; a secret on an app registration is replaced by a managed identity, then a federated credential, then a certificate. It’s a candidate only: Chapter 03 makes the decision. Design guidance for both.

A stale lead is an enabled Active Directory account with no logon in 90 days. It carries the note “confirm against domain controller and Entra sign-in logs before any change”, and the code never disables, moves or deletes anything. The register reads no Entra sign-in activity, because the service principal sign-in activity report is a beta API (research gaps, row 3).

An account whose SPN starts with MSSQLSvc/, or whose name matches the program’s SQL Server and LDAP-bind patterns, is tagged DataPlatform and written to data-platform-handoff.csv: the plan hands those accounts to the Data Platform plan (task NP0.4; Book 06, Chapter 09).

The findings

Test-NpiRegister turns the rows into findings, each with a stable ID, <rule>:<identity>, so the same gap keeps its ID from run to run.

Rule Severity What it flags Source
NPI-INV-01 Finding A HIGH-risk identity, with its reasons and candidate target Project plan, NP2.2 and Gate 0; Book 02
NPI-INV-02 Warn A stale lead: an enabled AD account with no logon in 90 days, to confirm against sign-in logs Book 02, Chapter 09; Learn on lastLogonTimestamp47
NPI-INV-03 Warn An app registration or legacy service principal with fewer than two owners (Chapter 02 checks who they are) Book 02, Chapter 09; Learn: at least two owners where possible48
NPI-INV-04 Warn A client secret Book 04, Chapter 05: never a client secret
NPI-INV-05 Warn An expired credential still on the object Book 04, Chapter 05: remove unused credentials
NPI-INV-06 Warn A delegated grant consented for all users (AllPrincipals) Learn: consent type on oAuth2PermissionGrant49
NPI-INV-07 Info A SQL Server or LDAP-bind account handed to the Data Platform plan Project plan, NP0.4
NPI-INV-08 Warn An account found only by a password that never expires: may be a person Learn: the criteria “might not find accounts”, and find people too50
NPI-INV-09 Warn An object class or service principal type the register doesn’t classify Design guidance

The run and its manifest

Invoke-NpiInventory.ps1 reads, classifies, finds and writes register.json, register.csv, findings.json, data-platform-handoff.csv and manifest.json. The manifest records the task, the mode (Fixture or Live), the cloud, the tenant ID, the domain, the as-of date, the permissions, roles and rights the task holds, every request and AD read with its time, the SHA-256 of config/register.json, and the SHA-256 of each file. A live run refuses to start without -TenantId. The manifest names no one; the register names accounts and owners’ object IDs, so treat the run’s folder as sensitive. Chapter 02 refuses a register whose hash doesn’t match its manifest.

The tests

Pester 5 under PowerShell 7, 513 tests across the kit (Lab 1 runs them all), none needing a module, a tenant, a domain or a network. This chapter’s are:

  • tests/MicrosoftSamples.Tests.ps1, 20 tests in the Chapter 01 blocks: Learn’s commands and script, run as published.
  • tests/NpiInventory.Tests.ps1, 37 tests: clouds, the call list (each task’s declaration equals the union of its calls), refused calls, cmdlets, hosts and writes, paging with page sizes of 1, 3 and 50, the one LDAP query, every type in the register, credentials, permissions, risk, targets, the hand-off, stale leads, each finding, the manifest’s hashes and the console output.

The other 10 sample tests and the 31 in tests/NpiOwnership.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 estate served by fakes. They show what the code does, not that a tenant or a domain returns the same shapes. Review the code, and run Lab 2 in a test tenant and test domain, before it reads a real one.

Lab 1: fixtures, no tenant

You need PowerShell 7.1 or later with Pester 5. Work in examples/non-person-identity.

NoteEvery command in Lab 1

Every command in this lab, in order, from examples/non-person-identity. 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 what the inventory needs
Import-Module ./powershell/NpiInventory/NpiInventory.psm1
$p = Get-NpiRequiredPermission -Task inventory
$p.GraphPermissions -join ', '; $p.AzureRoles -join ', '; $p.AdRights -join ', '

# Step 3: Run the inventory against the fictional estate
./Invoke-NpiInventory.ps1 -Cloud gcc -FixturePath tests/fixtures/estate.json -OutputPath out/inventory

# Step 4: Read the HIGH-risk identities
Get-Content out/inventory/findings.json -Raw | ConvertFrom-Json | Where-Object Rule -eq 'NPI-INV-01' | Format-Table Object, Issue -Wrap

# Step 5: See the reader refuse
$r = New-NpiReader -Cloud gcc -Task inventory -Invoke (New-NpiFixtureInvoker -Path tests/fixtures/estate.json).Invoke
Invoke-NpiList -Reader $r -CallId graph-user -Bind @{ id = 'x' }
Get-NpiCloud GCC

# Step 6: Read the hand-off to the Data Platform plan
Import-Csv out/inventory/data-platform-handoff.csv | Format-Table Name, Type, Spns, Risk

# Step 7: 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 20, FailedCount 0, NotRunCount 10 (the ten are Chapter 02’s). Open the test file and read the dMSA test: Learn’s command returns the dMSA, and nothing in the sample says what it is.

  2. List what the inventory needs:

    Import-Module ./powershell/NpiInventory/NpiInventory.psm1
    $p = Get-NpiRequiredPermission -Task inventory
    $p.GraphPermissions -join ', '; $p.AzureRoles -join ', '; $p.AdRights -join ', '
    Application.Read.All, Directory.Read.All
    Reader
    Read (default for authenticated domain users)

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

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

    Nothing ends in ReadWrite.

  3. Run the inventory against the fictional estate:

    ./Invoke-NpiInventory.ps1 -Cloud gcc -FixturePath tests/fixtures/estate.json -OutputPath out/inventory
    Register: 35 identities (AD 15, Entra 20); risk HIGH 8, LOW 19, MEDIUM 8
    Findings: 8 Finding, 11 Warn, 3 Info; 3 handed to the Data Platform plan
    Written to out/inventory (manifest.json: tenant c0ffee00-0000-4000-8000-000000000001, every request, a SHA-256 per file)

    Terminal window showing ./Invoke-NpiInventory.ps1 -Cloud gcc -FixturePath tests/fixtures/estate.json … and what it printed

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

    Get-Content out/inventory/findings.json -Raw | ConvertFrom-Json | Where-Object Rule -eq 'NPI-INV-01' | Format-Table Object, Issue -Wrap

    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)

    Eight rows: svc-hrsync (password never expires, unchanged in 2800 days; candidate a gMSA or dMSA), svc-sqlprod (the same, handed to the Data Platform plan), svc-backup (Backup Operators), svc-oldftp (password never expires; “verify use first”, because it’s also a stale lead), jdoe (password never expires, and found only by that criterion: NPI-INV-08 asks whether it’s a person), svc-webfront (unconstrained delegation), gmsa-ops$ (Domain Admins) and the Mail Automation app registration (Directory.ReadWrite.All). The disabled svc-reports isn’t there: its risk is LOW and its candidate is retirement, after confirmation.

  5. See the reader refuse:

    $r = New-NpiReader -Cloud gcc -Task inventory -Invoke (New-NpiFixtureInvoker -Path tests/fixtures/estate.json).Invoke
    Invoke-NpiList -Reader $r -CallId graph-user -Bind @{ id = 'x' }
    Get-NpiCloud GCC

    Terminal window showing $r = New-NpiReader -Cloud gcc -Task inventory -Invoke (New-NpiFixtureInvoker … and what it printed

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

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

  6. Read the hand-off to the Data Platform plan:

    Import-Csv out/inventory/data-platform-handoff.csv | Format-Table Name, Type, Spns, Risk

    Terminal window showing Import-Csv out/inventory/data-platform-handoff.csv | Format-Table Name, Type,… and what it printed

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

    svc-sqlprod and gmsa-sql$ by their MSSQLSvc/ SPNs, and svc-ldapbind by name.

  7. 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: 513 tests passing, every row of the gap table seen with your own eyes, and out/inventory/manifest.json listing 36 requests: 34 GETs and 2 AD reads. The later labs make their own copy of out/inventory. Delete out when you’re done:

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, and a test Active Directory domain, never production, with fictional people. The run only reads. Work in examples/non-person-identity on the test host. 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 its test certificate), <test-domain> (the test domain’s DNS name).

NoteEvery command in Lab 2

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

# Step 4: On a domain-joined test host with the ActiveDirectory module, as an ordinary domain user, 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-NpiInventory.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -OutputPath out/inventory-test

# Step 5: Check the two things the permissions assumed
Import-Csv out/inventory-test/register.csv | Where-Object FederatedCredentials -gt 0 | Format-Table Name, Type, FederatedCredentials, FederatedSubjects -Wrap
(Get-Content out/inventory-test/manifest.json -Raw | ConvertFrom-Json).requests | Where-Object target -match '^https://management\.' | Format-Table call, target -Wrap

# Step 6: Check the directory
./Invoke-NpiInventory.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -OutputPath out/inventory-test
Import-Csv out/inventory-test/register.csv | Where-Object Source -eq 'AD' | Format-Table Name, Type, PasswordAgeDays, LastLogon, PrivilegedGroups -Wrap

# Step 7: Cross-check against Learn's own lists
Import-Csv out/inventory-test/register.csv | Where-Object Source -ne 'AD' | Sort-Object Type, Name | Format-Table Type, Name

# Step 8: 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. Entra ID > App registrations > New registration. Name it, keep Accounts in this organizational directory only, and select Register. Copy the Application (client) ID from Overview: it’s <app-id>.
    2. Certificates & secrets > Certificates > Upload certificate. Upload the public part (.cer) of a test certificate whose private key is in the test host’s certificate store, and select Add. The Thumbprint shown is <thumbprint>.
  2. Grant the two application permissions from Lab 1, step 2, and grant admin consent. Nothing else:

    1. In the app, API permissions > Add a permission > Microsoft Graph > Application permissions.
    2. Select Application.Read.All and Directory.Read.All, then Add permissions.
    3. Select Grant admin consent for your tenant, and confirm.
  3. Grant Reader to the app’s service principal on a resource group holding one user-assigned managed identity with one federated credential. In the Azure portal:

    1. Open the resource group, then Access control (IAM) > Add > Add role assignment.
    2. On Role, select Reader, then Next.
    3. On Members, choose Select members, select the app, then Select and Review + assign.
  4. On a domain-joined test host with the ActiveDirectory module, as an ordinary domain user, 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-NpiInventory.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -OutputPath out/inventory-test
  5. Check the two things the permissions assumed. Confirm that the app registration with a federated credential shows it (research gaps, row 4), and that the user-assigned identity’s federated credential is read; note the host of any nextLink Resource Manager returns (row 7). The first command lists the rows with a federated credential; the second, every Resource Manager request the run sent, followed pages among them:

    Import-Csv out/inventory-test/register.csv | Where-Object FederatedCredentials -gt 0 | Format-Table Name, Type, FederatedCredentials, FederatedSubjects -Wrap
    (Get-Content out/inventory-test/manifest.json -Raw | ConvertFrom-Json).requests | Where-Object target -match '^https://management\.' | Format-Table call, target -Wrap
  6. Check the directory. Create a test dMSA on a Windows Server 2025 test domain if you have one, and confirm it comes back as dMSA (row 1); confirm PasswordLastSet, LastLogonDate and MemberOf are filled for an ordinary user’s read (row 2). With the dMSA created, read the directory again (the sessions from step 4 are still open) and list its rows:

    ./Invoke-NpiInventory.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -OutputPath out/inventory-test
    Import-Csv out/inventory-test/register.csv | Where-Object Source -eq 'AD' | Format-Table Name, Type, PasswordAgeDays, LastLogon, PrivilegedGroups -Wrap
  7. Cross-check against Learn’s own lists. Every app registration in the admin center’s App registrations list should be in register.csv, and every managed identity on Enterprise apps filtered to managed identities.51 This lists the register’s names by type, to compare:

    Import-Csv out/inventory-test/register.csv | Where-Object Source -ne 'AD' | Sort-Object Type, Name | Format-Table Type, Name
  8. Back it out. Do this after the last test-tenant lab, not before: Chapters 02, 03, 04, 06, 07 and 09 reuse this lab’s reading app, its certificate and out/inventory-test, so keep all three until then, or rebuild them for each later lab. Then remove the Reader assignment and the two permissions, delete the app registration and its certificate, and delete out/inventory-test: it maps which accounts and apps can do what.

    1. In the Azure portal, open the resource group, Access control (IAM) > Role assignments, select the app’s Reader row, and Delete.

    2. In the Microsoft Entra admin center, Entra ID > App registrations, open the app, and Delete it, with its certificate and its two permissions.

    3. On the test host:

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

Verified facts

Item Value US Government clouds Stage Source
Finding user-based service accounts No attribute marks them; criteria: delegation, SPNs, passwords that never expire; may miss accounts; svc prefix recommended n/a (Active Directory) GA 52
Managed service accounts Get-ADServiceAccount -Filter *; msDS-GroupManagedServiceAccount and msDS-ManagedServiceAccount; gMSA passwords 240 bytes, changed every 30 days n/a GA 53 54
dMSA Windows Server 2025; supersedes a traditional account, disables its password, authentication tied to mapped machines n/a GA 55 56
lastLogonTimestamp Updated only when older than the current time minus msDS-LogonTimeSyncInterval n/a — 57
List applications Application.Read.All; also returns agent identity blueprints; $select of keyCredentials throttled at 150 requests a minute per tenant; page size 100 by default, 999 at most L4 and L5 v1.0 58
List owners of an application Application.Read.All; owners of unreadable types return id and @odata.type only L4 and L5 v1.0 59
List federated identity credentials Application.Read.All (the Get page names Application.ReadWrite.OwnedBy) L4 and L5 v1.0 60 61
List service principals; owners Application.Read.All; owners are users or service principals Not read for this chapter v1.0 62 63
servicePrincipalType Application, ManagedIdentity, Legacy, ServiceIdentity (agent identity), SocialIdp — v1.0 64
alternativeNames Holds a managed identity’s resource group and full resource ID — v1.0 65
List appRoleAssignedTo Application.Read.All Not read for this chapter v1.0 66
List delegated permission grants Directory.Read.All L4 and L5 v1.0 67
A user-assigned identity’s federated credentials GET …/userAssignedIdentities/{name}/federatedIdentityCredentials, api-version=2024-11-30 Azure Government host management.usgovcloudapi.net; the API’s availability there not read GA 68 69
Graph hosts GCC worldwide; GCC High graph.microsoft.us; DoD dod-graph.microsoft.us — — 70

Where Learn’s pages differ: the List and Get pages for federated identity credentials name different least-privileged permissions (research gaps, row 4); the service principal page’s tag filter for “multi-tenant apps” is the Entra PowerShell reference’s filter for all enterprise apps (row 6); the export script’s explanation describes an expiry prompt the script doesn’t have; and the Resource Manager list sample’s nextLink is on a placeholder host (row 7). The code follows the stricter reading in each case and a test pins each.

Federal requirements and evidence

The register is the record the guide’s G1 Inventory gate checks: every non-person identity, its type, its owners and its credentials, in one file. Each row names the file or command that is the evidence. The controls are in the NIST SP 800-53 Rev. 5 Moderate baseline.71

What the output proves Requirement it supports KSI it aligns to Who keeps it
Learn’s commands and export script, run as published, define what the register looks for, and each gap they leave is pinned by a test (Lab 1, step 1: MicrosoftSamples.Tests.ps1, 20 passed) CM-8 System Component Inventory KSI-PIY-GIV The repository’s tests; each pinned copy’s snapshot.json
The read holds only Application.Read.All, Directory.Read.All, Reader and the default AD read, with nothing ending in ReadWrite (Lab 1, step 2: Get-NpiRequiredPermission -Task inventory; config/calls.json) AC-6 Least Privilege, for a process acting for the program KSI-IAM-ELP, KSI-IAM-JIT Identity engineering; the ISSO keeps the consent record
Every AD service account, MSA, gMSA, dMSA, app registration, service principal and managed identity is one row, read from the directory itself (Lab 1, step 3, and Lab 2, step 4: register.json and register.csv) CM-8 System Component Inventory KSI-PIY-GIV The identity governance office; the CMDB owner
Each identity has its own identifier (ad:<objectGUID>, app:<appId>, sp:<id>, mi:<id>), a type, and its owners (register.csv columns IdentityId, Type, OwnerCount, OwnerIds) AC-2 Account Management (account types and account managers); IA-4 Identifier Management KSI-IAM-SNU (AC-2); KSI-IAM-ELP, KSI-IAM-JIT (IA-4) The identity governance office
HIGH-risk identities are listed with their reasons: a password that never expires or is over 365 days old, a privileged group, unconstrained delegation, or a high-privilege Graph permission (Lab 1, step 4: findings.json, NPI-INV-01) AC-6(5) Privileged Accounts; IA-5 Authenticator Management KSI-IAM-SNU, KSI-IAM-JIT Identity engineering, for the first wave
Client secrets, and expired credentials still on an object, are flagged (findings.json, NPI-INV-04 and NPI-INV-05; register.csv columns Secrets, ExpiredCredentials, NextCredentialExpiry) IA-5 and IA-5(2) Public Key-based Authentication KSI-IAM-SNU, KSI-SVC-ASM Application owners, through Chapter 04
Stale accounts are leads to confirm against sign-in logs, and the code never acts on them (findings.json, NPI-INV-02) AC-2(3) Disable Accounts (inactive accounts) KSI-IAM-AAM The identity governance officer
App registrations and legacy service principals with fewer than two owners are flagged (findings.json, NPI-INV-03) AC-2 (account managers) KSI-IAM-SNU The identity governance office, through Chapter 02
Delegated grants consented for all users are flagged (findings.json, NPI-INV-06) AC-6 Least Privilege KSI-IAM-ELP Application owners, through Chapter 07
The reader refuses a call the task doesn’t declare, and a cloud name it doesn’t know (Lab 1, step 5: the two error texts) AC-6 Least Privilege KSI-IAM-ELP The repository’s tests
SQL Server and LDAP-bind accounts go to the Data Platform plan (Lab 1, step 6: data-platform-handoff.csv; NPI-INV-07) CM-8 (accountability information for each component) KSI-PIY-GIV The Data Platform plan
The run records the tenant, the cloud, every request and AD read, the SHA-256 of config/register.json and a SHA-256 per file, and Chapter 02 refuses a register that doesn’t match (manifest.json) AU-9 Protection of Audit Information; SI-7 Software, Firmware, and Information Integrity KSI-MLA-OSM (AU-9); KSI-SVC-VRI (SI-7) The ISSO, with the run’s folder
The code passes its tests (Lab 1, step 7: Invoke-Pester -Path tests, 513 passed) CM-3(2) Testing, Validation, and Documentation of Changes KSI-CMT-VTD The repository; the pull request’s checks
The reading app signs in with a certificate and holds only the two read permissions and Reader (Lab 2, steps 1 to 3: the app’s Certificates & secrets and API permissions pages; the Reader role assignment) AC-6; IA-5(2) KSI-IAM-ELP, KSI-IAM-SNU Identity engineering
Federated credentials are read from Graph and from Resource Manager, and every Resource Manager request stays on the cloud’s host (Lab 2, step 5: register.csv columns FederatedCredentials and FederatedSubjects; the requests list in manifest.json) IA-5 Authenticator Management KSI-IAM-SNU Identity engineering
A dMSA is classified as one, and an ordinary domain user’s read returns password age, last logon and groups (Lab 2, step 6: the AD rows of register.csv) CM-8 KSI-PIY-GIV AD engineering
The register holds every app registration and managed identity the admin center lists, once (Lab 2, step 7: the Type, Name listing, compared with App registrations and Enterprise apps) CM-8 (every component, no duplicates) KSI-PIY-GIV The identity governance office
The lab’s reading app is removed when the lab ends (Lab 2, step 8: the Reader assignment and the app registration deleted, which the Entra audit log shows) AC-2 (remove accounts no longer needed) KSI-IAM-JIT Identity engineering

KSIs are FedRAMP 20x indicators for cloud service providers. The KSI column names the indicators whose related controls, as FedRAMP lists them, include the row’s control or one of its enhancements; which output supports which indicator is the program’s mapping (design guidance).72 The agency authorizes its own system and lines this evidence up with what its providers report against KSIs; it doesn’t report KSIs itself (Book 00, Appendix B). Lab steps that only delete out have no row. The track-wide mapping is the guide’s Evidence table.

Dangers

Danger What happens The guard
A service account missed No owner, no decision, and it outlives the domain Learn’s three criteria plus naming and description in one query; managed service accounts read separately; Lab 2 cross-checks the admin center
A person treated as a service account The orphan rule disables someone’s account Accounts found only by a password that never expires are marked (NPI-INV-08) and Chapter 02 skips them until confirmed
A stale account retired on a guess A quarterly job fails Stale is a lead with a note; the code never disables or deletes; Chapter 02’s orphan rule needs the identity governance officer’s confirmation
A dMSA left unclassified The Kerberos island list in Chapter 06 is short Classified by object class; anything else is NPI-INV-09
An app counted twice, or a third party’s app treated as ours Inflated HIGH counts; owner findings on apps the tenant can’t change Permissions on the registration row only; external service principals typed by home tenant
A broad permission Directory.ReadWrite.All or Application.ReadWrite.All granted “to make it work” The call list; a test pins the task’s exact permissions, none ReadWrite; Directory.Read.All only for consent grants
The wrong cloud, tenant or domain A GCC High run reads graph.microsoft.com, or the token goes to another tenant Explicit cloud, no default; host checked on every page; live sessions checked against -TenantId; AD reads sent to -Domain
Throttling on credentials Reading keyCredentials is limited to 150 requests a minute per tenant73 One paged list of applications, never a call per app for its credentials
The register as a leak It maps which accounts can do what The manifest names no one; Lab 2 deletes the output

Checklist

Back to top

Footnotes

  1. Microsoft Learn, “Secure user-based service accounts in Active Directory,” find on-premises user accounts used as service accounts; assess on-premises user account security, https://learn.microsoft.com/entra/architecture/service-accounts-user-on-premises (accessed Oct 31, 2026).↩︎

  2. Microsoft Learn, “Delegated Managed Service Accounts overview,” https://learn.microsoft.com/windows-server/identity/ad-ds/manage/delegated-managed-service-accounts/delegated-managed-service-accounts-overview (accessed Oct 30, 2026).↩︎

  3. Microsoft Learn, “Microsoft.Graph servicePrincipals” (Bicep reference), property values, servicePrincipalType, https://learn.microsoft.com/graph/templates/bicep/reference/serviceprincipals?view=graph-bicep-1.0 (accessed Oct 30, 2026).↩︎

  4. Microsoft Learn, “Update servicePrincipal,” request body, alternativeNames, https://learn.microsoft.com/graph/api/serviceprincipal-update?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  5. Microsoft Learn, “Federated Identity Credentials - List” (Managed Identity REST API, 2024-11-30), https://learn.microsoft.com/rest/api/managedidentity/federated-identity-credentials/list (accessed Oct 30, 2026).↩︎

  6. Microsoft Learn, “List applications,” national cloud table, permissions, optional query parameters, https://learn.microsoft.com/graph/api/application-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  7. Microsoft Learn, “Get-ADUser,” https://learn.microsoft.com/powershell/module/activedirectory/get-aduser (accessed Oct 30, 2026).↩︎

  8. Microsoft Learn, “Secure user-based service accounts in Active Directory,” find on-premises user accounts used as service accounts; assess on-premises user account security, https://learn.microsoft.com/entra/architecture/service-accounts-user-on-premises (accessed Oct 31, 2026).↩︎

  9. Microsoft Learn, “Secure group managed service accounts,” benefits; find gMSAs, https://learn.microsoft.com/entra/architecture/service-accounts-group-managed (accessed Oct 30, 2026).↩︎

  10. Microsoft Learn, “Get-ADServiceAccount,” description and examples, https://learn.microsoft.com/powershell/module/activedirectory/get-adserviceaccount (accessed Oct 30, 2026).↩︎

  11. Microsoft Learn, “List applications,” national cloud table, permissions, optional query parameters, https://learn.microsoft.com/graph/api/application-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  12. Microsoft Learn, “List owners of an application,” https://learn.microsoft.com/graph/api/application-list-owners?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  13. Microsoft Learn, “List federatedIdentityCredential objects,” https://learn.microsoft.com/graph/api/federatedidentitycredential-list?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

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

  15. Microsoft Learn, “List owners of a service principal,” permissions, https://learn.microsoft.com/graph/api/serviceprincipal-list-owners?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  16. Microsoft Learn, “List appRoleAssignments granted for a service principal,” permissions, https://learn.microsoft.com/graph/api/serviceprincipal-list-approleassignedto?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  17. Microsoft Learn, “List oauth2PermissionGrants (delegated permission grants),” https://learn.microsoft.com/graph/api/oauth2permissiongrant-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  18. Microsoft Learn, “Federated Identity Credentials - List” (Managed Identity REST API, 2024-11-30), https://learn.microsoft.com/rest/api/managedidentity/federated-identity-credentials/list (accessed Oct 30, 2026).↩︎

  19. Microsoft Learn, “Last-Logon-Timestamp attribute,” https://learn.microsoft.com/windows/win32/adschema/a-lastlogontimestamp (accessed Oct 30, 2026).↩︎

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

  21. 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).↩︎

  22. Microsoft Learn, “List applications,” national cloud table, permissions, optional query parameters, https://learn.microsoft.com/graph/api/application-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

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

  24. Microsoft Learn, “List federatedIdentityCredential objects,” https://learn.microsoft.com/graph/api/federatedidentitycredential-list?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  25. Microsoft Learn, “List oauth2PermissionGrants (delegated permission grants),” https://learn.microsoft.com/graph/api/oauth2permissiongrant-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  26. Microsoft Learn, “Secure user-based service accounts in Active Directory,” find on-premises user accounts used as service accounts; assess on-premises user account security, https://learn.microsoft.com/entra/architecture/service-accounts-user-on-premises (accessed Oct 31, 2026).↩︎

  27. Microsoft Learn, “Secure group managed service accounts,” benefits; find gMSAs, https://learn.microsoft.com/entra/architecture/service-accounts-group-managed (accessed Oct 30, 2026).↩︎

  28. Microsoft Learn, “Securing service principals in Microsoft Entra ID,” find accounts; assess service principal security, https://learn.microsoft.com/entra/architecture/service-accounts-principal (accessed Oct 30, 2026).↩︎

  29. Microsoft Learn, “Govern on-premises service accounts,” lifecycle management process, https://learn.microsoft.com/entra/architecture/service-accounts-govern-on-premises (accessed Oct 30, 2026).↩︎

  30. Microsoft Learn, “PowerShell sample: Export secrets and certificates for app registrations,” https://learn.microsoft.com/entra/identity/enterprise-apps/scripts/powershell-export-all-app-registrations-secrets-and-certs (accessed Oct 30, 2026).↩︎

  31. Microsoft, MicrosoftDocs/entra-docs, docs/architecture/service-accounts-user-on-premises.md, service-accounts-group-managed.md and service-accounts-principal.md at commit 7f78b50dce9d8cf119de57cbd6fd66e24a6c58d4 (October 30, 2026; the pages last changed February 25, 2026), MIT License, https://github.com/MicrosoftDocs/entra-docs/tree/7f78b50dce9d8cf119de57cbd6fd66e24a6c58d4/docs/architecture (accessed Oct 30, 2026); pinned copy in examples/third_party/entra-service-account-pages.↩︎

  32. Microsoft, MicrosoftDocs/entra-docs, docs/identity/enterprise-apps/scripts/powershell-export-all-app-registrations-secrets-and-certs.md at commit 7f78b50dce9d8cf119de57cbd6fd66e24a6c58d4 (October 30, 2026; the page last changed February 19, 2026), MIT License, https://github.com/MicrosoftDocs/entra-docs/blob/7f78b50dce9d8cf119de57cbd6fd66e24a6c58d4/docs/identity/enterprise-apps/scripts/powershell-export-all-app-registrations-secrets-and-certs.md (accessed Oct 30, 2026); pinned copy in examples/third_party/entra-app-credentials-export-page.↩︎

  33. Microsoft Learn, “Secure user-based service accounts in Active Directory,” find on-premises user accounts used as service accounts; assess on-premises user account security, https://learn.microsoft.com/entra/architecture/service-accounts-user-on-premises (accessed Oct 31, 2026).↩︎

  34. Microsoft Learn, “Securing on-premises service accounts,” types of on-premises service accounts, https://learn.microsoft.com/entra/architecture/service-accounts-on-premises (accessed Oct 31, 2026).↩︎

  35. Microsoft Learn, “Get-EntraServicePrincipal,” example 7, retrieve all enterprise apps, https://learn.microsoft.com/powershell/module/microsoft.entra.applications/get-entraserviceprincipal?view=entra-powershell (accessed Oct 30, 2026).↩︎

  36. Microsoft Learn, “Page through a collection using the Microsoft Graph SDKs,” https://learn.microsoft.com/graph/sdks/paging (accessed Oct 31, 2026).↩︎

  37. Microsoft Learn, “Get-ADUser,” https://learn.microsoft.com/powershell/module/activedirectory/get-aduser (accessed Oct 30, 2026).↩︎

  38. Microsoft Learn, “Get-ADServiceAccount,” description and examples, https://learn.microsoft.com/powershell/module/activedirectory/get-adserviceaccount (accessed Oct 30, 2026).↩︎

  39. Microsoft Learn, “List applications,” national cloud table, permissions, optional query parameters, https://learn.microsoft.com/graph/api/application-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  40. Microsoft Learn, “List owners of an application,” https://learn.microsoft.com/graph/api/application-list-owners?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  41. Microsoft Learn, “List federatedIdentityCredential objects,” https://learn.microsoft.com/graph/api/federatedidentitycredential-list?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

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

  43. Microsoft Learn, “List owners of a service principal,” permissions, https://learn.microsoft.com/graph/api/serviceprincipal-list-owners?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  44. Microsoft Learn, “List appRoleAssignments granted for a service principal,” permissions, https://learn.microsoft.com/graph/api/serviceprincipal-list-approleassignedto?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  45. Microsoft Learn, “List oauth2PermissionGrants (delegated permission grants),” https://learn.microsoft.com/graph/api/oauth2permissiongrant-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

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

  47. Microsoft Learn, “Last-Logon-Timestamp attribute,” https://learn.microsoft.com/windows/win32/adschema/a-lastlogontimestamp (accessed Oct 30, 2026).↩︎

  48. Microsoft Learn, “Overview of enterprise application ownership in Microsoft Entra ID,” FAQ, https://learn.microsoft.com/entra/identity/enterprise-apps/overview-assign-app-owners (accessed Oct 30, 2026).↩︎

  49. Microsoft Learn, “List oauth2PermissionGrants (delegated permission grants),” https://learn.microsoft.com/graph/api/oauth2permissiongrant-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  50. Microsoft Learn, “Secure user-based service accounts in Active Directory,” find on-premises user accounts used as service accounts; assess on-premises user account security, https://learn.microsoft.com/entra/architecture/service-accounts-user-on-premises (accessed Oct 31, 2026).↩︎

  51. Microsoft Learn, “View the service principal of a managed identity,” https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-view-managed-identity-service-principal (accessed Oct 30, 2026).↩︎

  52. Microsoft Learn, “Secure user-based service accounts in Active Directory,” find on-premises user accounts used as service accounts; assess on-premises user account security, https://learn.microsoft.com/entra/architecture/service-accounts-user-on-premises (accessed Oct 31, 2026).↩︎

  53. Microsoft Learn, “Secure group managed service accounts,” benefits; find gMSAs, https://learn.microsoft.com/entra/architecture/service-accounts-group-managed (accessed Oct 30, 2026).↩︎

  54. Microsoft Learn, “Get-ADServiceAccount,” description and examples, https://learn.microsoft.com/powershell/module/activedirectory/get-adserviceaccount (accessed Oct 30, 2026).↩︎

  55. Microsoft Learn, “Delegated Managed Service Accounts overview,” https://learn.microsoft.com/windows-server/identity/ad-ds/manage/delegated-managed-service-accounts/delegated-managed-service-accounts-overview (accessed Oct 30, 2026).↩︎

  56. Microsoft Learn, “Setting up delegated Managed Service Accounts,” create a standalone dMSA, https://learn.microsoft.com/windows-server/identity/ad-ds/manage/delegated-managed-service-accounts/delegated-managed-service-accounts-set-up-dmsa (accessed Oct 30, 2026).↩︎

  57. Microsoft Learn, “Last-Logon-Timestamp attribute,” https://learn.microsoft.com/windows/win32/adschema/a-lastlogontimestamp (accessed Oct 30, 2026).↩︎

  58. Microsoft Learn, “List applications,” national cloud table, permissions, optional query parameters, https://learn.microsoft.com/graph/api/application-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  59. Microsoft Learn, “List owners of an application,” https://learn.microsoft.com/graph/api/application-list-owners?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  60. Microsoft Learn, “List federatedIdentityCredential objects,” https://learn.microsoft.com/graph/api/federatedidentitycredential-list?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  61. Microsoft Learn, “Get federatedIdentityCredential,” permissions, https://learn.microsoft.com/graph/api/federatedidentitycredential-get?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

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

  63. Microsoft Learn, “List owners of a service principal,” permissions, https://learn.microsoft.com/graph/api/serviceprincipal-list-owners?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  64. Microsoft Learn, “Microsoft.Graph servicePrincipals” (Bicep reference), property values, servicePrincipalType, https://learn.microsoft.com/graph/templates/bicep/reference/serviceprincipals?view=graph-bicep-1.0 (accessed Oct 30, 2026).↩︎

  65. Microsoft Learn, “Update servicePrincipal,” request body, alternativeNames, https://learn.microsoft.com/graph/api/serviceprincipal-update?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  66. Microsoft Learn, “List appRoleAssignments granted for a service principal,” permissions, https://learn.microsoft.com/graph/api/serviceprincipal-list-approleassignedto?view=graph-rest-1.0 (accessed Oct 31, 2026).↩︎

  67. Microsoft Learn, “List oauth2PermissionGrants (delegated permission grants),” https://learn.microsoft.com/graph/api/oauth2permissiongrant-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎

  68. Microsoft Learn, “Federated Identity Credentials - List” (Managed Identity REST API, 2024-11-30), https://learn.microsoft.com/rest/api/managedidentity/federated-identity-credentials/list (accessed Oct 30, 2026).↩︎

  69. 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).↩︎

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

  71. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-2(3), AC-6, AC-6(5), AU-9, CM-3(2), CM-8, IA-4, IA-5, IA-5(2) and SI-7), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 2, 2026).↩︎

  72. FedRAMP, “Key Security Indicators,” FedRAMP Consolidated Rules for 2026, indicator statements and “Related SP 800-53 Controls,” https://www.fedramp.gov/2026/reference/key-security-indicators/ (read November 2, 2026).↩︎

  73. Microsoft Learn, “List applications,” national cloud table, permissions, optional query parameters, https://learn.microsoft.com/graph/api/application-list?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎