Bringing Old Scripts Forward
Recently, I wrote about the New-DistributionGroup cmdlet and explained how distribution lists remain so useful, even when Microsoft pushes Microsoft 365 Groups as the answer for all collaboration problems. A reader promptly wrote to say that distribution groups are different in Exchange Online than they are for Exchange Server and pointed out that PowerShell scripts written for on-premises consumption often don’t work in the cloud. They even cited a Practical 365 article from 2015 telling how to report the number of members in distribution lists as an example.
The script is to highlight large distribution lists of the type which might potentially cause Reply-All mail storms. Microsoft recently updated the reply-all control settings for Exchange Online to make mail storms less likely, but there’s no doubt that it’s a good idea to know if any very large distribution lists exist and, more importantly, who looks after these monsters.
The Difference Between Get-ADGroupMember and Get-AzureADGroupMember
The original script relies on the ability of the Get-ADGroupMember cmdlet (for Active Directory) to recursively fetch distribution list members. In other words, if a distribution list contains nested distribution lists, you need to fetch details of all members to know how many recipients will receive messages sent to the list. The Exchange transport service expands distribution lists during its fan-out process to create copies of messages for recipients, and that’s when the full number of recipients is known.
The problem about taking the original script to the cloud is that the equivalent Get-AzureADGroupMember cmdlet doesn’t handle recursion. The Graph API for Groups has a transitiveMembers call to return “a flat list of all nested members,” but that capability hasn’t reached the PowerShell cmdlet. To show how transitive members work, Figure 1 shows the result of a call to https://graph.microsoft.com/v1.0/groups/4f6a1e58-84cc-4365-b339-7520508c1cbc/transitiveMembers to retrieve members of a distribution list containing nested lists using the Graph Explorer tool.
If you’ve never used the Graph Explorer, you should spend some time to get used to it because it’s a great way to learn how to interact with Graph API calls.
Groups and Lists
Distribution lists are Exchange mail-enabled objects and exist in both the Exchange Online directory (EXODS) and Azure AD. They’re called distribution lists in the world of Exchange and distribution groups more generally across Microsoft 365, or just groups within Azure AD. Microsoft 365 Groups are much easier to process when it comes to member counts. First, you can’t nest Microsoft 365 Groups. Second, only tenant and guest accounts can be members. Third, the Get-UnifiedGroup cmdlet returns counts for tenant members and guest members. See this article for an example of how to report the membership of Microsoft 365 Groups.
Returning to PowerShell
It is straightforward to write a script to use the Graph API to fetch the membership of nested distribution lists, but let’s return to basic PowerShell and consider what we can do to solve the same problem as Get-ADGroupMember does on-premises.
In this article, Vasil Michev takes an in-depth look at the issues involved in generating a list of all members of all groups in an organization, including Microsoft 365 Groups, dynamic distribution lists, and so on. Although we could adapt Vasil’s script to do what we want, there’s no fun in that, so I spent an hour or so coming up with a solution to the problem of identifying large distribution lists.
The first rule of a successful PowerShell hacker is never to recreate the wheel. Always begin a coding project by searching to see what people have done to solve a problem, or at least part of the problem, that you face. In this case, a search to find how people handled the question of handling nested Azure AD groups turned up several useful functions, including one I decided to use in my script.
The Newly Coded Solution
The code is reasonably simple. First, find all distribution lists in the tenant, excluding those used for room lists.
For each list, we find its membership. In this example, I use Get-AzureADGroupMember rather than Get-DistributionGroupMember. Either will work. My decision was driven by the fact that the function I adapted used Azure AD. Finding the membership involves figuring out which members are simple (user accounts, guest accounts, and mail contacts) and which are groups. The membership of each nested group is resolved and added to the overall membership. At the end, the script removes any duplicate entries from the membership.
After processing a distribution list, the script extracts some statistics about the number of members, tenant accounts, guest accounts, groups, and “others” (mail contacts mostly). The script also generates a list of members. This information is stored in a PowerShell list.
Once the script has processed all the distribution lists, it outputs a CSV file and shows the results via the Out-GridView cmdlet (Figure 2).
You can download two versions of the script from GitHub. The first (described in this article) uses Exchange Online PowerShell; the second uses a mixture of PowerShell and Graph API requests. Feel free to improve the code in either and make the scripts meet your requirements. The point is that PowerShell code is very adaptable for different purposes, which is how it should be.