Identity Bytes // IB-ENTRA-SEC Track
Intermediate Lab 01 of 12 Est. 90 minutes

Baselining Identity Risk with Microsoft Graph PowerShell

Stand up a safe simulation tenant, connect with least-privilege scopes, seed realistic personas, then produce a defensible identity risk baseline covering MFA registration, privileged roles, dormant accounts and SMS reliance. This is the assessment work every Entra hardening programme begins with.

Section 1

Lab Metadata

Lab ID
IB-ENTRA-SEC-01
Difficulty
Intermediate
Scenario Org
Northgate Financial
Estimated Time
90 minutes

Core technologies

Microsoft Entra ID Microsoft Graph PowerShell SDK PowerShell 7 Entra ID P2 (trial) Authentication Methods report Directory roles Sign-in activity
Section 2

Scenario and Description

IN PLAIN TERMS Before renovating a building's security, a surveyor walks every floor and writes down every unlocked door, every window without a latch, every master key that has gone missing, and every office nobody has entered in months. You cannot decide what to fix, or later prove that you fixed it, without that written survey. This lab's baseline script is that survey for your identities. It counts who can actually prove who they are, who is holding the master keys, and which accounts have sat silent for months, so the whole programme starts from facts rather than guesswork.

Northgate Financial is a mid-sized UK financial services firm with roughly 4,800 staff across retail banking, operations and a small cyber security function. An internal audit and a follow-up penetration test have both flagged the same theme: identity is the weak edge. Some staff still complete multi-factor authentication over SMS, a handful of administrators have no strong second factor at all, several long-departed contractors retain active accounts, and nobody can produce a single authoritative view of who holds privileged roles.

You have been brought in as the identity security specialist to lead the remediation programme. Before you change a single policy you need a baseline: a measured, evidence-backed picture of the current risk. Auditors will ask what the position was before and after. Change controls will ask you to justify each hardening step. Executives will want a number that moves. All of that starts here, with a read-only assessment run through the Microsoft Graph.

In this lab you build the reproducible lab environment used by every later lab in the track, seed it with the Northgate personas, then run a baseline script that reports MFA and self-service password reset registration, privileged role membership, dormant accounts, guest accounts and SMS-reliant users. You finish with CSV evidence and a printed risk summary you could hand to a programme board.

Section 3

Prerequisites

Prior labs

None. This is the foundation lab. Every later lab in IB-ENTRA-SEC assumes the tenant, module and personas built here.

Knowledge assumed

Zero prior Entra or PowerShell experience is assumed. Where a term appears for the first time, such as "tenant", "scope" or "directory role", it is defined before it is used.

System requirements

ItemMinimumNotes
Operating systemWindows 10/11, macOS 13+, or Ubuntu 22.04The Graph SDK is cross-platform
PowerShell7.4 or laterWindows PowerShell 5.1 works but 7.x is the supported baseline for this track
Memory / disk4 GB RAM, 2 GB freeThe Graph SDK module set is around 700 MB
NetworkOutbound HTTPS (443)Reaches graph.microsoft.com and login.microsoftonline.com
A test tenantEntra ID tenant with a P2 trialSee the environment options below. Never run this against a production tenant
SECURITY WARNING Use a dedicated, disposable tenant for this entire track. The seeding script in Section 7 creates users. The account disablement automation in Lab 06 disables users. Running any of this against a live tenant is a change you did not raise and cannot easily unwind. Treat the lab tenant as radioactive with respect to production.

Choosing your lab tenant (verified July 2026)

The free tenant landscape changed, so pick the path that fits you. All later labs need Entra ID P1 or P2 features (Conditional Access needs P1, Privileged Identity Management and Identity Protection need P2, and the signInActivity data used in this lab needs P1). The cleanest way to get all of that at no cost is an Entra ID P2 trial on a fresh Azure tenant.

