In this module
IAM1.1 Identity Types as Governance Objects
Module 0 audited your tenant's governance gaps — attribute coverage, lifecycle stage coverage, permission creep, non-human identity inventory. This section examines the five identity types in Entra ID not as admin categories but as governance objects. Each type has different lifecycle characteristics, different governance controls available, and different gaps. The governance program you build across this course must cover all five — not just the human ones.
Five identity types, five governance models
Entra ID stores five categories of identity object. Most documentation presents these as administrative types — "members are internal users, guests are external users." That framing misses what matters for governance. The governance question for each type isn't what it is, but how it's created, how it gets access, whether that access is reviewed, who's accountable for its lifecycle, and what happens when it should no longer exist.
Each identity type has a different answer to those questions. Members arrive through HR provisioning or manual creation, get access through group membership, and should be reviewed by their manager. Guests arrive through invitation, get access through B2B collaboration or entitlement management, and should be reviewed by their sponsor. Service principals are created programmatically, get access through API permissions and role assignments, and should be reviewed by their application owner. Managed identities are created by Azure and get access through RBAC assignments tied to the resource lifecycle. AI agents are registered through Entra Agent ID, get access through blueprints and entitlement management, and should be reviewed by their human sponsor.
In this section you'll query each identity type through the Graph API, examine the governance-relevant properties that the API returns, and identify where the governance controls exist and where they're absent. By the end, you'll have an identity type census with governance coverage assessment per type.
Estimated time: 55 minutes.
Figure IAM1.1 — Five identity types mapped to available governance controls. Members have the richest governance infrastructure. Service principals, managed identities, and AI agents have progressively less. The program you build must cover all five.
Member identities — the ones with a partial lifecycle
Member identities are the accounts that represent employees, contractors, and internal service accounts. They're the identity type with the most governance infrastructure available — lifecycle workflows, access reviews, entitlement management, PIM — and the type where the governance gap is most visible because the tools exist but aren't used.
Query a single member identity and examine the full set of governance-relevant properties:
Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All",
"Directory.Read.All"
$member = Get-MgUser -UserId "priya.sharma@yourtenant.onmicrosoft.com" `
-Property id, displayName, userPrincipalName, userType, accountEnabled,
createdDateTime, department, jobTitle, manager, employeeHireDate,
employeeType, employeeId, companyName, officeLocation, usageLocation,
onPremisesSyncEnabled, onPremisesDistinguishedName,
signInActivity, lastPasswordChangeDateTime,
creationType
$member | Select-Object displayName, userType, accountEnabled,
createdDateTime, department, jobTitle, employeeHireDate,
employeeType, onPremisesSyncEnabled,
@{N='LastSignIn'; E={$_.SignInActivity.LastSignInDateTime}},
@{N='LastSuccessfulSignIn'; E={$_.SignInActivity.LastSuccessfulSignInDateTime}},
@{N='LastNonInteractive'; E={$_.SignInActivity.LastNonInteractiveSignInDateTime}},
lastPasswordChangeDateTime | Format-ListdisplayName : Priya Sharma
userType : Member
accountEnabled : True
createdDateTime : 2022-06-15T09:14:22Z
department : Security
jobTitle : SOC Analyst L1
employeeHireDate : 2022-06-15T00:00:00Z
employeeType :
onPremisesSyncEnabled :
LastSignIn : 2026-05-09T14:22:41Z
LastSuccessfulSignIn : 2026-05-09T14:22:41Z
LastNonInteractive : 2026-05-11T08:15:03Z
lastPasswordChangeDateTime: 2025-11-20T10:33:18ZRead each field through the governance lens — not "what does this tell the admin?" but "what does this tell the governance program?"
employeeHireDate: 2022-06-15 — Priya has a hire date set. This means lifecycle workflows that trigger relative to hire date (pre-hire tasks at -7 days, day-one automation at 0 days) can fire for her. This is the exception, not the norm — IAM0.1 found 80% of members missing this attribute. The presence of this field is a governance signal. Its absence is a governance gap.
employeeType: (empty) — this attribute distinguishes employees from contractors, interns, and other employment categories. Entra ID lifecycle workflows use employeeType as a scoping condition — you can build different joiner workflows for employees vs contractors. When it's empty, the workflow can't differentiate. Priya is an employee, but the data model doesn't say so. A lifecycle workflow that triggers only for employeeType -eq "Employee" would skip her.
onPremisesSyncEnabled: (empty) — Priya was created directly in Entra ID (cloud-native), not synced from on-premises Active Directory. This matters for governance because cloud-native accounts are managed entirely through Entra ID — attribute changes happen through the Entra admin center or Graph API. Synced accounts are managed through on-premises AD and the changes replicate to Entra ID via Entra Connect (or, from July 2026, Entra Cloud Sync). If your governance program automates attribute updates through Graph API, those changes won't stick for synced accounts — the next sync cycle will overwrite them with the on-prem values. You need to know which accounts are cloud-native and which are synced before designing lifecycle automation.
signInActivity — three timestamps. LastSignInDateTime is the most recent interactive sign-in (user entered credentials). LastSuccessfulSignInDateTime is the most recent successful sign-in regardless of type — added to the v1.0 API in December 2023. LastNonInteractiveSignInDateTime is the most recent token refresh or background authentication. The gap between LastSignIn and LastNonInteractive reveals whether the identity is actively using services (frequent non-interactive) or has stopped (both timestamps stale). For governance, LastSuccessfulSignInDateTime is the most reliable staleness indicator — it captures both interactive and non-interactive authentication and only counts successes.
lastPasswordChangeDateTime: 2025-11-20 — Priya last changed her password about six months ago. For governance, password age is relevant when evaluating whether the identity has been compromised and the credential rotated, or whether the identity is using the same credential indefinitely. In organizations with password expiry policies, this field drives the rotation cadence. In passwordless environments, this field becomes less relevant as FIDO2 and Windows Hello replace passwords.
Entra Admin Center
Identity → Users → All users → select a member user → Properties
The portal shows the same fields — department, job title, employee hire date, employee type — in the Identity section. Scroll to the Sign-in activity card for last sign-in timestamps. The portal view is useful for verifying a single identity but doesn't support the bulk governance queries you'll run throughout this module. The Graph API is the governance tool. The portal is the verification tool.
Now run the member governance profile across all members to see the coverage landscape:
$members = Get-MgUser -All -Property id, displayName, userType,
accountEnabled, department, employeeHireDate, employeeType,
onPremisesSyncEnabled, signInActivity, createdDateTime |
Where-Object { $_.UserType -eq "Member" -and $_.AccountEnabled -eq $true }
$memberProfile = [PSCustomObject]@{
Total = $members.Count
HasDepartment = ($members | Where-Object { $_.Department }).Count
HasHireDate = ($members | Where-Object { $_.EmployeeHireDate }).Count
HasEmpType = ($members | Where-Object { $_.EmployeeType }).Count
CloudNative = ($members | Where-Object { -not $_.OnPremisesSyncEnabled }).Count
Synced = ($members | Where-Object { $_.OnPremisesSyncEnabled -eq $true }).Count
SignedIn30d = ($members | Where-Object {
$_.SignInActivity.LastSuccessfulSignInDateTime -gt (Get-Date).AddDays(-30)
}).Count
Stale90d = ($members | Where-Object {
$_.SignInActivity.LastSuccessfulSignInDateTime -and
$_.SignInActivity.LastSuccessfulSignInDateTime -lt (Get-Date).AddDays(-90)
}).Count
}
Write-Host "=== MEMBER GOVERNANCE PROFILE ==="
Write-Host "Total active members: $($memberProfile.Total)"
Write-Host " Has department: $($memberProfile.HasDepartment) ($([math]::Round($memberProfile.HasDepartment/$memberProfile.Total*100))%)"
Write-Host " Has hire date: $($memberProfile.HasHireDate) ($([math]::Round($memberProfile.HasHireDate/$memberProfile.Total*100))%)"
Write-Host " Has employee type: $($memberProfile.HasEmpType) ($([math]::Round($memberProfile.HasEmpType/$memberProfile.Total*100))%)"
Write-Host " Cloud-native: $($memberProfile.CloudNative)"
Write-Host " Synced from AD: $($memberProfile.Synced)"
Write-Host " Active (30d): $($memberProfile.SignedIn30d)"
Write-Host " Stale (90d+): $($memberProfile.Stale90d)"The employeeType coverage is typically the worst — it's a field that exists in the schema but almost nobody populates unless they have HR-driven provisioning. That field becomes critical in Module 2 when you build lifecycle workflows scoped by employment category.
At Northgate Engineering: The member governance profile returns 6 of 15 NE personas with hire dates (40%), 12 with departments (80%), and 0 with employeeType. The hire date coverage is better than the IAM0.1 finding (which measured the broader tenant) because Rachel deliberately ensured the security team's accounts had complete attributes when she joined. The rest of the organization — Phil's manual provisioning — has the same gaps you'd expect: no hire dates, inconsistent department values, no employment type.
Guest identities — the ones without a lifecycle
Guest identities represent external users who access your tenant through B2B collaboration. They're created through invitation — someone sends an email, the guest accepts (or doesn't), and a user object appears in your directory with userType: Guest.
Entra Admin Center
Identity → Users → All users → Add filter → User type → Guest
The filtered list shows only guest accounts. Click any guest → Properties. Note the Invitation status (Accepted or Pending). Click Groups to see what access the guest holds. Click Sign-in logs to see when (or whether) the guest last authenticated. A guest with "Pending" status and group memberships has access assigned but has never validated their identity — the access exists in a state of perpetual readiness without a completed authentication.
Query guest identities with governance-relevant properties:
$guests = Get-MgUser -All -Property id, displayName, userPrincipalName,
userType, accountEnabled, createdDateTime, creationType,
externalUserState, externalUserStateChangeDateTime,
signInActivity, mail |
Where-Object { $_.UserType -eq "Guest" }
$guests | Select-Object displayName,
@{N='State'; E={$_.ExternalUserState}},
@{N='StateChanged'; E={
if ($_.ExternalUserStateChangeDateTime) {
$_.ExternalUserStateChangeDateTime.ToString("yyyy-MM-dd")
} else { "Unknown" }
}},
createdDateTime,
@{N='LastSuccessful'; E={
if ($_.SignInActivity.LastSuccessfulSignInDateTime) {
$_.SignInActivity.LastSuccessfulSignInDateTime.ToString("yyyy-MM-dd")
} else { "NEVER" }
}} | Format-Table -AutoSizedisplayName State StateChanged createdDateTime LastSuccessful
----------- ----- ------------ --------------- --------------
Alex Rivera (Contoso) PendingAcceptance 2023-06-14 2023-06-14T... NEVER
Vendor-PenTest Accepted 2024-02-16 2024-02-15T... 2024-03-12
Sarah Kim (Partner) Accepted 2023-11-02 2023-11-01T... 2024-08-20
Old Contractor Accepted 2024-01-10 2024-01-09T... 2024-06-15
Project Contact (Vendor) Accepted 2024-05-20 2024-05-19T... 2024-09-03The governance-critical fields for guests are different from members.
externalUserState — PendingAcceptance means the invitation was sent but never redeemed. Alex Rivera was invited in June 2023 and has never accepted. The guest object exists in your directory, may hold group memberships, and may have been granted access to resources — all without the guest ever authenticating. This is a governance finding: an identity with potential access that has never been validated through sign-in.
externalUserStateChangeDateTime — when the state last changed. For accepted guests, this is when they redeemed the invitation. Combined with LastSuccessfulSignIn, it reveals whether the guest authenticated once to accept and then never returned.
The governance gap for guests is the absence of three mechanisms: sponsor accountability (who invited this guest and who is responsible for their ongoing access?), expiry policies (when should this guest account be disabled if unused?), and access reviews (does the guest still need access to the resources they were granted?). Entra ID Governance provides all three — guest access reviews, sponsor-based review routing, and guest lifecycle policies with expiry. Without Governance licensing, you monitor manually using the signInActivity queries from IAM0.1.
The creationType field distinguishes how the guest arrived: Invitation (someone explicitly invited them), SelfServiceSignUp (the guest signed up through a user flow), or null for guests created through other mechanisms. For governance, the creation type determines who to hold accountable — an invited guest has an identifiable inviter, a self-service guest may not.
Query the creation types across your guest population:
$guests | Group-Object CreationType | Select-Object Name, Count | Format-Table -AutoSizeName Count
---- -----
Invitation 4
1Most guests arrive through invitation. The empty creationType typically indicates a guest created through older Graph API calls or admin center actions that didn't set the field. For governance, the guests with Invitation creation type have an audit trail — the invitation event is logged and the inviter's identity is recorded. Guests with empty creation type have no provenance. You can't determine who invited them, when, or why without searching the audit log manually.
$guests | ForEach-Object {
$groupCount = (Get-MgUserMemberOf -UserId $_.Id -All -ErrorAction SilentlyContinue).Count
[PSCustomObject]@{
Name = $_.DisplayName
State = $_.ExternalUserState
Groups = $groupCount
LastSign = if ($_.SignInActivity.LastSuccessfulSignInDateTime) {
$_.SignInActivity.LastSuccessfulSignInDateTime.ToString("yyyy-MM-dd")
} else { "NEVER" }
}
} | Format-Table -AutoSizeName State Groups LastSign
---- ----- ------ --------
Alex Rivera (Contoso) PendingAcceptance 2 NEVER
Vendor-PenTest Accepted 1 2024-03-12
Sarah Kim (Partner) Accepted 3 2024-08-20
Old Contractor Accepted 1 2024-06-15
Project Contact (Vendor) Accepted 2 2024-09-03Alex Rivera has never signed in but holds 2 group memberships. Someone added the pending guest to groups before the invitation was accepted. Those groups may grant access to SharePoint sites, Teams channels, or applications. The guest can't exercise that access (they haven't authenticated), but the access assignment exists — and if the invitation is redeemed months later, the access activates instantly.
At Northgate Engineering: Rachel Okafor traces the 5 NE guest accounts. Alex Rivera was invited by a project manager who left NE 8 months ago. The invitation was never accepted. The guest object holds membership in
SG-Project-AlphaandTeam-Vendor-Onboarding-2024. Nobody knows who to contact about this guest because the inviter is gone. The guest has no sponsor in Entra ID — B2B collaboration doesn't require a sponsor field. That's Gap 1 for guest governance: invited without accountability, pending without expiry, holding access without review.
Service principal identities — the ones without governance controls
Service principals are the identity objects that represent applications in your tenant. You ran the non-human identity census in IAM0.4. Now examine the governance properties the Graph API returns for a single service principal.
Entra Admin Center
Identity → Applications → Enterprise applications → All applications
This page lists service principals. Select any non-Microsoft application → Properties. Note the Name, Application ID, and Enabled for users to sign-in toggle. Click Owners — if empty, nobody is accountable for this service principal's lifecycle. Click Permissions to see what API permissions the application has been granted. Click Sign-in logs to see whether the service principal has authenticated recently. The portal gives you the per-application view. The Graph API gives you the governance audit.
$sp = Get-MgServicePrincipal -Filter "displayName eq 'NE-Data-Migration-Tool'" `
-Property id, displayName, appId, servicePrincipalType,
appOwnerOrganizationId, createdDateTime, accountEnabled,
signInActivity, notes, description,
passwordCredentials, keyCredentials
$sp | Select-Object displayName, servicePrincipalType, accountEnabled,
createdDateTime, notes, description,
@{N='Secrets'; E={$_.PasswordCredentials.Count}},
@{N='Certs'; E={$_.KeyCredentials.Count}},
@{N='LastSignIn'; E={
if ($_.SignInActivity.LastSignInDateTime) {
$_.SignInActivity.LastSignInDateTime.ToString("yyyy-MM-dd")
} else { "NEVER" }
}} | Format-ListdisplayName : NE-Data-Migration-Tool
servicePrincipalType : Application
accountEnabled : True
createdDateTime : 2026-05-11T...
notes :
description :
Secrets : 1
Certs : 0
LastSignIn : NEVERCompare this to the member identity profile. The service principal has no department, no manager, no employeeHireDate, no employeeType. There's no manager to route an access review to. There's no hire date to trigger a lifecycle workflow. There's no department to scope a dynamic group rule. The governance mechanisms that exist for members — lifecycle workflows, manager-based access reviews, dynamic group membership — don't apply to service principals at all.
What the service principal does have: notes and description — both empty. These are the free-text fields where an administrator or application owner should document the purpose, the owner, and the business justification. In production tenants, these fields are empty for 90%+ of service principals. Without them, the governance question "what is this service principal for and who owns it?" has no answer.
signInActivity on service principals works the same as for users — it records when the service principal last authenticated. The NEVER result for NE-Data-Migration-Tool is expected since you just created it in the lab. In a production tenant, a service principal that hasn't signed in for 180+ days is a decommissioning candidate — the integration it supported may no longer exist.
The governance framework for service principals — which you'll build in Module 9 — uses different mechanisms than the member framework. Instead of lifecycle workflows triggered by employeeHireDate, you'll use credential expiry monitoring and automated alerting. Instead of manager-based access reviews, you'll use owner-based attestation ("does this app still need to exist?"). Instead of dynamic group membership, you'll use permission right-sizing based on sign-in logs and resource access patterns.
Managed identities — the ones with an automatic lifecycle
Managed identities are service principals created and managed by Azure for Azure resources. They come in two types: system-assigned (tied to a single resource, deleted when the resource is deleted) and user-assigned (independent of any single resource, shared across multiple resources).
If your developer tenant has Azure resources, query the managed identities:
$managedIds = Get-MgServicePrincipal -All -Property id, displayName,
servicePrincipalType, alternativeNames |
Where-Object { $_.ServicePrincipalType -eq "ManagedIdentity" }
Write-Host "Managed identities: $($managedIds.Count)"
$managedIds | Select-Object displayName, servicePrincipalType,
@{N='Type'; E={
if ($_.AlternativeNames -match '/Microsoft.ManagedIdentity/') {
"User-assigned"
} else { "System-assigned" }
}} | Format-Table -AutoSizeManaged identities are the most governance-friendly non-human identity type. System-assigned identities have an automatic lifecycle — they're created with the resource and deleted with the resource. No credential management required — Azure handles the authentication internally. No expired secrets, no credential rotation, no leaked passwords.
The governance concern with managed identities is permission scope, not lifecycle. A managed identity can accumulate Azure RBAC role assignments over time, just like a member account accumulates group memberships. A system-assigned managed identity for a web app that started with Storage Blob Data Reader on one storage account may, over time, receive Contributor on the resource group and Key Vault Secrets User on a key vault — and nobody reviews whether those additional permissions are still needed.
User-assigned managed identities have a different governance challenge. Because they're shared across multiple resources, the permission scope is the union of what all consuming resources need. A user-assigned identity shared between a web app and a function app holds permissions for both — and when the function app is decommissioned, the permissions it required aren't automatically removed from the shared identity. Over time, a shared managed identity accumulates the permission requirements of every resource that has ever used it, while only the currently active resources need those permissions.
The governance response is periodic permission right-sizing — auditing the RBAC assignments against the current consuming resources and removing permissions that no active resource requires. This is the managed identity equivalent of the access review for members, and it's entirely manual in most environments. Module 10 builds the automation.
AI agent identities — the ones with no governance yet
AI agent identities are the newest category. Microsoft launched Entra Agent ID in preview in March 2026, with general availability through Agent 365 licensing ($15/user/month) and M365 E7 ($99/user/month, GA May 2026). Agent identities represent AI agents — Copilot agents, custom agents, and third-party agents — that act autonomously within your tenant.
Agent identities don't exist in most developer tenants yet. If your tenant has Agent 365 or M365 E7:
$agents = Get-MgServicePrincipal -All -Property id, displayName, tags |
Where-Object { $_.Tags -contains "AgentIdentity" }
Write-Host "Agent identities: $($agents.Count)"The governance model for agents is fundamentally different from other identity types. A service principal runs code written by a developer — the access pattern is deterministic and reviewable. An AI agent runs instructions interpreted by a model — the access pattern depends on user prompts, agent configuration, and the model's reasoning chain. The same permission set produces different data access patterns depending on what the agent is asked to do.
Module 11 builds the agent governance framework. The key mechanisms are blueprints (centralized policy templates that govern all agents of a type), sponsor accountability (every agent requires a human accountable for its lifecycle), and permission boundaries (constraining what the agent can access regardless of its instructions).
Identity type census — the governance coverage map
Consolidate the findings into a single governance coverage assessment:
Write-Host "=== IDENTITY TYPE GOVERNANCE COVERAGE ==="
Write-Host ""
Write-Host "MEMBER IDENTITIES"
Write-Host " Lifecycle workflows: Available (requires Governance license + employeeHireDate)"
Write-Host " Access reviews: Available (requires Governance license + manager assignment)"
Write-Host " Dynamic groups: Available (requires department/employeeType attributes)"
Write-Host " Entitlement management: Available (requires Governance license)"
Write-Host " PIM: Available (P2 license)"
Write-Host " Key gap: Attribute coverage — 80% missing hire date, 25% no manager"
Write-Host ""
Write-Host "GUEST IDENTITIES"
Write-Host " Lifecycle workflows: Limited (guest-specific workflows available)"
Write-Host " Access reviews: Available (sponsor-based or manager-based)"
Write-Host " Expiry policies: Available (requires Governance license)"
Write-Host " Entitlement management: Available (connected organizations)"
Write-Host " Key gap: No sponsor accountability, no expiry, no review cadence"
Write-Host ""
Write-Host "SERVICE PRINCIPALS"
Write-Host " Lifecycle workflows: Not available (no lifecycle workflow support)"
Write-Host " Access reviews: Limited (app-scoped reviews in preview)"
Write-Host " Credential governance: Manual (no built-in rotation enforcement)"
Write-Host " Permission reviews: Manual (Graph API queries)"
Write-Host " Key gap: Zero governance mechanisms in most tenants"
Write-Host ""
Write-Host "MANAGED IDENTITIES"
Write-Host " Lifecycle: Automatic for system-assigned (tied to resource)"
Write-Host " Credential management: Automatic (Azure-managed, no secrets to rotate)"
Write-Host " Permission reviews: Manual (RBAC assignment reviews)"
Write-Host " CA policies: Available (Workload ID Premium, `$3/workload/month)"
Write-Host " Key gap: Permission accumulation on shared user-assigned identities"
Write-Host ""
Write-Host "AI AGENT IDENTITIES"
Write-Host " Lifecycle: Entra Agent ID (preview, requires Agent 365)"
Write-Host " Governance: Blueprints, sponsor accountability (preview)"
Write-Host " Permission boundaries: Agent entitlement management (preview)"
Write-Host " Key gap: Framework doesn't exist yet — Module 11 builds it"This output is your identity type governance map. Save it — you'll reference it in the governance state assessment (IAM1.7) and update it module by module as you build the governance controls for each type.
At Northgate Engineering: Rachel Okafor presents the identity type governance map to Marcus Webb. His response: "We've been treating security as a controls problem — CA policies, MFA, Defender rules. The identity types we've secured the least are the ones with the broadest access." The 347 app registrations with zero governance controls hold more combined permissions than the 810 members. The governance asymmetry isn't a controls gap — it's a visibility gap. The controls exist (for members). The governance framework for non-human identities doesn't.
Reusable script — the identity type governance census from this section:
# IAM1.1 — Identity Type Governance Census
Connect-MgGraph -Scopes "User.Read.All", "Application.Read.All",
"Directory.Read.All", "AuditLog.Read.All"
# Members
$members = Get-MgUser -All -Property id, userType, accountEnabled,
department, employeeHireDate, employeeType, onPremisesSyncEnabled,
signInActivity |
Where-Object { $_.UserType -eq "Member" -and $_.AccountEnabled -eq $true }
Write-Host "=== MEMBERS ($($members.Count)) ==="
Write-Host " department: $(($members | Where-Object { $_.Department }).Count) populated"
Write-Host " hireDate: $(($members | Where-Object { $_.EmployeeHireDate }).Count) populated"
Write-Host " employeeType: $(($members | Where-Object { $_.EmployeeType }).Count) populated"
Write-Host " cloud-native: $(($members | Where-Object { -not $_.OnPremisesSyncEnabled }).Count)"
Write-Host " synced: $(($members | Where-Object { $_.OnPremisesSyncEnabled }).Count)"
# Guests
$guests = Get-MgUser -All -Property id, userType, externalUserState,
signInActivity, createdDateTime |
Where-Object { $_.UserType -eq "Guest" }
$pending = ($guests | Where-Object { $_.ExternalUserState -eq "PendingAcceptance" }).Count
$neverSigned = ($guests | Where-Object { -not $_.SignInActivity.LastSuccessfulSignInDateTime }).Count
Write-Host "`n=== GUESTS ($($guests.Count)) ==="
Write-Host " Pending: $pending"
Write-Host " Never signed in: $neverSigned"
# Service principals (non-Microsoft)
$sps = Get-MgServicePrincipal -All -Property id, displayName,
servicePrincipalType, appOwnerOrganizationId, notes, description
$nonMsft = $sps | Where-Object {
$_.AppOwnerOrganizationId -ne "f8cdef31-a31e-4b4a-93e4-5f571e91255a"
}
$noDesc = ($nonMsft | Where-Object { -not $_.Description -and -not $_.Notes }).Count
Write-Host "`n=== SERVICE PRINCIPALS (non-Microsoft: $($nonMsft.Count)) ==="
Write-Host " No description/notes: $noDesc ($([math]::Round($noDesc/$nonMsft.Count*100))%)"
# Managed identities
$managed = $sps | Where-Object { $_.ServicePrincipalType -eq "ManagedIdentity" }
Write-Host "`n=== MANAGED IDENTITIES ($($managed.Count)) ==="
# Summary
$total = $members.Count + $guests.Count + $nonMsft.Count + $managed.Count
Write-Host "`n=== TOTAL IDENTITY OBJECTS: $total ==="Identity governance is designed for member users: lifecycle workflows trigger on employeeHireDate, access reviews route to the user's manager, dynamic groups reference the department attribute. But 40% of the identities in the average tenant aren't member users — they're service principals, app registrations, guest accounts, and managed identities. None of these have a manager. None have an employeeHireDate. None appear in department-based dynamic groups. The governance framework covers 60% of the identity population and silently ignores the rest. The non-human identities accumulate permissions, credentials, and access with no lifecycle, no review, and no accountability.
Decision-point simulation
Scenario 1. Your identity type census shows 810 members, 89 guests, 347 app registrations, 42 service principals without matching app registrations, and 15 managed identities. The total is 1,303 identities. Your CISO asks: "How many identities do we actually govern?" What's your honest answer?
You govern the 810 members (partially — depending on attribute completeness) and the 89 guests (minimally — depending on sponsor coverage). The 347 app registrations, 42 service principals, and 15 managed identities have no formal governance program — no ownership attestation, no credential lifecycle, no permission review. The honest answer: "We administer 1,303 identities. We govern approximately 900. The 400+ non-human identities have no governance framework." Modules 7 and 8 build that framework.
Scenario 2. A vendor asks you to create a "service account" (a member user with a shared password) for their integration because "that's how we've always done it." The identity type taxonomy says this should be a service principal. How do you respond?
The service account model has three governance failures: shared credentials (no individual accountability), no credential rotation enforcement, and the user object consumes a license. A service principal authenticates with a certificate or managed identity (no shared password), has credential expiry policies, and doesn't consume a user license. The migration path: create an app registration, grant the required Graph API permissions, issue a certificate credential, and decommission the shared account. If the vendor's integration doesn't support service principal authentication, document this as a risk register entry with a remediation timeline.
Scenario 3. You discover a service principal that authenticates with a client secret, holds Mail.ReadWrite permission on all mailboxes, and was created by a developer who left 14 months ago. Nobody in IT knows what it does. What's your immediate action and your governance recommendation?
Immediate: do NOT delete it — it might be running a production mail integration that breaking would cause an outage. Instead: rotate the credential (generate a new secret, invalidate the old one), notify the development team lead, and set a 14-day deadline to identify the app's purpose. If nobody claims it after 14 days, disable the service principal (don't delete) and monitor for break-fix reports. Governance recommendation: every service principal needs an ownership attestation policy — Module 7 builds this.
You're reading the free modules of Identity and Access Management in Microsoft 365
The full course continues with advanced topics, production detection rules, worked investigation scenarios, and deployable artifacts.