In this module
IAM0.6 Lab Setup and Cost Management
IAM0.1 through IAM0.4 audited your own tenant. IAM0.5 introduced the Northgate Engineering scenario with its 8 governance gaps. This section builds the NE lab environment in your M365 developer tenant — 15 persona accounts, groups with deliberate governance gaps, app registrations with varying credential states, guest accounts, and the Entra ID Governance trial license that unlocks lifecycle workflows, entitlement management, and access reviews from Module 2 onward.
What you need before starting
Two things: an M365 E5 developer tenant and a connection to Microsoft Graph PowerShell. If you already have both from an earlier Ridgeline course or your own lab environment, skip to the Governance trial activation section.
Estimated time: 45 minutes (first setup) or 25 minutes (existing tenant).
M365 E5 developer tenant
The Microsoft 365 Developer Program provides a free E5 tenant with 25 user licenses. The tenant is non-production — designed for development and learning. It includes everything this course requires: Entra ID P2, Exchange Online, SharePoint Online, Teams, Defender XDR, and Sentinel.
Microsoft Developer Program
Sign up: developer.microsoft.com/microsoft-365/dev-program
Sign in with a Microsoft account (personal or work). Select Set up E5 subscription. Choose a country, create an admin username and domain name (e.g., admin@yourlab.onmicrosoft.com). The tenant provisions in 2–3 minutes.
Verify: Sign in to entra.microsoft.com with your new admin account. Navigate to Identity → Overview. Confirm you see your tenant name and that the license shows Microsoft 365 E5 Developer.
The developer tenant renews automatically as long as you're actively developing — creating users, configuring policies, running PowerShell commands. The lab work in this course constitutes active development.
Connect to Microsoft Graph PowerShell
Install the Microsoft Graph PowerShell SDK if you haven't already:
Install-Module Microsoft.Graph -Scope CurrentUser -ForceConnect with the scopes needed for this setup:
Connect-MgGraph -Scopes "User.ReadWrite.All", "Group.ReadWrite.All",
"Application.ReadWrite.All", "Directory.ReadWrite.All",
"RoleManagement.ReadWrite.Directory"A browser consent prompt appears. Sign in with the admin account from your developer tenant and grant the permissions. Verify the connection:
Get-MgContext | Select-Object Account, TenantId, ScopesYou should see your admin UPN, the tenant ID, and the granted scopes. If any scope is missing, disconnect and reconnect with the full scope list.
Activate the Entra ID Governance trial
The E5 developer license includes Entra ID P2 — Conditional Access, Identity Protection, and basic PIM. It does not include Entra ID Governance, which provides lifecycle workflows, entitlement management, and access reviews. The course requires these from Module 2 onward.
Entra Admin Center
Identity → Overview → Manage trials (or Billing → Purchase services in the M365 admin center)
Search for Microsoft Entra ID Governance. Select the trial. It provides 90 days of full Governance functionality for up to 100 users. The trial is renewable — as long as you're actively using the features, Microsoft typically extends it.
Verify: After activation, navigate to Identity Governance in the Entra admin center sidebar. You should see Entitlement management, Access reviews, and Lifecycle workflows as available menu items. If these show "Requires license," the trial hasn't propagated yet — wait 10 minutes and refresh.
If the Governance trial isn't available in your region or has been previously used on this tenant, the course includes "Without Governance Licensing" callouts in every module where the feature is gated. You can complete Modules 0 and 1 without the Governance license. From Module 2 onward, the callouts provide Graph API and PowerShell workarounds for governance features that require the license.
Create the NE persona accounts
The following script creates 15 accounts representing NE personas across departments. The accounts are designed with deliberate governance gaps — some have complete attributes (department, manager, hire date), some are missing one or more. This mirrors the governance attribute coverage gaps you found in IAM0.1.
Entra Admin Center — Manual Alternative
Identity → Users → All users → New user → Create new user
If you prefer creating accounts through the portal: enter the Display name, User principal name, and generate a password. Under Properties, expand Job info to set Department, Job title, and Employee hire date for personas that should have these attributes. Leave them blank for the "gap" personas (James Whitfield, Sarah Blackwood, Mark Taylor, etc.) to reproduce the data quality findings from IAM0.1. The PowerShell script below creates all 15 accounts in one run — use whichever method you prefer.
Replace yourtenant.onmicrosoft.com with your developer tenant domain throughout.
$domain = "yourtenant.onmicrosoft.com"
$passwordProfile = @{
Password = "IAMcourse2026!"
ForceChangePasswordNextSignIn = $false
}
# Personas with full governance attributes
$fullPersonas = @(
@{ First="Rachel"; Last="Okafor"; Dept="Security"; Title="CISO";
Hire="2024-11-15" },
@{ First="Tom"; Last="Ashworth"; Dept="Security"; Title="SOC Analyst L1";
Hire="2023-08-01" },
@{ First="Priya"; Last="Sharma"; Dept="Security"; Title="SOC Analyst L1";
Hire="2022-06-15" },
@{ First="Marcus"; Last="Webb"; Dept="Security"; Title="Security Architect";
Hire="2023-01-10" },
@{ First="Elena"; Last="Petrova"; Dept="GRC"; Title="GRC Analyst";
Hire="2024-03-01" },
@{ First="Phil"; Last="Greaves"; Dept="IT"; Title="IT Director";
Hire="2022-03-01" }
)
# Personas with missing attributes (deliberate governance gaps)
$gapPersonas = @(
@{ First="James"; Last="Whitfield"; Dept="Engineering"; Title="Engineer";
Hire=$null },
@{ First="Sarah"; Last="Blackwood"; Dept=$null; Title="Marketing Lead";
Hire=$null },
@{ First="David"; Last="Chen"; Dept="Engineering"; Title="Senior Engineer";
Hire="2021-09-01" },
@{ First="Lisa"; Last="Okonkwo"; Dept="Finance"; Title="Finance Analyst";
Hire=$null },
@{ First="Mark"; Last="Taylor"; Dept=$null; Title=$null;
Hire=$null },
@{ First="Anna"; Last="Kowalski"; Dept="HR"; Title="HR Manager";
Hire="2023-06-01" },
@{ First="Ben"; Last="Hughes"; Dept="Engineering"; Title="Project Lead";
Hire=$null },
@{ First="Fatima"; Last="Al-Rashid"; Dept="Legal"; Title="Legal Counsel";
Hire="2024-01-15" },
@{ First="Chris"; Last="Morrison"; Dept="IT"; Title="Systems Admin";
Hire=$null }
)
$allPersonas = $fullPersonas + $gapPersonas
foreach ($p in $allPersonas) {
$upn = "$($p.First.ToLower()).$($p.Last.ToLower())@$domain"
$params = @{
DisplayName = "$($p.First) $($p.Last)"
UserPrincipalName = $upn
MailNickname = "$($p.First.ToLower()).$($p.Last.ToLower())"
AccountEnabled = $true
PasswordProfile = $passwordProfile
}
if ($p.Dept) { $params.Department = $p.Dept }
if ($p.Title) { $params.JobTitle = $p.Title }
if ($p.Hire) { $params.EmployeeHireDate = $p.Hire }
$user = New-MgUser @params
Write-Host "Created: $($user.DisplayName) ($upn)"
}
Write-Host "`nCreated $($allPersonas.Count) personas"After the script completes, verify the attribute coverage gaps:
$personas = Get-MgUser -Filter "startsWith(userPrincipalName, 'rachel.') or
startsWith(userPrincipalName, 'tom.') or startsWith(userPrincipalName, 'priya.') or
startsWith(userPrincipalName, 'marcus.') or startsWith(userPrincipalName, 'elena.') or
startsWith(userPrincipalName, 'phil.') or startsWith(userPrincipalName, 'james.') or
startsWith(userPrincipalName, 'sarah.') or startsWith(userPrincipalName, 'david.') or
startsWith(userPrincipalName, 'lisa.') or startsWith(userPrincipalName, 'mark.') or
startsWith(userPrincipalName, 'anna.') or startsWith(userPrincipalName, 'ben.') or
startsWith(userPrincipalName, 'fatima.') or startsWith(userPrincipalName, 'chris.')" `
-Property displayName, department, jobTitle, employeeHireDate
$personas | Select-Object displayName, department, jobTitle,
@{N='HireDate'; E={ if ($_.EmployeeHireDate) { "Set" } else { "MISSING" }}} |
Format-Table -AutoSizeYou should see 6 personas with complete attributes and 9 with varying gaps — matching the 23% missing department and 80% missing hire date patterns from IAM0.1.
Set up manager relationships
Manager assignments drive access reviews (manager-based reviewers) and lifecycle workflows (manager notification). Set up the reporting structure:
# Get user IDs
$rachel = (Get-MgUser -Filter "displayName eq 'Rachel Okafor'").Id
$tom = (Get-MgUser -Filter "displayName eq 'Tom Ashworth'").Id
$phil = (Get-MgUser -Filter "displayName eq 'Phil Greaves'").Id
# Security team reports to Rachel
@("Tom Ashworth", "Priya Sharma", "Marcus Webb") | ForEach-Object {
$user = Get-MgUser -Filter "displayName eq '$_'"
Set-MgUserManagerByRef -UserId $user.Id -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/users/$rachel"
}
Write-Host "Set manager: $_ → Rachel Okafor"
}
# Elena reports to Rachel
$elena = Get-MgUser -Filter "displayName eq 'Elena Petrova'"
Set-MgUserManagerByRef -UserId $elena.Id -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/users/$rachel"
}
Write-Host "Set manager: Elena Petrova → Rachel Okafor"
# IT team reports to Phil
@("Chris Morrison") | ForEach-Object {
$user = Get-MgUser -Filter "displayName eq '$_'"
Set-MgUserManagerByRef -UserId $user.Id -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/users/$phil"
}
Write-Host "Set manager: $_ → Phil Greaves"
}
# Deliberately leave some personas without managers
Write-Host "`nPersonas without managers (deliberate gap):"
Write-Host " Mark Taylor, Sarah Blackwood, Ben Hughes"Create NE groups
The groups simulate NE's group landscape — some governed, some ownerless, some from completed projects:
$groups = @(
@{ Name="SG-All-Staff"; Type="Security"; Desc="All NE staff"; Owner=$phil },
@{ Name="SG-Engineering"; Type="Security"; Desc="Engineering department"; Owner=$null },
@{ Name="SG-Finance-Readers"; Type="Security"; Desc=$null; Owner=$null },
@{ Name="SG-SOC-Operations"; Type="Security"; Desc="SOC team operational access"; Owner=$rachel },
@{ Name="SG-IT-Admins"; Type="Security"; Desc="IT administration"; Owner=$phil },
@{ Name="SG-Engineering-Leads"; Type="Security"; Desc="Engineering team leads"; Owner=$null },
@{ Name="SG-SharePoint-HR-Site"; Type="Security"; Desc=$null; Owner=$null },
@{ Name="SG-Defender-Alerts"; Type="Security"; Desc="Defender alert recipients"; Owner=$rachel },
@{ Name="SG-Compliance-Training"; Type="Security"; Desc=$null; Owner=$null },
@{ Name="SG-Project-Alpha"; Type="Security"; Desc=$null; Owner=$null },
@{ Name="Team-Office-Migration-2023"; Type="M365"; Desc=$null; Owner=$null },
@{ Name="Team-SOC-L1"; Type="M365"; Desc="SOC Level 1 analyst team"; Owner=$tom },
@{ Name="Team-Vendor-Onboarding-2024"; Type="M365"; Desc=$null; Owner=$null }
)
foreach ($g in $groups) {
$params = @{
DisplayName = $g.Name
MailEnabled = ($g.Type -eq "M365")
MailNickname = $g.Name.ToLower() -replace '[^a-z0-9]', ''
SecurityEnabled = $true
}
if ($g.Type -eq "M365") {
$params.GroupTypes = @("Unified")
}
if ($g.Desc) { $params.Description = $g.Desc }
$newGroup = New-MgGroup @params
Write-Host "Created group: $($g.Name) ($($g.Type)) — Owner: $(if ($g.Owner) { 'Set' } else { 'NONE' })"
if ($g.Owner) {
New-MgGroupOwner -GroupId $newGroup.Id -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/users/$($g.Owner)"
}
}
}Now add persona memberships that simulate the accumulation pattern — Priya holds finance access from her previous role, project groups from completed initiatives, and her current SOC groups:
# Priya's accumulated memberships (the mover problem)
$priya = (Get-MgUser -Filter "displayName eq 'Priya Sharma'").Id
@("SG-All-Staff", "SG-Engineering", "SG-Finance-Readers", "SG-Project-Alpha",
"Team-Office-Migration-2023", "Team-SOC-L1", "SG-SOC-Operations",
"SG-SharePoint-HR-Site", "SG-Defender-Alerts", "SG-Engineering-Leads",
"SG-Compliance-Training") | ForEach-Object {
$gid = (Get-MgGroup -Filter "displayName eq '$_'").Id
New-MgGroupMember -GroupId $gid -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$priya"
} -ErrorAction SilentlyContinue
}
Write-Host "Priya Sharma: 11 group memberships (mover problem)"
# Tom — SOC groups only
$tomId = (Get-MgUser -Filter "displayName eq 'Tom Ashworth'").Id
@("SG-All-Staff", "Team-SOC-L1", "SG-SOC-Operations", "SG-Defender-Alerts") | ForEach-Object {
$gid = (Get-MgGroup -Filter "displayName eq '$_'").Id
New-MgGroupMember -GroupId $gid -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$tomId"
} -ErrorAction SilentlyContinue
}
# Phil — over-privileged IT Director
$philId = (Get-MgUser -Filter "displayName eq 'Phil Greaves'").Id
@("SG-All-Staff", "SG-IT-Admins", "SG-Engineering", "SG-Finance-Readers",
"SG-SOC-Operations", "SG-Project-Alpha", "Team-Office-Migration-2023") | ForEach-Object {
$gid = (Get-MgGroup -Filter "displayName eq '$_'").Id
New-MgGroupMember -GroupId $gid -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$philId"
} -ErrorAction SilentlyContinue
}
Write-Host "Phil Greaves: 7 group memberships (cross-department accumulation)"Create NE app registrations
These app registrations simulate the non-human identity landscape from IAM0.4 — a mix of active integrations, completed projects, and over-permissioned tools:
$appSpecs = @(
@{ Name="NE-CRM-Integration"; Perms=@("User.Read.All") },
@{ Name="NE-Data-Migration-Tool"; Perms=@("User.ReadWrite.All",
"Group.ReadWrite.All", "Mail.ReadWrite", "Directory.ReadWrite.All") },
@{ Name="NE-Backup-Service"; Perms=@("Mail.Read", "Files.Read.All") },
@{ Name="NE-Dev-Test-App"; Perms=@("User.Read") },
@{ Name="NE-Legacy-Reporting"; Perms=@("Files.ReadWrite.All",
"Sites.ReadWrite.All") },
@{ Name="NE-Vendor-SSO"; Perms=@("User.Read.All", "GroupMember.Read.All") }
)
$graphAppId = "00000003-0000-0000-c000-000000000000"
foreach ($spec in $appSpecs) {
# Create app registration
$app = New-MgApplication -DisplayName $spec.Name
# Add a client secret
$secret = Add-MgApplicationPassword -ApplicationId $app.Id -PasswordCredential @{
DisplayName = "Lab secret"
EndDateTime = (Get-Date).AddMonths(6)
}
Write-Host "Created app: $($spec.Name) — Secret expires: $(
(Get-Date).AddMonths(6).ToString('yyyy-MM-dd'))"
}
# Create one app with an already-expired secret (for credential health testing)
$expiredApp = New-MgApplication -DisplayName "NE-Abandoned-POC"
# Note: You cannot create an already-expired secret via Graph API.
# The expired credential scenario will be simulated in Module 9.
Write-Host "Created app: NE-Abandoned-POC (credential expiry simulated in M9)"Create guest accounts
Guest accounts simulate the stale external access pattern from IAM0.1:
Entra Admin Center — Manual Alternative
Identity → Users → All users → New user → Invite external user
Enter the guest's email address and display name. Don't send the invitation email (toggle off Send invite message) — these are lab accounts that won't redeem the invitation. After creation, the guest appears in the user list with Invitation status: Pending. This pending state is deliberate — it reproduces the unredeemed invitation pattern from IAM0.1 where guest accounts exist in the directory with assigned access but the external user has never authenticated.
$guests = @(
@{ Email="alex.rivera@contoso.com"; Name="Alex Rivera (Contoso)" },
@{ Email="vendor.pentest@external.com"; Name="Vendor-PenTest" },
@{ Email="sarah.kim@partner.com"; Name="Sarah Kim (Partner)" },
@{ Email="old.contractor@legacy.com"; Name="Old Contractor" },
@{ Email="project.contact@vendor.com"; Name="Project Contact (Vendor)" }
)
foreach ($g in $guests) {
$invitation = New-MgInvitation -InvitedUserEmailAddress $g.Email `
-InvitedUserDisplayName $g.Name `
-InviteRedirectUrl "https://myapps.microsoft.com" `
-SendInvitationMessage:$false
Write-Host "Invited guest: $($g.Name) ($($g.Email))"
}
Write-Host "`n5 guest accounts created (invitations not sent — lab accounts)"Assign admin roles to Phil (the over-privileged IT Director)
Phil holds multiple directory roles as permanent assignments — the separation of duties problem from IAM0.3. This is the most common privilege model in small-to-midsize organizations: one person has Global Administrator because they set up the tenant, and they accumulate additional roles as responsibilities grow. Nobody removes the earlier assignments because the admin center doesn't flag the accumulation as a problem.
Entra Admin Center — Manual Alternative
Identity → Roles & admins → Roles & admins → select Global Administrator → Add assignments
Search for Phil Greaves and assign the role. Repeat for Exchange Administrator, SharePoint Administrator, and User Administrator. After all four assignments, select Phil's user account → Assigned roles. You should see all four roles listed with scope "/" (tenant-wide) and assignment type "Active" (permanent). This view is the separation of duties finding that Module 7 addresses — four tenant-wide permanent roles on a single identity, with no PIM activation, no approval workflow, and no time boundary.
$philId = (Get-MgUser -Filter "displayName eq 'Phil Greaves'").Id
$roles = @("Global Administrator", "Exchange Administrator",
"SharePoint Administrator", "User Administrator")
foreach ($roleName in $roles) {
$role = Get-MgDirectoryRole -Filter "displayName eq '$roleName'" -ErrorAction SilentlyContinue
if (-not $role) {
$template = Get-MgDirectoryRoleTemplate | Where-Object { $_.DisplayName -eq $roleName }
$role = New-MgDirectoryRole -RoleTemplateId $template.Id
}
New-MgDirectoryRoleMember -DirectoryRoleId $role.Id -BodyParameter @{
"@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$philId"
} -ErrorAction SilentlyContinue
Write-Host "Assigned role: $roleName → Phil Greaves"
}What the lab simulates and why
Each lab component exists to reproduce a specific governance gap that a module addresses. The lab isn't a simplified demo — it's a deliberately broken environment designed to generate real findings when you run the course diagnostics.
15 personas with mixed attribute coverage: 8 personas have complete governance attributes (department, hire date, manager). 7 have deliberate gaps — missing department, missing hire date, missing manager, or missing all three. When you run the data quality audit in IAM1.3, the coverage percentages reflect real incomplete data. When you deploy lifecycle workflows in Module 2, the workflows genuinely skip the personas with missing hire dates. The gap is structural, not simulated.
13 groups with mixed governance state: Some groups have owners and descriptions. Others are deliberately ownerless and undescribed. When you run the group architecture audit in IAM1.4, the ownerless and undescribed counts are real. When you deploy access reviews in Module 5, the reviews genuinely can't route to owners for the ownerless groups.
7 app registrations with credential patterns: Some have valid secrets, some have near-expiry secrets, one has no governance documentation. When you run the credential health audit in IAM0.4, the expired and near-expiry findings are genuine Graph API results, not mock data.
5 guest accounts (invitations not sent): All guests have "Pending" invitation status. Some have group memberships despite never authenticating. The stale guest pattern from IAM0.1 is live — the guests genuinely hold access in your tenant's directory without having verified their identity.
Phil's 4 permanent roles: The separation of duties finding from IAM0.3 is real — Phil genuinely holds Global Administrator, User Administrator, Exchange Administrator, and SharePoint Administrator as permanent tenant-wide assignments. Module 7's PIM conversion will convert these to eligible assignments with activation workflows.
The lab with 15 accounts produces the same governance patterns as a production tenant with 810. The percentages differ (15 accounts vs 810) but the structural gaps — missing attributes, ownerless groups, stale guests, over-privileged admins, ungoverned app registrations — are architecturally identical. The diagnostics you build work on both scales.
Cost modeling for production governance
The lab uses a free developer tenant and a Governance trial. In production, governance licensing has real cost. Understanding the cost model helps you build the business case in Module 13.
The developer tenant and Governance trial cover the full course. In production, the Governance add-on costs $7/user/month on P1 or $4/user/month on P2. For NE's 810 users on E5: $3,240/month ($38,880/year). Without the Governance license, lifecycle workflows, entitlement management, and advanced access reviews are gated. The workaround: Graph API and PowerShell scripts that replicate approximately 70% of governance capability. The 30% gap — AI-assisted review recommendations, visual workflow designer, custom Logic App extensions — is documented as residual risk. IAM1.6 covers the full licensing landscape and workarounds.
Entra ID P2 — included in M365 E5 ($57/user/month). If you're on E3 ($36/user/month), P2 is an add-on at $9/user/month. P2 provides Identity Protection, risk-based Conditional Access, and basic PIM.
Entra ID Governance — add-on at $7/user/month on top of P2. Provides lifecycle workflows, entitlement management, access reviews with AI recommendations, and advanced PIM features. For an 810-person organization like NE, that's $5,670/month or $68,040/year.
Entra Suite — bundled license at $12/user/month that includes P2 + Governance + additional features (Verified ID, Internet Access, Private Access). For NE: $9,720/month or $116,640/year.
Agent 365 — $15/user/month for AI agent governance features (Entra Agent ID, agent blueprints, agent entitlement management). Pricing may change as the feature exits preview.
The "Without Governance Licensing" callouts throughout the course show what you can achieve with Graph API and PowerShell when the Governance license isn't available. The workarounds cover approximately 70% of governance capabilities. The 30% gap — AI-assisted review recommendations, advanced lifecycle workflow triggers, automated entitlement management — is documented as residual risk in each module's ADR.
At Northgate Engineering: Rachel Okafor's budget conversation with the CFO: Governance licensing for 810 users costs $68,040/year. A consulting firm's IAM implementation engagement costs $50,000–$150,000 and delivers a one-time configuration the internal team can't maintain. The course teaches your team to build and operate the program — ongoing capability, not a one-time project. The licensing cost is the operational expense. The capability is the asset.
Verify your lab environment
Run this verification script to confirm everything is set up correctly:
Write-Host "=== LAB ENVIRONMENT VERIFICATION ==="
$users = Get-MgUser -All -Property displayName, department, employeeHireDate
$nePersonas = $users | Where-Object {
$_.DisplayName -match "Okafor|Ashworth|Sharma|Webb|Petrova|Greaves|Whitfield|Blackwood|Chen|Okonkwo|Taylor|Kowalski|Hughes|Al-Rashid|Morrison"
}
Write-Host "NE personas: $($nePersonas.Count) / 15 expected"
$groups = Get-MgGroup -All -Property displayName
$neGroups = $groups | Where-Object { $_.DisplayName -match "^(SG-|Team-)" }
Write-Host "NE groups: $($neGroups.Count) / 13 expected"
$apps = Get-MgApplication -All -Property displayName
$neApps = $apps | Where-Object { $_.DisplayName -like "NE-*" }
Write-Host "NE app registrations: $($neApps.Count) / 7 expected"
$guests = Get-MgUser -All | Where-Object { $_.UserType -eq "Guest" }
Write-Host "Guest accounts: $($guests.Count) / 5 expected"
# Check governance trial
try {
$lcw = Get-MgIdentityGovernanceLifecycleWorkflow -ErrorAction Stop
Write-Host "Governance trial: ACTIVE (lifecycle workflows accessible)"
} catch {
Write-Host "Governance trial: NOT ACTIVE — activate before Module 2"
}
Write-Host "`n=== VERIFICATION COMPLETE ==="If any count is wrong, re-run the relevant section above. The persona and group setup is idempotent — running it again won't create duplicates if the names already exist (the commands will fail silently on existing objects).
Tenant configuration baseline
Before starting Module 1, confirm your developer tenant has the default security configuration that the course assumes. These settings ship with the E5 developer license and shouldn't need changes, but verifying them prevents confusion when Module 6 (Application Access Governance) and Module 10 (Workload Identity) reference them.
Entra Admin Center — Configuration Baseline
Identity → Applications → Enterprise applications → Consent and permissions → User consent settings
Verify the setting is "Allow user consent for apps from verified publishers, for selected permissions." This is the default for E5 tenants. Module 6 modifies the consent policy. Don't change it now.
Identity → Users → User settings
Verify Users can register applications is set to "Yes" (default). Verify Restrict non-admin users from creating tenants is "Yes" (default). Module 6 tightens these settings as part of application access governance.
Protection → Conditional Access → Policies
The developer tenant should have Security Defaults enabled (or a small set of default CA policies). Module 10 builds Conditional Access for workload identities. Don't create policies now — the course builds them with specific governance rationale documented in ADRs.
# Quick tenant configuration check
Write-Host "=== TENANT CONFIGURATION BASELINE ==="
# Check security defaults
$org = Get-MgOrganization
Write-Host "Tenant: $($org.DisplayName)"
# Check user settings
$authPolicy = Get-MgPolicyAuthorizationPolicy
Write-Host "Users can register apps: $($authPolicy.DefaultUserRolePermissions.AllowedToCreateApps)"
Write-Host "Users can create tenants: $(if ($authPolicy.DefaultUserRolePermissions.AllowedToCreateTenants) { 'Yes' } else { 'Restricted' })"
# Check CA policies
$caPolicies = Get-MgIdentityConditionalAccessPolicy -All -ErrorAction SilentlyContinue
Write-Host "Conditional Access policies: $(if ($caPolicies) { $caPolicies.Count } else { '0 (Security Defaults likely active)' })"
Write-Host "`nBaseline configuration verified. Do not modify these settings — the course changes them with documented ADR rationale."Your lab environment is complete. 15 personas, 13 groups, 7 app registrations, 5 guest accounts, Phil's 4 over-privileged roles, the Governance trial, and a verified configuration baseline. Module 1 uses this environment for every diagnostic query.
Decision-point simulation
Scenario 1. Your E5 developer tenant's 90-day Entra ID Governance trial is about to expire. You're on Module 6 of 14. Do you renew the trial, purchase a Governance license, or restructure the remaining modules to avoid Governance features?
Renew the trial. Microsoft allows trial extensions for active developer tenants — the Developer Program dashboard shows renewal options. Purchasing a single Governance license ($7/user/month on P1) for your 15 test accounts costs $105/month, which is unnecessary for a learning environment. If renewal fails, the Budget Callout sections in each module document the Graph API and PowerShell workarounds that cover approximately 70% of Governance capability without the license.
Scenario 2. You want to test Conditional Access policies that block sign-ins from non-compliant devices. Your lab tenant has no Intune-managed devices. How do you approach this without enrolling a production device?
Use Report-Only mode. Conditional Access in Report-Only evaluates the policy against every sign-in without enforcing it. You see what would have been blocked in the sign-in logs without actually blocking anything. For device compliance testing specifically, create the CA policy targeting "Require compliant device," set it to Report-Only, and sign in from your lab machine. The sign-in log shows "Report-only: Failure" with the reason "Device is not compliant" — proving the policy logic works without needing an enrolled device. Module 5 covers this pattern in detail.
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.