PathWhat you getBest for
A. Microsoft 365 Developer Program (E5 sandbox) E5 with 25 licences, pre-seeded users, includes Entra ID P2. Renews for the life of your Visual Studio subscription. Anyone holding a Visual Studio Professional or Enterprise standard subscription. Since 2024 this is effectively the entry requirement, individual sign-ups without it are usually refused.
B. Azure free account + Entra ID P2 trial (recommended if you lack path A) Free Azure tenant, then a 31-day Entra ID P2 trial for up to 100 licences. Covers Conditional Access, PIM, Identity Protection, access reviews and sign-in activity. Most independent learners. Needs a card for identity verification and a work or school style account in the tenant to activate cleanly.
C. Azure free account only (Entra free tier) Users, groups, app registrations, Graph access. No Conditional Access, PIM or sign-in activity. Running only this lab's user and role sections. You will hit licence walls from Lab 02 onward.
INFO: activating the P2 trial (path B) Create the free Azure account at azure.microsoft.com/free. Sign in to the Microsoft Entra admin center at entra.microsoft.com, open Billing > Licenses > All products, choose Try / Buy, select Microsoft Entra ID P2 and activate the free trial. If it prompts you to complete a "sold-to" address and payment method on the billing account first, do that, then reload the Licenses blade. Finally assign the P2 licence to your admin user under Licenses > All products > Microsoft Entra ID P2 > Assign.

Required tools with versions

Install PowerShell 7 and verify
# Windows (winget). PowerShell 7 installs alongside Windows PowerShell 5.1.
winget install --id Microsoft.PowerShell --source winget

# macOS (Homebrew)
brew install --cask powershell

# Ubuntu 22.04
sudo apt-get update && sudo apt-get install -y wget apt-transport-https software-properties-common
wget -q "https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb"
sudo dpkg -i packages-microsoft-prod.deb && sudo apt-get update && sudo apt-get install -y powershell

# Verify: open a new PowerShell 7 session (command name is: pwsh) and run
$PSVersionTable.PSVersion
# Expected: Major = 7, Minor = 4 or higher
Install the Microsoft Graph PowerShell SDK and verify
# Run inside pwsh (PowerShell 7). CurrentUser scope avoids needing admin rights.
Install-Module Microsoft.Graph -Scope CurrentUser -Repository PSGallery -Force

# The meta-module pulls in sub-modules (Users, Reports, Identity.DirectoryManagement, etc.)
# Verify the module and a key command are present:
Get-Module Microsoft.Graph -ListAvailable | Select-Object Name,Version | Sort-Object Version -Descending | Select-Object -First 1
Get-Command Connect-MgGraph -Module Microsoft.Graph.Authentication

# Expected: a version (2.x or later) and the Connect-MgGraph command resolves.
INFO: what "Microsoft Graph" is Microsoft Graph is the single REST API in front of Entra ID, Microsoft 365 and related services. The Graph PowerShell SDK wraps that API in cmdlets whose nouns start with Mg (for example Get-MgUser). Instead of clicking through the portal, you query and change identity objects programmatically, which is exactly what "automating administration and reporting through PowerShell" means in the role you are preparing for.
Section 4

Real-World Problem Statement

An identity hardening programme that starts by changing controls, rather than by measuring, is one that cannot prove it worked and cannot prioritise. You need to answer four questions with evidence before you touch a policy: how many accounts can actually perform strong MFA, who holds privileged roles, which accounts are dormant, and who still depends on weak factors such as SMS.

DimensionWhy the baseline matters
RiskDormant enabled accounts and SMS-only MFA are two of the most commonly exploited footholds. You cannot reduce what you have not counted.
ComplianceAudit and frameworks such as ISO 27001 and Cyber Essentials expect evidence of access reviews and MFA coverage. A dated CSV baseline is that evidence.
ProductivityA repeatable script replaces days of manual portal exports and can be re-run to show progress week over week to the programme board.
Security postureThe baseline becomes the denominator for every later metric: "MFA-capable rose from 61 percent to 100 percent", "privileged role holders fell from 14 to 6".

