In this module
IAM1.4 Group Architecture for IAM
IAM1.3 measured data quality and established the minimum thresholds for deploying governance mechanisms. This section examines the group architecture that those mechanisms act on. Groups are the primary access assignment mechanism in M365 — security groups grant SharePoint access, M365 groups grant Teams membership, role-assignable groups grant Entra directory roles. If the group architecture is sprawling and ungoverned, every governance mechanism that targets groups inherits that disorder.
Groups are access infrastructure — not an admin convenience
Every time you add a user to a group, you're making an access decision. That group might grant read access to a SharePoint site, membership in a Teams channel, eligibility for a Conditional Access policy, or assignment to an Entra directory role. The group is the mechanism — the pipe that connects an identity to a resource. When groups are well-governed, access flows through documented, reviewed, owned channels. When groups are ungoverned, access accumulates silently through channels nobody tracks.
Module 3 builds the group architecture for the IAM program. This section audits the current state — how many groups exist, what types they are, who owns them, which have descriptions, which are dynamic, and where the governance gaps are. The audit you produce here becomes the baseline that Module 3's architecture redesign addresses.
Estimated time: 55 minutes.
Figure IAM1.4 — Four group types with different governance characteristics. Security groups are the workhorse but lack expiry and auto-membership. Dynamic groups solve the mover problem but depend on data quality. Role-assignable groups have the strictest controls but are rare.
Groups are created for projects, teams, and access needs — and never cleaned up. A 3-year-old tenant has 200+ groups with names like SG-Project-Alpha, Team-Migration-2023, and FinanceReaders. 30% have no owner. 40% have no description. 15% are empty but still referenced in CA policies or SharePoint permissions. Nobody knows which groups are authoritative, which are redundant, or which grant access to what. Access reviews target groups but reviewers can't evaluate membership without knowing what the group does.
The group landscape — portal view first
Start with what the admin sees day-to-day.
Entra Admin Center
Identity → Groups → All groups
This page lists every group in your tenant. Note the column headers: Group type (Security or Microsoft 365), Membership type (Assigned, Dynamic user, Dynamic device), and Source (Cloud or Windows Server AD). Use the Add filter button to filter by group type or membership type.
Count the total groups. Scan the names — look for patterns. Do you see a naming convention? Or a mix of formats like SG-Engineering, Engineering-Team, eng_group, and All Engineering that all serve roughly the same purpose?
Click any group. The Overview shows the group type, membership type, and member/owner counts. Click Owners in the left nav. If the owner list is empty, nobody is accountable for this group's membership. Click Members to see who's in the group. Click Properties to see the description — if it's blank, nobody documented what this group is for.
The portal gives you the individual group view. Now quantify the landscape programmatically.
Group classification audit
Four group types exist in Entra ID, each with different governance characteristics. Query all groups and classify them:
Connect-MgGraph -Scopes "Group.Read.All", "GroupMember.Read.All"
$groups = Get-MgGroup -All -Property id, displayName, groupTypes,
securityEnabled, mailEnabled, membershipRule, description,
createdDateTime, isAssignableToRole, onPremisesSyncEnabled
$classification = $groups | ForEach-Object {
$type = if ($_.IsAssignableToRole) { "Role-assignable" }
elseif ($_.GroupTypes -contains "DynamicMembership") { "Dynamic" }
elseif ($_.GroupTypes -contains "Unified") { "M365" }
elseif ($_.SecurityEnabled -and -not $_.MailEnabled) { "Security" }
elseif ($_.SecurityEnabled -and $_.MailEnabled) { "Mail-enabled security" }
else { "Distribution" }
[PSCustomObject]@{
Name = $_.DisplayName
Type = $type
Source = if ($_.OnPremisesSyncEnabled) { "Synced" } else { "Cloud" }
HasDescription = [bool]$_.Description
Created = $_.CreatedDateTime.ToString("yyyy-MM-dd")
MembershipRule = if ($_.MembershipRule) { $_.MembershipRule.Substring(0,
[Math]::Min(60, $_.MembershipRule.Length)) + "..." } else { "" }
}
}
$classification | Group-Object Type | Select-Object Name, Count |
Sort-Object Count -Descending | Format-Table -AutoSizeName Count
---- -----
Security 142
M365 47
Dynamic 12
Mail-enabled security 8
Distribution 3
Role-assignable 2Security groups (142) — the workhorse. Most access in M365 flows through security groups. They grant permissions to SharePoint sites, scope Conditional Access policies, assign application access, and gate resources in entitlement management. Their governance gap: membership is entirely manual unless the group is converted to dynamic. Someone adds a member; nobody removes them.
M365 groups (47) — tied to Teams, SharePoint sites, and shared mailboxes. When you create a Teams team, an M365 group is created automatically. M365 groups have a built-in governance advantage: they support expiry policies (the group can be set to expire after a period of inactivity, requiring the owner to renew it). Their governance gap: many are created automatically by Teams and then abandoned when the team goes inactive.
Dynamic groups (12) — the only groups with automated membership. Members are added and removed based on attribute rules (e.g., user.department -eq "Security"). They're the governance-friendly group type because membership adjusts automatically when attributes change — solving the mover problem for any attribute the rule evaluates. Their governance gap: they depend entirely on data quality. A dynamic rule targeting department works for 80% of your population (IAM1.3) and silently excludes the 20% with no department value.
Examine your existing dynamic group rules to understand what attributes they evaluate:
$dynamicGroups = $groups | Where-Object { $_.GroupTypes -contains "DynamicMembership" }
$dynamicGroups | Select-Object displayName,
@{N='Rule'; E={ $_.MembershipRule }},
@{N='Members'; E={
(Get-MgGroupMember -GroupId $_.Id -All -ErrorAction SilentlyContinue).Count
}} | Format-ListdisplayName : SG-Dynamic-Security-Dept
Rule : (user.department -eq "Security")
Members : 4
displayName : SG-Dynamic-All-Employees
Rule : (user.userType -eq "Member") -and (user.accountEnabled -eq True)
Members : 15The first rule depends on department — it catches the 4 NE personas with department: Security. The second rule uses userType and accountEnabled, which are always populated, so it captures all active members. That's the design trade-off: rules based on reliably populated attributes (userType, accountEnabled) produce complete membership. Rules based on governance attributes (department, employeeType) produce partial membership proportional to data quality.
Entra Admin Center — Dynamic Group Rules
Identity → Groups → All groups → select a dynamic group → Dynamic membership rules
The rule builder shows the current membership rule. For simple rules (one or two expressions), the visual builder displays them as dropdowns. For complex rules (more than five expressions or nested logic), you'll see the raw syntax in the text editor. Click Validate rules to test whether a specific user matches the rule — enter a UPN and the validator reports whether the user would be included or excluded, and why.
Try it now: select your department-scoped dynamic group, click Validate rules, and enter the UPN of a user with no department value (e.g., mark.taylor@yourtenant.onmicrosoft.com). The validator will show "Not a member" because the rule requires a department value. Then enter a user with department set (e.g., rachel.okafor@yourtenant.onmicrosoft.com). The validator confirms membership. This is the data quality gap made visible — the rule works, but it only works for identities with the attribute it evaluates.
Dynamic groups require Entra ID P1 licensing for each unique user who's a member. Your E5 developer tenant includes P1, so dynamic groups work in your lab. In production E3 environments, the P1 licensing cost is an additional $9/user/month for all users who are members of at least one dynamic group.
Synced groups vs cloud-native groups
Groups synced from on-premises AD have additional governance constraints. Like synced user accounts, synced groups have their authoritative source in on-premises AD. Membership changes made through Entra ID are overwritten on the next sync cycle. Ownership, description, and group type changes must happen in on-premises AD for synced groups.
$syncedGroups = ($groups | Where-Object { $_.OnPremisesSyncEnabled }).Count
$cloudGroups = $groups.Count - $syncedGroups
Write-Host "Cloud-native groups: $cloudGroups (manage in Entra ID)"
Write-Host "Synced groups: $syncedGroups (manage in on-prem AD)"If your tenant has a significant number of synced groups, the governance model must account for the dual management surface. Access reviews that remove members from a synced group will have the removal overwritten on the next sync. Module 3 addresses this with guidance on group migration strategies — converting synced groups to cloud-native where the on-premises dependency no longer exists, and documenting the synced groups that must remain on-premises managed.
Role-assignable groups (2) — groups that can be assigned Entra directory roles. These have the strictest governance: the isAssignableToRole property must be set at creation and cannot be changed afterward (the property is immutable). Role-assignable groups cannot be dynamic — membership must be assigned, not rule-based. This is a deliberate security constraint: if role-assignable groups supported dynamic membership, any administrator who can modify dynamic group rules could indirectly grant themselves directory roles. Only Privileged Role Administrators can create role-assignable groups.
Ownership and description audit
Ownership determines accountability. Description determines discoverability. Query both:
$ownershipAudit = $groups | ForEach-Object {
$ownerCount = (Get-MgGroupOwner -GroupId $_.Id -All -ErrorAction SilentlyContinue).Count
$memberCount = (Get-MgGroupMember -GroupId $_.Id -All -ErrorAction SilentlyContinue).Count
[PSCustomObject]@{
Name = $_.DisplayName
Type = ($classification | Where-Object { $_.Name -eq $_.DisplayName }).Type
Owners = $ownerCount
Members = $memberCount
HasDesc = [bool]$_.Description
Source = if ($_.OnPremisesSyncEnabled) { "Synced" } else { "Cloud" }
}
}
$totalGroups = $groups.Count
$ownerless = ($ownershipAudit | Where-Object { $_.Owners -eq 0 }).Count
$noDesc = ($ownershipAudit | Where-Object { -not $_.HasDesc }).Count
$empty = ($ownershipAudit | Where-Object { $_.Members -eq 0 }).Count
Write-Host "=== GROUP GOVERNANCE AUDIT ==="
Write-Host "Total groups: $totalGroups"
Write-Host "Ownerless: $ownerless ($([math]::Round($ownerless/$totalGroups*100))%)"
Write-Host "No description: $noDesc ($([math]::Round($noDesc/$totalGroups*100))%)"
Write-Host "Empty (0 members): $empty ($([math]::Round($empty/$totalGroups*100))%)"=== GROUP GOVERNANCE AUDIT ===
Total groups: 214
Ownerless: 67 (31%)
No description: 89 (42%)
Empty (0 members): 31 (14%)31% ownerless. These groups have no one accountable for membership decisions. When an access review targets these groups, the review can't route to an owner — it either falls to a fallback reviewer or the group is skipped. When someone requests removal from one of these groups, there's no owner to approve the change. When the group's purpose ends, there's no owner to decommission it. Ownerless groups persist indefinitely.
Entra Admin Center — Finding Ownerless Groups
Identity → Groups → All groups → click any group → Owners
If the owner list is empty, this group is ownerless. The admin center doesn't provide a built-in filter for "groups with no owners" — you can see owners per group, but you can't filter the list to show only ownerless groups. That's why the PowerShell audit above is necessary for the full picture. The portal is for verifying individual groups. PowerShell is for auditing the landscape.
To assign an owner: on the group's Owners page, click Add owners, search for the user, and select them. The owner receives the ability to manage group membership and properties. For governance purposes, every group should have at least one owner — preferably two, so ownership doesn't lapse if one person leaves.
42% without descriptions. A group named SG-Project-Alpha with no description provides no information about what access it grants, what project it relates to, or whether it's still needed. When a reviewer encounters this group in an access review, they can't evaluate membership because they don't know what the group does. The description field is the minimum documentation a group needs for governance — what access does this group grant, who should be a member, and when was it last reviewed.
14% empty. Thirty-one groups with zero members. These are either groups created for future use (acceptable if documented) or remnants of completed projects, decommissioned teams, or failed experiments. Empty groups clutter the directory, confuse administrators searching for the right group, and create noise in governance audits.
Group sprawl and redundancy
Sprawl isn't just about quantity. It's about redundancy — multiple groups serving the same purpose, created by different people at different times, with overlapping but non-identical membership.
# Find groups with similar names (potential duplicates)
$nameGroups = $groups | Group-Object {
($_.DisplayName -replace '[^a-zA-Z]', '').ToLower()
} | Where-Object { $_.Count -gt 1 }
Write-Host "=== POTENTIAL DUPLICATE GROUPS ==="
$nameGroups | ForEach-Object {
Write-Host " Similar names ($($_.Count) groups):"
$_.Group | ForEach-Object {
$mc = (Get-MgGroupMember -GroupId $_.Id -All -ErrorAction SilentlyContinue).Count
Write-Host " $($_.DisplayName) — $mc members, created $($_.CreatedDateTime.ToString('yyyy-MM-dd'))"
}
}The output reveals groups that were created independently for the same purpose. In NE's tenant, you might find SG-Engineering, Engineering-Team, and All-Engineering with overlapping membership. Each grants slightly different access. Nobody knows which is authoritative. When a new engineer joins, Phil adds them to whichever group he remembers — or all three. When someone leaves, Phil removes them from whichever group he finds first.
Entra Admin Center — Comparing Group Membership
Identity → Groups → All groups → search for a group name pattern (e.g., "engineering")
If multiple groups appear with variations of the same name, open each one and compare the Members list. Note overlapping members across the groups. This comparison — which the portal lets you do visually by opening groups in separate browser tabs — reveals whether the groups are genuinely different (scoped to different resources) or redundant (same purpose, created by different admins at different times).
Group consolidation is Module 3 work. The assessment here documents the redundancy. The redesign happens later.
What groups actually grant access to
The governance question for every group isn't just who's in it — it's what access does membership provide. A group with 50 members that grants read access to a public SharePoint site has a different governance weight than a group with 5 members that grants write access to the finance system. The access review for the first group can be annual. The review for the second should be quarterly.
Entra Admin Center — Group Resource Assignments
Identity → Groups → All groups → select a group → Assigned roles (if role-assignable) or Applications
The Assigned roles tab shows any Entra directory roles granted through this group. The Applications tab shows enterprise applications the group is assigned to. For M365 groups, the connected resources (Teams team, SharePoint site, shared mailbox) are listed on the Overview page.
For security groups, the portal doesn't directly show SharePoint site permissions or Conditional Access policy assignments — those are configured in the respective admin centers. To see the full picture of what a security group grants access to, you'd check: SharePoint admin center (site permissions), Entra CA policies (group inclusions/exclusions), enterprise application assignments, and any Intune configuration profiles that target the group. This cross-admin-center visibility gap is one reason groups need descriptions — the description should state what access the group grants so reviewers don't have to trace it across four admin centers.
Query the application assignments for your groups:
$groupAppAssignments = $groups | ForEach-Object {
$appAssignments = Get-MgGroupAppRoleAssignment -GroupId $_.Id `
-ErrorAction SilentlyContinue
if ($appAssignments.Count -gt 0) {
[PSCustomObject]@{
Group = $_.DisplayName
Apps = ($appAssignments | ForEach-Object { $_.ResourceDisplayName }) -join ", "
Count = $appAssignments.Count
}
}
} | Where-Object { $_ }
Write-Host "=== GROUPS WITH APPLICATION ASSIGNMENTS ==="
Write-Host "Groups granting app access: $($groupAppAssignments.Count)"
$groupAppAssignments | Format-Table -AutoSizeGroups with application assignments are higher-governance-weight than groups with only SharePoint access. Application access typically means the group grants permissions to line-of-business systems, SaaS applications, or internal tools. These groups warrant more frequent reviews, stricter ownership requirements, and documented approval processes for membership changes.
Naming convention assessment
Naming conventions make groups discoverable, filterable, and automatable. A consistent convention means Conditional Access policies can target SG-* groups, automation scripts can identify department groups by prefix, and administrators can find the right group without searching through 214 entries.
Assess your tenant's current naming patterns:
$prefixPattern = $groups | ForEach-Object {
$prefix = if ($_.DisplayName -match '^([A-Za-z]+-)')
{ $Matches[1] }
elseif ($_.DisplayName -match '^([A-Za-z]+_)')
{ $Matches[1] }
else { "(No prefix)" }
[PSCustomObject]@{ Prefix = $prefix; Name = $_.DisplayName }
}
$prefixPattern | Group-Object Prefix | Sort-Object Count -Descending |
Select-Object -First 10 | Format-Table Name, Count -AutoSizeName Count
---- -----
(No prefix) 98
SG- 62
Team- 27
DL- 12
App- 8
Role- 4
Proj- 398 groups — nearly half — have no consistent prefix. These are the groups that can't be filtered by convention, can't be targeted by automation, and can't be identified by purpose from the name alone. The 62 groups with SG- prefix follow a convention — but does that convention mean "Security Group" (administrative type) or something more specific? Without documentation, the prefix tells you the group type but not the purpose.
Module 3 designs the naming convention. For now, document what exists: how many groups follow a pattern, how many don't, and what the existing prefixes mean (if anything).
At Northgate Engineering: Phil Greaves created most of NE's groups over four years. He started with
SG-for security groups andTeam-for M365 groups. Over time, other admins created groups without following the convention —Engineering-Team,FinanceReaders,project-alpha-access. The 98 groups without a consistent prefix are the ones Phil didn't create. Nobody enforced the convention because no convention was formally documented. The naming convention design in Module 3 starts with cataloging what exists and defining a standard that covers all group types, all purposes, and all scopes.
Group age and lifecycle
Groups, like identities, have a lifecycle. Groups created for projects should be decommissioned when the project ends. Groups created for teams should be reviewed when the team reorganizes. Query the age distribution:
$ageDistribution = $groups | ForEach-Object {
$ageMonths = [math]::Round(((Get-Date) - $_.CreatedDateTime).TotalDays / 30)
$members = (Get-MgGroupMember -GroupId $_.Id -All -ErrorAction SilentlyContinue).Count
[PSCustomObject]@{
Name = $_.DisplayName
AgeMonths = $ageMonths
Members = $members
Bracket = if ($ageMonths -lt 6) { "0-6 months" }
elseif ($ageMonths -lt 12) { "6-12 months" }
elseif ($ageMonths -lt 24) { "1-2 years" }
else { "2+ years" }
}
}
$ageDistribution | Group-Object Bracket | Sort-Object { $_.Name } |
ForEach-Object {
$emptyInBracket = ($_.Group | Where-Object { $_.Members -eq 0 }).Count
Write-Host " $($_.Name): $($_.Count) groups ($emptyInBracket empty)"
}Groups older than 2 years with zero members are decommissioning candidates. Groups older than 1 year with no owner and no description are governance blind spots — they grant access to something, but nobody knows what, nobody is accountable, and nobody reviews the membership.
Entra Admin Center — Group Expiry Policies
Identity → Groups → Expiration
M365 groups support built-in expiry policies. You can set groups to expire after 180, 365, or a custom number of days. When a group approaches expiry, the owner is notified and must renew it. If nobody renews, the group is soft-deleted (recoverable for 30 days, then permanently deleted). This is the only built-in lifecycle mechanism for groups.
Security groups don't support expiry policies — they persist indefinitely unless manually deleted. This is why security group governance requires access reviews (Module 5) and manual lifecycle management rather than the automatic expiry M365 groups provide. For organizations where security groups are the primary access assignment mechanism (which is most organizations), the absence of automatic expiry makes the access review program the only mechanism that prevents security group membership from becoming permanent by default.
At Northgate Engineering: Rachel Okafor runs the group audit and finds: 214 total groups, 67 ownerless (31%), 89 undescribed (42%), 31 empty (14%), 12 dynamic (6%), and 98 without a naming prefix (46%). She categorizes the findings into three remediation priorities. Immediate: delete the 31 empty groups after confirming no downstream dependencies (Conditional Access policies, application assignments, or SharePoint permissions that reference them). Short-term (2 weeks): assign owners to the 67 ownerless groups — starting with the groups that have application assignments, since those grant the most significant access. Medium-term (Module 3): design and deploy the naming convention, convert manual groups to dynamic where data quality supports it, and document every group's purpose in the description field. The group audit numbers become the baseline — Module 3 measures improvement against them.
Reusable script — the group architecture audit from this section:
# IAM1.4 — Group Architecture Audit
Connect-MgGraph -Scopes "Group.Read.All", "GroupMember.Read.All"
$groups = Get-MgGroup -All -Property id, displayName, groupTypes,
securityEnabled, mailEnabled, description, createdDateTime,
isAssignableToRole, onPremisesSyncEnabled, membershipRule
# Classification
$totalGroups = $groups.Count
$security = ($groups | Where-Object { $_.SecurityEnabled -and -not $_.MailEnabled -and
-not ($_.GroupTypes -contains "Unified") -and -not $_.IsAssignableToRole }).Count
$m365 = ($groups | Where-Object { $_.GroupTypes -contains "Unified" }).Count
$dynamic = ($groups | Where-Object { $_.GroupTypes -contains "DynamicMembership" }).Count
$roleAssignable = ($groups | Where-Object { $_.IsAssignableToRole }).Count
Write-Host "=== GROUP CLASSIFICATION ==="
Write-Host "Total: $totalGroups | Security: $security | M365: $m365 | Dynamic: $dynamic | Role-assignable: $roleAssignable"
# Governance metrics
$ownerless = 0; $noDesc = 0; $empty = 0
foreach ($g in $groups) {
if ((Get-MgGroupOwner -GroupId $g.Id -All -ErrorAction SilentlyContinue).Count -eq 0) { $ownerless++ }
if (-not $g.Description) { $noDesc++ }
if ((Get-MgGroupMember -GroupId $g.Id -All -ErrorAction SilentlyContinue).Count -eq 0) { $empty++ }
}
Write-Host "`n=== GROUP GOVERNANCE ==="
Write-Host "Ownerless: $ownerless ($([math]::Round($ownerless/$totalGroups*100))%)"
Write-Host "No description: $noDesc ($([math]::Round($noDesc/$totalGroups*100))%)"
Write-Host "Empty: $empty ($([math]::Round($empty/$totalGroups*100))%)"
# Naming convention
$noPrefix = ($groups | Where-Object {
$_.DisplayName -notmatch '^[A-Za-z]+-' -and $_.DisplayName -notmatch '^[A-Za-z]+_'
}).Count
Write-Host "No naming prefix: $noPrefix ($([math]::Round($noPrefix/$totalGroups*100))%)"
# Source
$synced = ($groups | Where-Object { $_.OnPremisesSyncEnabled }).Count
$cloud = $totalGroups - $synced
Write-Host "`n=== SOURCE ==="
Write-Host "Cloud-native: $cloud | Synced: $synced"Decision-point simulation
Scenario 1. NE has 214 groups. Your audit shows 31 are empty and 67 have no owner. Phil Greaves wants to delete all 31 empty groups immediately to "clean up the directory." Why might that be premature?
Empty groups aren't necessarily unused. A dynamic group with a rule targeting department -eq "Quantum Computing" is empty because NE has no quantum computing department — but the group might be pre-staged for a planned team. A static group might be empty because the project it served just ended, but the associated SharePoint site still contains data under retention. Before deleting, check: does the group own any resources (Teams, SharePoint, Planner)? Is it referenced in any Conditional Access policy, app assignment, or lifecycle workflow? Is it a PIM-enabled group? Delete only after confirming no downstream dependencies.
Scenario 2. A department lead requests a new group for their team of 12. They want a static group they can manage themselves. Your governance standard prefers dynamic groups based on the department attribute. Which do you create?
If the 12 members all share the same department value and that value is reliably populated, a dynamic group is correct — it self-maintains as people join and leave the department. If the team is cross-functional (members from 3 departments working on a project), a dynamic group won't work — the membership criteria can't be expressed as an attribute rule. Create a static group with the department lead as owner, a naming convention that marks it as project-scoped, and an expiry policy. The decision depends on whether the membership criteria can be expressed as an attribute condition.
Scenario 3. Your dynamic group for the Finance department has 45 members. You know Finance has 48 people. Three are missing because their department attribute is set to "Finance & Accounting" instead of "Finance." Who's responsible for the mismatch — the group rule or the data?
The data. The group rule is correct — department -eq "Finance" does exactly what it says. The 3 users have a non-standard department value. This is a data quality issue from section 1.3, not a group architecture issue. The fix: standardize the 3 users' department to "Finance" (the canonical value from your data standard). The lesson: dynamic group reliability is a direct function of attribute data quality.
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.