How to Create a Report About the Membership of Microsoft 365 Groups (and Teams)

Writing a Script to Report Microsoft 365 Group Memberships

Updated: 19 January 2023 – See this article for a new version of the script based on the Microsoft Graph PowerShell SDK.

Hot on the heels of the discussion about how to create a printable report listing the membership of a Microsoft 365 group (or team), the question is: “How can I create a report listing the members of all groups in my tenant?” Given the widespread use of Teams, the request is often to report teams membership.

A HTML report of all members in all Microsoft 365 Groups in a tenant

Report Teams membership
Figure 1: A HTML report detailing all members for all the Microsoft 365 Groups in a tenant

The Usual Approach Works, but It’s Slow

It’s a good question, and it’s one that is answered elsewhere, such as Steve Goodman’s take on the topic. However, all the approaches I have seen to date have attacked the problem as follows:

  • Run the Get-UnifiedGroup cmdlet to create a list of groups.
  • For each group, use the Get-UnifiedGroupLinks cmdlet to fetch the membership (and owners).
  • Export the results in a CSV file.

Apart from its slowness, there’s nothing wrong with this approach. The Get-UnifiedGroup cmdlet is a “fat” cmdlet. It fetches a lot of information to deliver the set of properties for each group. And the Get-UnifiedGroupLinks cmdlet is also pretty heavy. Put the two together, and things will be slow. This is fine if you have only a couple of hundred groups to process. It’s not so good when you have thousands.

Process Users, Not Groups

I decided to take a different tack. Instead of processing one group at a time, the script should process users. Basically:

  • Get a list of users from Azure Active Directory. This includes tenant and guest accounts.
  • Drop the accounts that belong to the tenant but aren’t licensed. These include accounts used for room and resource mailboxes, shared mailboxes, and service accounts, none of which will be in group membership.
  • Use Get-UnifiedGroup to return a list of team-enabled groups. Although expensive, at least the call uses a server-side filter, so Exchange Online does the processing to return the set. The set of teams is put into a hash table for quick lookup when we need to know if a group is team-enabled. As noted below, a Graph query is faster at retrieving groups and teams.
  • For each user, use the Get-Recipient cmdlet to fetch a list of Microsoft 365 groups the user belongs to. This sounds as if it should take a lot of processing, but it doesn’t because we can use a server-side filter based on the user’s distinguished name.
  • If some groups are returned, extract information for each group, check if it is team-enabled, and update a PowerShell list with the details.
  • Update another list with summary information about the user, such as how many groups they belong to and a list of the display names for those groups.
  • If the user doesn’t belong to any groups (many guest accounts are in this category), only output the summary data.
  • After processing all users, generate some statistics and create three output files:
    • A HTML report in two sections. First, a listing of group membership with a single line for each group a user belongs to. Second, a one-line summary for each user reporting how many groups they are in and the display names of those groups.
    • A CSV file of the group membership data.
    • A CSV file of the user membership summary data.

The script can be downloaded from GitHub. In testing, it took around a half-second per account (Figure 2), which isn’t too bad considering the amount of processing done.

Creating a Microsoft 365 Groups membership report with PowerShell
Figure 2: Creating a Microsoft 365 Groups membership report with PowerShell

Groups with no members are ignored by the script. These groups might have owners, but the lack of members mean that they are not picked up when checking group membership on a per-user basis.

Searching for Speed in a Teams Membership Report

A script powered by the Graph API will deliver faster results in places like fetching a list of team-enabled groups (using the list groups API). You can also use the Get-MgGroup cmdlet from the Microsoft Graph PowerShell SDK to return the list of team-enabled groups. The set of groups a user belongs to is found using the list user transitive member of API. For example, a call like https://graph.microsoft.com/v1.0/users/{GUID}/transitiveMemberOf returns the set of groups that the account with the object identifier (GUID) is a member of. Using the Microsoft Graph PowerShell SDK, the code to return the set of groups a user belongs to would be something like this:

$User = Get-MgUser -UserId James.Ryan@office365itpros.com
$Uri = "https://graph.microsoft.com/v1.0/users/" + $User.Id + "/transitiveMemberOf "
[array]$UserGroups = Invoke-MgGraphRequest -Uri $Uri -Method Get

I also wrote a Graph version of the script, which you can also find on GitHub. Remember that you must register an app in Azure AD, assign the app the necessary permissions, and create an app secret (or other credentials) before you can use this version. On the upside, the Graph version is faster and scales better for large tenants.


Learn much more about interacting with Microsoft 365 Groups and Teams through PowerShell by subscribing to the Office 365 for IT Pros eBook. Monthly updates keep you up to date with what’s happening across the Microsoft 365 ecosystem.

7 Replies to “How to Create a Report About the Membership of Microsoft 365 Groups (and Teams)”

  1. Thanks for the script and the explanation. I want to see if I can extend this a bit to include membership for each person in any security-only groups. The Get-Recipient seems to only return email-related groups or objects, correct? Is there a way to include other groups in this or do I need to build another loop to use Get-AzureADGroupMember instead?

    1. Correct. Get-Recipient only handles mail-enabled objects. I haven’t looked into how to approach the problem for security groups. One day I will… Starting off with Get-AzureADGroupMember sounds like a start but it could be very intensive if there are many groups to check.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.