Concrete scenario: Northgate's CISO has committed to the board that within one quarter, every account will be MFA-capable, SMS will be retired, and privileged access will be time-bound. Your first deliverable, due this week, is the "as-is" baseline that those commitments are measured against.

Section 5

Skills Mapped to Production Solutions

Skill learned in this labReal-world enterprise application
Connecting to Graph with scoped delegated permissionsLeast-privilege operational access for identity engineers, so read tasks never run with write rights
Reading the authentication methods registration reportMeasuring MFA and SSPR coverage across the estate for board reporting and audit evidence
Enumerating directory role membershipPrivileged access inventories and the input to a least-privilege and PIM programme
Querying sign-in activity to find dormancyJoiner-mover-leaver hygiene and automated deprovisioning candidates
Exporting structured evidence to CSVRepeatable, dated compliance artefacts and before-and-after remediation metrics
Section 6

Architecture Overview

The lab has three planes: your analyst workstation running PowerShell 7 and the Graph SDK, the Microsoft Graph API as the control surface, and the Entra ID tenant holding the identity objects you assess. Nothing you do in this lab writes to the tenant except the one-off persona seeding step, which is clearly isolated.

Analyst Workstation PowerShell 7 (pwsh) Microsoft.Graph SDK Microsoft Graph graph.microsoft.com OAuth 2.0 / delegated scopes REST v1.0 endpoints Entra ID Tenant Northgate Financial Users and guests asmith / jpatel / lokafor Auth methods report MFA and SSPR registration Directory roles privileged assignments Sign-in activity last sign-in (dormancy) Baseline evidence CSV exports + risk summary query read

Component breakdown

ComponentPurposeTransportKey configuration
PowerShell 7 + Graph SDKRuns the assessment cmdletsLocal processModule installed at CurrentUser scope
Microsoft GraphAuthenticated control surface for EntraHTTPS 443Delegated OAuth scopes, consented at sign-in
Entra ID tenantHolds users, roles, registration and sign-in dataManaged by MicrosoftP2 trial licence assigned to enable premium data
CSV evidenceDated, portable audit artefactLocal diskOne folder per run, timestamped

Data flow

  1. Authenticate: Connect-MgGraph opens a browser sign-in and requests only the scopes you name, so consent is explicit and minimal.
  2. Query: each cmdlet calls a Graph v1.0 endpoint. The registration report, role membership and sign-in activity are separate endpoints, combined client-side.
  3. Assess: PowerShell filters the returned objects into risk categories (not MFA-capable, dormant, privileged, SMS-reliant).
  4. Evidence: results are written to timestamped CSVs and printed as a summary you can screenshot for the board.
PRODUCTION CONSIDERATION In a real tenant you would run recurring baselines as an app registration with application permissions and a certificate, not interactive delegated sign-in. Delegated sign-in is correct for a hands-on lab and for ad hoc assessment. Lab 11 converts this into an unattended, certificate-authenticated automation.
Section 7

Step-by-Step Implementation

Phase A - Connect with least privilege

REAL WORLD ANALOGYA 'scope' is simply a named permission, and requesting only read scopes is like being issued a visitor badge that opens the reading rooms but none of the store cupboards. Even if you trip and fall against a door, nothing opens. That is why the assessment connects read-only: your own tools physically cannot break anything while you survey.

1Sign in to Graph with read-only scopes

Purpose: establish an authenticated session that can read identity data and nothing more.

Context: a "scope" is a named permission such as User.Read.All. Requesting only read scopes means even a mistake in your script cannot modify the tenant.

Connect and confirm the session
# Read-only scopes for the whole baseline.
# User.Read.All          -> read user objects
# AuditLog.Read.All      -> read registration report and signInActivity
# Directory.Read.All     -> read directory roles and members
# RoleManagement.Read.Directory -> read role assignments
$readScopes = @(
  'User.Read.All',
  'AuditLog.Read.All',
  'Directory.Read.All',
  'RoleManagement.Read.Directory'
)

