Over time, a tenant collects groups that nobody uses. A project ends, a team is reorganized, or everyone moves on, and the group stays behind. Groups with no people in them clutter the directory and make it harder to tell which groups still matter. This short script lists every group in your Microsoft Entra tenant that has no users, shows how many members of any kind each one still has, and saves the list to a CSV.
What you need
- PowerShell 7 and the Microsoft Graph PowerShell SDK, including the
Microsoft.Graph.Groupsmodule. - An account that can read groups and their members in your tenant. The script asks for the read-only
Group.Read.Allpermission.
The script
Connect-MgGraph -Scopes "Group.Read.All" -NoWelcome
Write-Host "Running... Please wait. This may take a while."
$Groups = Get-MgGroup -All
$Failed = 0
$Results = foreach ($Group in $Groups) {
try {
$UserCount = [int](Get-MgGroupMemberCountAsUser -GroupId $Group.Id -ConsistencyLevel eventual)
if ($UserCount -eq 0) {
$MemberCount = [int](Get-MgGroupMemberCount -GroupId $Group.Id -ConsistencyLevel eventual)
[PSCustomObject]@{
DisplayName = $Group.DisplayName
GroupId = $Group.Id
UserCount = $UserCount
MemberCount = $MemberCount
HasMembers = $MemberCount -gt 0
}
}
}
catch {
$Failed++
Write-Warning "Could not check '$($Group.DisplayName)': $($_.Exception.Message)"
}
}
if ($Failed -gt 0) {
Write-Warning "$Failed group(s) could not be checked, so the results may be incomplete."
}
if (-not $Results) {
Write-Host "No groups with zero users were found."
return
}
$Results | Export-Csv "./EmptyEntraGroups.csv" -NoTypeInformation
$Results | Format-Table DisplayName, UserCount, MemberCount, HasMembers, GroupId -AutoSize
What it does
- Signs in to Microsoft Graph with the read-only
Group.Read.Allpermission and gets every group in the tenant. - Asks Graph how many users are in each group. A group with any users is skipped.
- For each group with zero users, asks for its total member count and records three numbers:
UserCount,MemberCountandHasMembers. - Saves the list to
EmptyEntraGroups.csvin the folder where you run the script, and shows the results on screen as a table. If it can’t check a group, it warns you and moves on to the next one, and it tells you at the end that the results may be incomplete. If no group has zero users, it says so and doesn’t write a CSV. - Only reads data. It doesn’t delete or change anything.
How to read the results
A group can have users, devices, organizational contacts, service principals and other groups as members, and Microsoft Learn lists all of them. That is why the script reports both counts.
- HasMembers is False. The group has nothing in it at all. These are the best candidates for cleanup.
- HasMembers is True. The group has no users, but something else is in it, such as devices or another group. Find out what before you decide. The group may be doing a job.
Check before you delete anything
A group on this list is a candidate for cleanup, not a verdict. A few things can make the results look cleaner than they are:
- Service principals may not be counted. Microsoft documents a known issue where the v1.0 endpoint doesn’t list service principals as group members. A group whose only members are service principals can show
HasMembersas False. - Hidden membership groups may look empty. Reading the members of a hidden membership group takes an extra permission,
Member.Read.Hidden, which this script doesn’t request. - The counts can lag. The script uses the eventual consistency counts that Graph provides, and Microsoft notes they use an index that might not reflect very recent changes. A group someone just filled may still show as empty.
- New groups are empty too. And a group with no users may still be referenced by a policy or an app assignment, so check where a group is used before you remove it.
The script is in my GitHub, along with other scripts for Microsoft 365 admins. It is free and open source under the MIT license.