Looking for The Best Way to Find Memberless Groups

The question arose about how to find groups without members using the Graph APIs. This is a common issue, especially in large Microsoft 365 tenants where group membership gradually declines over time due to people leaving the company or moving roles. Microsoft’s ownerless group policy is a way to address the issue of locating groups that end up without owners for the same reasons, but it only handles Microsoft 365 groups and doesn’t check for zero memberships. An answer is needed to deal with all the group types supported in Microsoft 365.

A discussion in the GitHub repository of the Microsoft Graph PowerShell SDK development team (always a good place to find tidbits about the Graph) offers various ways to approach the problem. The approaches cited here and elsewhere usually fetch all groups and apply a client-side filter to find memberless groups.

Although the group resource type defined by the Graph supports collections of owners and members, these structures must be expanded to reveal owners or members. The List Groups Graph API can’t filter and expand the owners or members collections in a single operation to find memberless or ownerless groups. Most of the proposed solutions I’ve seen therefore work by fetching the set of groups and using a client-side filter or ForEach-Object loop to check the membership of each group.

The Exchange Solution for Memberless Groups

The Get-UnifiedGroup cmdlet from the Exchange Online management module has two hidden group calculated properties to hold membership counts. These properties are stored with the group objects in EXODS (the Exchange Online Directory Store) instead of Entra ID. One property holds the count of members, the other has the count of external (guest) members.

For example, to find Microsoft 365 Groups with a zero membership count, run:

Get-UnifiedGroup -Filter {GroupMemberCount -eq 0}

This filter is run server-side, so it’s reasonably effective. However, the Get-UnifiedGroup cmdlet only handles Microsoft 365 Groups. Distribution lists, security groups, and mail-enabled security groups are ignored.

The Graph Solution

The Get-MgGroup cmdlet from the Microsoft Graph PowerShell SDK handles all types of Microsoft 365 groups except dynamic distribution lists. Exchange Online does not synchronize dynamic distribution lists to Entra ID, so these objects are invisible to the Graph APIs.

This variation on the fetch and filter solution uses the Get-MgGroup cmdlet to fetch all groups, expanding the members collection as the Graph retrieves objects. Only a single expandable property can be processed as the Graph fetches objects, so you can either look for memberless groups or ownerless groups at one time.

To prove the concept and minimize the amount of data fetched, only two properties are requested – Members and the group identifier. After fetching the groups, a client-side filter finds the set with zero membership. Increasing the page size to 999 (the maximum) minimizes the number of transactions necessary to fetch the complete set.

Write-Host "Checking for groups with no members..."
[array]$NoMembers = Get-MgGroup -All -PageSize 999 -ExpandProperty "members(`$select=id)" -Property Members, Id | Select-Object Members, Id | Where-Object {($_.Members.count) -eq 0}

$NoMembers
 
Members Id
------- --
{}      0335947a-60ce-4f46-a7e0-47531da381c4
{}      0644e3ba-a489-471f-aadd-3bd29a0f3391
{}      07ef82f5-6fd6-4a20-a47d-6c7249579255
{}      0a022083-29b3-46f7-aa61-6e830e88d167

The same request made against the List Groups Graph API, looks like this:

$uri = "https://graph.microsoft.com/v1.0/groups?`$expand=members(`$select=id)&`$select=id" 
[array]$data = Invoke-MgGraphRequest -Method get -Uri $Uri
[array]$NoMembers = $Data.Value | Where-Object {$_.Members.count -eq 0}

Because the Graph limits the number of objects retrieved by requests, you must paginate to retrieve the full set of results. Specifying the All parameter for the Get-MgGroup cmdlet handles pagination for the cmdlet.

This solution works in that it finds the set of memberless groups. When I ran the code for the first time in my tenant, I was surprised to find quite so many ownerless groups. Clearly my tenant’s housekeeping is not up to scratch.

Creating a Script to Report Memberless Groups

A set of group identifiers isn’t any good to a tenant administrator. A more developed solution creates a report of the memberless groups with the details needed to investigate the groups. I therefore wrote a script to:

  • Fetch more information about each group such as the display name, security enabled flag, created date, and so on.
  • For each group, check its type. If the group is not a Microsoft 365 group and is mail-enabled, it’s likely to be a distribution list, so the script checks if this is true.
  • Generates a report (Figure 1) and outputs the data as a CSV file.
  • Does the same for ownerless groups (the technique is the same and checks the Owners property instead of Members).
Finding memberless groups
Figure 1: Finding memberless groups

You can download the complete script from GitHub.

A Word on Performance

Fetching and analyzing every group in a large tenant is not going to be a quick operation. If you’re faced with the task of processing tens of thousands of groups, I would break processing up into batches, perhaps by using the first letter of group display names to create 26 batches. It’s possible that the parallel processing capabilities introduced in PowerShell 7 can help too.

Looking for ownerless or memberless groups is not a daily task. Consider running the job as an Azure Automation scheduled runbook that uploads the output files to a SharePoint Online site or sends the files as attachments to administrators via email. Once a month should be enough to keep these problem groups under control.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Leave a Reply