Connect-MgGraph -Scopes $readScopes -NoWelcome

# Confirm who you are and which scopes were granted:
$ctx = Get-MgContext
$ctx | Select-Object Account,TenantId,@{n='Scopes';e={($_.Scopes -join ', ')}}
VERIFICATION Get-MgContext returns your account, the tenant ID, and a scope list that includes the four read scopes above. If the browser consent prompt lists write permissions, you named the wrong scopes, disconnect with Disconnect-MgGraph and reconnect.

What just happened? You exchanged an interactive sign-in for a short-lived access token carrying exactly the four permissions you asked for. Every later cmdlet rides on that token. Because none of the scopes end in .ReadWrite.All, the tenant is safe from accidental change during assessment.

Phase B - Seed the Northgate personas

REAL WORLD ANALOGYAn empty tenant is like rehearsing a fire drill in an empty building: nothing to find, nothing to learn. Seeding personas is hiring a few actors to play typical employees, a finance clerk, an IT admin, a contractor who left, so the drill produces realistic findings. Enterprises do the same in their test tenants before touching production.
SECURITY WARNING This is the only step in the lab that writes to the tenant. It runs under separate write scopes so the write capability exists for exactly one step and no longer. If your tenant already has test users, you can skip this phase.

2Create test users with a deliberately mixed risk profile

Purpose: give the baseline something realistic to find: a normal user, an administrator, a dormant leaver and a guest.

Context: we reuse the Northgate personas from the wider Identity Bytes labs so the narrative stays consistent: asmith (finance), jpatel (IT operations, will be made an admin), lokafor (cyber security).

Connect with write scopes and create the users
# Elevate to write scopes ONLY for this seeding step.
Connect-MgGraph -Scopes 'User.ReadWrite.All','Directory.ReadWrite.All' -NoWelcome

# Resolve the tenant's default domain (for example contoso.onmicrosoft.com)
$domain = (Get-MgOrganization).VerifiedDomains |
          Where-Object { $_.IsDefault } |
          Select-Object -ExpandProperty Name
Write-Host "Default domain: $domain"

# Persona definitions. Passwords are temporary and must be changed at first sign-in.
$personas = @(
  @{ First='Aisha'; Last='Smith';  Alias='asmith';  Dept='Finance' },
  @{ First='Jay';   Last='Patel';  Alias='jpatel';  Dept='IT Operations' },
  @{ First='Lola';  Last='Okafor'; Alias='lokafor'; Dept='Cyber Security' },
  @{ First='Mark';  Last='Reeves'; Alias='mreeves'; Dept='Contractor (left)' }
)

foreach ($p in $personas) {
  $upn = "$($p.Alias)@$domain"
  $pwProfile = @{
    Password                      = 'N0rthgate!' + (Get-Random -Minimum 1000 -Maximum 9999)
    ForceChangePasswordNextSignIn = $true
  }
  New-MgUser -DisplayName "$($p.First) $($p.Last)" `
             -UserPrincipalName $upn `
             -MailNickname $p.Alias `
             -AccountEnabled `
             -PasswordProfile $pwProfile `
             -UsageLocation 'GB' `
             -Department $p.Dept | Out-Null
  Write-Host "Created $upn"
}
Make jpatel a privileged admin (so the baseline has a role holder to find)
# Activate the 'User Administrator' directory role if it is not already active,
# then add jpatel. Role template IDs are fixed GUIDs published by Microsoft.
# fe930be7-5e62-47db-91af-98c3a49a38b1 = User Administrator

$roleTemplateId = 'fe930be7-5e62-47db-91af-98c3a49a38b1'

$role = Get-MgDirectoryRole -All | Where-Object { $_.RoleTemplateId -eq $roleTemplateId }
if (-not $role) {
  $role = New-MgDirectoryRole -RoleTemplateId $roleTemplateId
}

