Table of Contents
A New Take on an Old Favorite Script
In the past, I’ve written several times about using PowerShell to report the membership of Exchange Online distribution lists. Support of multiple mail-enabled objects, including nested groups, makes the extraction of full distribution list membership trickier than simply running the Get-DistributionGroupMember cmdlet and a variety of techniques have been used over the years to expand and report all members using Exchange Online and Azure AD cmdlets and Microsoft Graph API requests.
Normally, I don’t return to the same topic again and again. The reason why I’m back here for a third bite at the cherry is that Microsoft will deprecate the Azure AD PowerShell module on June 30, 2023. Although it’s possible to use Microsoft Graph API requests to report distribution list membership (with a caveat), some would prefer to convert their scripts to another PowerShell module rather than going full-blown Graph. I guess the Microsoft Graph PowerShell SDK is that half-way stop, so here goes.
Using the Graph SDK with Group Memberships
It’s important to understand that the Microsoft Graph PowerShell SDK interacts with Azure AD groups. Distribution lists are Exchange Online objects that synchronize to appear as groups in Azure AD. However, although distribution lists support membership of mail-enabled objects that are unique to Exchange, like mail-enabled public folders, these objects don’t show up in membership reported by Azure AD. The reason is simple: the objects don’t exist in Azure AD. What does show up are the objects supported by Azure AD: user accounts (including guests), contacts, and groups. That’s what you see when you run the Get-MgGroupMember cmdlet to retrieve group membership.
Because distribution groups support nested groups, we need a way to expand the members of nested groups and resolve duplicate member entries that might exist. This can be done using a Graph query to fetch transitive members. The transitive query does all the work to expand nested groups and return a unified set of members.
Because a Graph API request exists to fetch transitive members, an equivalent cmdlet is available in the Microsoft Graph PowerShell SDK. That cmdlet is Get-MgGroupTransitiveMember. For example, this call fetches all the members in the group pointed to by the variable $DL.ExternalDirectoryObjectId.
[array]$Members = Get-MgGroupTransitiveMember -GroupId $DL.ExternalDirectoryObjectId
Objects synchronized from Exchange Online to Azure AD store their Azure AD identifier (GUID) in the ExternalDirectoryObjectId property. For instance, a mailbox stores the identifier for its owning Azure AD user account in the property. Azure AD treats a distribution list like any other group, and so it has a group identifier that’s stored in the property. That identifier is the one we use to extract distribution list membership with Get-MgGroupTransitiveMember.
Get-MgGroupTransitiveMember returns a list of identifiers. In earlier versions of the Microsoft Graph PowerShell SDK, you had to resolve the identifiers into useful information, like the display names of individual group members. Now, the group cmdlets return the information in an array of member details stored in the AdditionalProperties property, which means that we can find what we want by extracting it from the array. For convenience, I usually extract the array into a separate variable:
[array]$MemberData = $Members.AdditionalProperties
You might ask why Microsoft decided to update the groupcmdlets to output the member data in a separate property instead of changing the default to output the list of members (which is how cmdlets like Get-AzureADGroupMember work). One explanation is that changing the output of a cmdlet will break existing scripts. In that context, it’s understandable to include a new property.
Parsing Distribution List Membership
After fetching the transitive membership for a distribution list, the remaining task is to figure out how many members of the different categories are in the set (members, contacts, and groups). This is easily done by counting the items in the set. After it gathers this basic information about the group, the script updates a PowerShell list with the data.
You can drive some other processing from the list. For instance, you might decide to convert any distribution list with over 100 members to a team (use the same kind of approach as described here to covert a dynamic distribution list to a team). An easier decision might be to remove any distribution list found with zero members on the basis that they’re no longer in use. This is easily done with:
$Report | Where-Object {$_.Members -eq 0} | Remove-DistributionGroup
To be safe, I left the confirmation prompt in place so that you’re asked to confirm the deletion of each distribution list. You can suppress the prompt by adding -Confirm:$False to the command.
Reporting Distribution List Membership
The final stage is to generate output, which the script does in the form of a CSV file and HTML file (Figure 1). This ground is well-known and there’s no mystery in the code needed to generate the files.

Converting from Azure AD cmdlets to Microsoft Graph PowerShell SDK cmdlets is not challenging – once you understand how the Graph SDK works. The trick is to make no assumptions about the input parameters or the output a cmdlet produces. You might expect things to work in a certain way, but the chances are that they won’t, so go into the conversion in the spirit of a voyage of discovery and you won’t be disappointed. To help, here’s the script to report distribution list members using the Microsoft Graph PowerShell SDK.
Learn about exploiting Exchange Online and the rest of Office 365 by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s important and how best to protect your tenant.
Hello
I love this report .. but i wanted to know how to just filter out a certain domain name. I run a multi tenant exchange environment and it takes forever to run through 14k DLs. i imagine it being a -Filter {-like@domainname} type of command but it just errors out on me.
Please help
thank you again for creating this
Change the command that fetches the DLs to something like this:
PS C:\temp> $DLs = Get-DistributionGroup -ResultSize Unlimited -Filter {RecipientTypeDetails -ne “Roomlist”} | Select DisplayName, ExternalDirectoryObjectId, ManagedBy, primarysmtpaddress | Where-Object {$_.PrimarySmtpAddress -like “*office365itpros.com*”}
(Change office365itpros.com for whatever domain name you want to search for)
thank you What if mY DLs are On Prem and are not 365 managed groups or 365 DLs? Would the command be different?
If the on-premises DLs are synchronized to Azure AD, the command will find them.