Updating the Distribution List Count Script for the Graph
In June, I described how I upgraded a PowerShell script written for Exchange on-premises to report the count of members in distributions to work with Exchange Online. Or more precisely, with Azure AD rather than Active Directory. While everything works just fine, the problem is that Microsoft is moving away from the Azure AD Graph API that the Azure AD cmdlets use. After June 30, 2022, Microsoft will no longer provide security updates for the Azure AD Graph, nor will they support its use. This doesn’t mean that you can’t continue to use the Azure AD PowerShell module; it just means that Microsoft won’t deliver support for any problems you encounter. The cmdlets in the module work well, so I don’t anticipate many problems, but the writing is on the wall.
Microsoft’s path forward is the Graph PowerShell SDK, a wrapper around many different Graph API calls. The problem is that the SDK is still under development and doesn’t work as well as it will (eventually). And the nagging doubt is that perhaps it might just be best to use the underlying Graph API calls instead.
Using Graph API Calls
With that in mind, let’s explore what it takes to convert a script using Azure AD cmdlets to Graph API calls. Our starting point is the script to report distribution list counts. The end is the Graph version (both available in GitHub).
PowerShell scripts can invoke Graph API calls using the Invoke-RestMethod or Invoke-WebRequest cmdlets. Either work, but the Invoke-RestMethod cmdlet is more aligned with the REST-based Graph APIs than the more general-purpose Invoke-WebRequest cmdlet is. Before you can use these cmdlets to call Graph APIs in a script, some housekeeping is necessary.
First, you need to register an app in Azure AD to use as an entry point to Graph API calls. To allow access to data via the Graph, the app receives consent from an administrator to use a set of permissions. In this case, the app needs the following Graph application permissions: Directory.Read.All, Group.Read.All, and User.Read.All permissions.
Second, you need to authenticate. In this case, the script is intended to run interactively (other arrangements are necessary to run scripts non-interactively). Authentication is performed by asking Azure AD for an access token. To prove that the app is authorized, it passes an app secret, which you generate for the app from the Azure AD admin center. A combination of the tenant identifier, the app identifier (also generated by Azure AD), and the app secret is sufficient to allow the app to authenticate.
If you examine the Graph version of the script, you’ll see that it has more lines of code than the original PowerShell version. Housekeeping needs some code to get done, but once you’ve figured out the code that works for you (taking your own coding style, error handling, and so on into account), you’ll probably have code that can be lifted from one script to another to fulfil the task of getting ready to communicate with the Graph. For instance, because many Graph API calls use pagination to return a limited set of data per call, I have a function to continue fetching data until no more is available.
Counting Distribution Lists
The big challenge when counting the number of members in a distribution list is how to deal with nested groups. The PowerShell version uses a function to expand the membership of any nested groups to come up with a full set of members, just like the way the Exchange Online transport service expands a distribution list to create copies of messages during its fan-out process.
The Graph API for Groups includes the transitiveMembers call to return the members of a group (including distribution lists). This means that we can replace the PowerShell function with a single graph call. In this code, we ask the Graph to return the membership for a distribution list identified by its Azure AD object identifier (stored in the list’s ExternalDirectoryObjectId property).
$Uri = "https://graph.microsoft.com/v1.0/groups/" + $DL.ExternalDirectoryObjectId + "/transitiveMembers" [array]$Members = Get-GraphData -AccessToken $Token -Uri $uri
Get-GraphData is the function to handle pagination referred to above.
After a successful call, the $Members array contains the full set of members. To analyze the membership and create a count of the different types of objects (mailboxes, groups, contacts, etc.), it’s a matter of looping through the array. The values for member type returned by the Graph take a little interpretation, but that’s not hard. Here’s the relevant code:
ForEach ($Member in $Members) { Switch ($Member."@odata.type") { "#microsoft.graph.orgContact" { # Mail contact $MemberDisplayName = $Member.DisplayName $CountContacts++ } "#microsoft.graph.user" { # Tenant user (including guests $MemberDisplayName = $Member.DisplayName If ($Member.UserPrincipalName -Like "*#EXT#*") { $CountGuests++ } Else { $CountTenantMembers++ } } "#microsoft.graph.group" { #Another group $MemberDisplayName = $Member.DisplayName $CountGroups++ } } #End Switch # Update member table $MemberData = [PSCustomObject][Ordered]@{ MemberName = $MemberDisplayName MemberId = $Member.Id } $MembersNames.Add($MemberData)
And that’s about it. A single Graph call replaces the function, and some slightly different processing counts the member types.
Simple Once You Know How
As it turns out, the task of converting the script was straightforward. Admittingly, I make the assertion from the view of someone who has worked with the Graph API for a while, can set up a registered app without breaking sweat, and have some code to paste into a new script to do the housekeeping. Still, I hold to my point. Like anything else to do with Office 365, it takes some time to become accustomed to how best to get things done. In five years, we’ll look back at all the Graph-enabled PowerShell we’ll write between now and then and wonder why anyone was concerned. At least, I hope so.