$jpatel = Get-MgUser -Filter "userPrincipalName eq 'jpatel@$domain'"

New-MgDirectoryRoleMemberByRef -DirectoryRoleId $role.Id -BodyParameter @{
  '@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($jpatel.Id)"
}
Write-Host "jpatel added to $($role.DisplayName)"
VERIFICATION Run Get-MgUser -Filter "department eq 'Finance'" | Select DisplayName,UserPrincipalName. You should see Aisha Smith. If New-MgUser returns an authorization error, your session still holds only read scopes, reconnect with the write scopes shown above.

What just happened? You created four users whose combined shape mirrors a real tenant: an ordinary user, an admin, a security analyst, and a contractor who will read as dormant because it has never signed in. The baseline in Phase C now has genuine findings to surface rather than an empty tenant.

Phase C - Run the identity risk baseline

REAL WORLD ANALOGYThe registration report is the attendance register: it tells you who has actually collected and set up their security pass, not who was merely told to. Dormant accounts are hotel keycards that have not been swiped in months, still live, still able to open the room, and nobody watching. Counting both is the survey every enterprise remediation programme starts with.

3Read the MFA and SSPR registration report

Purpose: measure how many accounts can perform strong MFA and how many rely on SMS.

Context: the authentication methods registration report is the authoritative source for who is MFA-capable. It replaces the old and unreliable habit of inferring MFA from per-user settings.

Pull registration details and classify SMS reliance
# Reconnect read-only if you elevated in Phase B.
Connect-MgGraph -Scopes $readScopes -NoWelcome

# One row per user: MFA capability, SSPR status, and the methods they registered.
$reg = Get-MgReportAuthenticationMethodUserRegistrationDetail -All

$mfaCapable    = $reg | Where-Object { $_.IsMfaCapable }
$mfaNotCapable = $reg | Where-Object { -not $_.IsMfaCapable }
$ssprReg       = $reg | Where-Object { $_.IsSsprRegistered }

# SMS-reliant = has mobile phone method but no Authenticator push and no FIDO2 key.
# These are your migration targets for Lab 02.
$smsReliant = $reg | Where-Object {
  ($_.MethodsRegistered -contains 'mobilePhone') -and
  -not ($_.MethodsRegistered -contains 'microsoftAuthenticatorPush') -and
  -not ($_.MethodsRegistered -contains 'fido2SecurityKey')
}
INFO: freshly created users show as not capable In a brand new tenant your seeded users will report IsMfaCapable = false because they have not registered a method yet. That is correct and useful: it is exactly the "no MFA configured" population Lab 06 will automate the disabling of.

4Inventory privileged role holders

Purpose: produce the single authoritative list of who holds directory roles.

Context: a "directory role" grants administrative power in Entra (for example Global Administrator, User Administrator). Counting and naming these holders is the first move of any least-privilege programme.

Enumerate active directory roles and their members
$privReport = foreach ($role in (Get-MgDirectoryRole -All)) {
  $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id -All
  foreach ($m in $members) {
    [pscustomobject]@{
      Role        = $role.DisplayName
      MemberType  = ($m.AdditionalProperties['@odata.type'] -replace '#microsoft.graph.','')
      DisplayName = $m.AdditionalProperties['displayName']
      Upn         = $m.AdditionalProperties['userPrincipalName']
    }
  }
}

$privReport | Sort-Object Role | Format-Table -AutoSize
VERIFICATION The table lists at least Global Administrator (your own account) and User Administrator (jpatel). A row with a blank UPN and MemberType of "servicePrincipal" or "group" is normal, roles can be held by non-user objects, which is itself a finding worth noting.

5Find dormant accounts from sign-in activity

Purpose: identify enabled accounts that have not signed in recently, the classic leaver and stale-service-account risk.

Context: signInActivity is a premium property, it requires Entra ID P1 and the AuditLog.Read.All scope. It must be requested explicitly with -Property.

Query last sign-in and flag accounts idle beyond 90 days
$props = 'id','displayName','userPrincipalName','accountEnabled',
         'createdDateTime','userType','signInActivity'

$allUsers = Get-MgUser -All -Property $props

$cutoff = (Get-Date).AddDays(-90)

$dormant = $allUsers | Where-Object {
  $_.AccountEnabled -and
  (
    $null -eq $_.SignInActivity -or
    $null -eq $_.SignInActivity.LastSignInDateTime -or
    $_.SignInActivity.LastSignInDateTime -lt $cutoff
  )
}

$guests = $allUsers | Where-Object { $_.UserType -eq 'Guest' }

$dormant |
  Select-Object DisplayName,UserPrincipalName,
    @{n='LastSignIn';e={$_.SignInActivity.LastSignInDateTime}},
    @{n='AgeDays';e={ if($_.SignInActivity.LastSignInDateTime){[int]((Get-Date)-$_.SignInActivity.LastSignInDateTime).TotalDays} else {'never'} }} |
  Sort-Object AgeDays -Descending |
  Format-Table -AutoSize
INFO A newly created account that has never signed in shows a null last sign-in and is flagged dormant. In production you would exclude accounts younger than, say, 30 days to avoid flagging genuine new joiners. Tune the $cutoff and add a created-date guard for your own estate.

6Assemble the baseline into evidence

Purpose: combine every finding into timestamped CSVs and a printed risk summary suitable for a programme board.

Full script: Invoke-NorthgateIdentityBaseline.ps1
#requires -Version 7.0
# Northgate Financial - read-only identity risk baseline.
# Produces CSV evidence and a console summary. Changes nothing in the tenant.

$readScopes = @('User.Read.All','AuditLog.Read.All',
                'Directory.Read.All','RoleManagement.Read.Directory')
Connect-MgGraph -Scopes $readScopes -NoWelcome

$stamp  = Get-Date -Format 'yyyyMMdd-HHmm'
$outDir = Join-Path (Get-Location) "NorthgateBaseline-$stamp"
New-Item -ItemType Directory -Path $outDir -Force | Out-Null

# --- Registration (MFA / SSPR) ---
$reg = Get-MgReportAuthenticationMethodUserRegistrationDetail -All
$mfaCapable = $reg | Where-Object { $_.IsMfaCapable }
$smsReliant = $reg | Where-Object {
  ($_.MethodsRegistered -contains 'mobilePhone') -and
  -not ($_.MethodsRegistered -contains 'microsoftAuthenticatorPush') -and
  -not ($_.MethodsRegistered -contains 'fido2SecurityKey')
}
$reg | Select-Object UserPrincipalName,IsAdmin,IsMfaCapable,IsMfaRegistered,
        IsSsprRegistered,@{n='Methods';e={$_.MethodsRegistered -join ';'}} |
  Export-Csv (Join-Path $outDir 'registration.csv') -NoTypeInformation

# --- Privileged roles ---
$priv = foreach ($role in (Get-MgDirectoryRole -All)) {
  foreach ($m in (Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id -All)) {
    [pscustomobject]@{
      Role=$role.DisplayName
      MemberType=($m.AdditionalProperties['@odata.type'] -replace '#microsoft.graph.','')
      DisplayName=$m.AdditionalProperties['displayName']
      Upn=$m.AdditionalProperties['userPrincipalName']
    }
  }
}
$priv | Export-Csv (Join-Path $outDir 'privileged-roles.csv') -NoTypeInformation

# --- Dormancy and guests ---
$props = 'id','displayName','userPrincipalName','accountEnabled',
         'createdDateTime','userType','signInActivity'
$users  = Get-MgUser -All -Property $props
$cutoff = (Get-Date).AddDays(-90)
$dormant = $users | Where-Object {
  $_.AccountEnabled -and
  ($null -eq $_.SignInActivity.LastSignInDateTime -or
   $_.SignInActivity.LastSignInDateTime -lt $cutoff)
}
$guests  = $users | Where-Object { $_.UserType -eq 'Guest' }
$dormant | Select-Object DisplayName,UserPrincipalName,
  @{n='LastSignIn';e={$_.SignInActivity.LastSignInDateTime}} |
  Export-Csv (Join-Path $outDir 'dormant.csv') -NoTypeInformation

# --- Summary ---
$total = $users.Count
$summary = [ordered]@{
  'Report generated'      = (Get-Date).ToString('u')
  'Total users'           = $total
  'MFA-capable'           = "$($mfaCapable.Count) ($([math]::Round(($mfaCapable.Count/[math]::Max($total,1))*100))%)"
  'SMS-reliant (migrate)' = $smsReliant.Count
  'Privileged assignments'= $priv.Count
  'Dormant (90d+)'        = $dormant.Count
  'Guest accounts'        = $guests.Count
  'Evidence folder'       = $outDir
}
"`n===== NORTHGATE IDENTITY RISK BASELINE =====" | Write-Host -ForegroundColor Cyan
$summary.GetEnumerator() | ForEach-Object {
  '{0,-24}: {1}' -f $_.Key, $_.Value | Write-Host
}
VERIFICATION After the script runs you have a folder NorthgateBaseline-<timestamp> containing registration.csv, privileged-roles.csv and dormant.csv, plus a cyan summary block in the console showing counts. Open registration.csv and confirm one row per user.

What just happened? You produced the exact artefact Northgate's programme board asked for: a dated, repeatable measure of MFA coverage, SMS reliance, privileged sprawl and dormancy. Re-running it next week gives you a trend line, which is how you prove remediation is working.

Section 8

Testing and Validation

End-to-end test

  1. Run the full baseline script against your seeded tenant.
  2. Confirm the summary shows four or more total users and at least one privileged assignment.
  3. Register the Microsoft Authenticator app for asmith via aka.ms/mfasetup, then re-run the script. IsMfaCapable for asmith flips to true and the MFA-capable percentage rises. This proves the report reflects real change.

Common failure modes

SymptomCauseResolution
Insufficient privileges on the registration reportSession lacks AuditLog.Read.AllReconnect with the full read scope set
signInActivity is always nullTenant has no P1/P2 licence, or the property was not requestedAssign the P2 trial licence and include signInActivity in -Property
Get-MgReport... not recognisedReports sub-module not loadedRun Import-Module Microsoft.Graph.Reports or reinstall the meta-module
Registration report is emptyNew tenant, report data can lag up to a few hoursWait and re-run, or validate against the seeded user count first
Section 9

Security Analysis

What makes this implementation sound

What is intentionally simplified for the lab

Production hardening recommendations

Section 10

Cleanup Instructions

INFO: preserve for the track If you intend to continue to Lab 02, keep the four personas, they are reused throughout IB-ENTRA-SEC. Only run the removal below if you want a clean tenant.
Remove the seeded personas and disconnect
Connect-MgGraph -Scopes 'User.ReadWrite.All' -NoWelcome
$domain = (Get-MgOrganization).VerifiedDomains | Where-Object IsDefault | Select-Object -ExpandProperty Name

foreach ($alias in 'asmith','jpatel','lokafor','mreeves') {
  $u = Get-MgUser -Filter "userPrincipalName eq '$alias@$domain'" -ErrorAction SilentlyContinue
  if ($u) { Remove-MgUser -UserId $u.Id; Write-Host "Removed $alias" }
}

# End the Graph session.
Disconnect-MgGraph
VERIFICATION Get-MgContext returns nothing after Disconnect-MgGraph. The CSV evidence folders on disk are yours to keep or delete, they contain no secrets, only object metadata.
Section 12

Key Takeaways and Next Lab

Identity Bytes // IB-ENTRA-SEC Track // Lab 01 of 12. British English. For lab and training use against a disposable tenant only.