Fetching Group Membership Information for an Azure AD Account

Discover Group Membership with the Graph SDK

Group membership with the Graph SDK

I’ve updated some scripts recently to remove dependencies on the Azure AD and Microsoft Online Services (MSOL) modules, which are due for deprecation on June 30, 2023 (retirement happens at the end of March for the license management cmdlets). In most cases, the natural replacement is cmdlets from the Microsoft Graph PowerShell SDK.

One example is when retrieving the groups an Azure AD user account belongs to. This is an easy task when dealing with the membership of individual groups using cmdlets like:

  • Get-DistributionGroupMember (fetch distribution list members).
  • Get-DynamicDistributionGroupMember (fetch dynamic distribution group members).
  • Get-UnifiedGroupLinks (fetch members of a Microsoft 365 group).
  • Get-MgGroupMember (fetch members of an Azure AD group).

Things are a little more complex when answering a question like “find all the groups that Sean Landy belongs to.” Let’s see how we can answer the request.

The Exchange Online Approach

One method of attacking the problem often found in Exchange scripts is to use the Get-Recipient cmdlet with a filter based on the distinguished name of the mailbox belonging to an account: For example, this code reports a user’s membership of Microsoft 365 groups:

$User = Get-EXOMailbox -Identity Sean.Landy
$DN = $User.DistinguishedName
$Groups = (Get-Recipient -ResultSize Unlimited -RecipientTypeDetails GroupMailbox -Filter "Members -eq '$DN'" )
Write-Host (“User is a member of {0} groups” -f $Groups.count)

The method works if the distinguished name doesn’t include special characters like apostrophes for users with names like Linda O’Shea. In these cases, extra escaping is required to make PowerShell handle the name correctly. This problem will reduce when Microsoft switches the naming mechanism for Exchange Online objects to be based on the object identifier instead of mailbox display name. However, there’s still many objects out there with distinguished names based on display names.

The Graph API Request

As I go through scripts, I check if I can remove cmdlets from other modules to make future maintenance easier. Using Get-Recipient means that a script must connect to the Exchange Online management module, so let’s remove that need by using a Graph API request. Here’s what we can do, using the Invoke-MgGraphRequest cmdlet to run the request:

$UserId = $User.ExternalDirectoryObjectId
$Uri = ("https://graph.microsoft.com/V1.0/users/{0}/memberOf/microsoft.graph.group?`$filter=groupTypes/any(a:a eq 'unified')&`$top=200&$`orderby=displayName&`$count=true" -f $UserId)
[array]$Data = Invoke-MgGraphRequest -Uri $Uri
[array]$Groups = $Data.Value
Write-Host (“User is a member of {0} groups” -f $Groups.count) 

We get the same result (always good) and the Graph request runs about twice as fast as Get-Recipient does.

Because the call is limited to Microsoft 365 groups, I don’t have to worry about transitive membership. If I did, then I’d use the group transitive memberOf API.

Using the SDK Get-MgUserMemberOf Cmdlet

The Microsoft Graph PowerShell SDK contains cmdlets based on Graph requests. The equivalent cmdlet is Get-MgUserMemberOf. This returns memberships of all group types known to Azure AD, so it includes distribution lists and security groups. To return the set of Microsoft 365 groups, apply a filter after retrieving the group information from the Graph.

[array]$Groups = Get-MgUserMemberOf -UserId $UserId -All | Where-Object {$_.AdditionalProperties["groupTypes"] -eq "Unified"}
Write-Host (“User is a member of {0} groups” -f $Groups.count) 

Notice that the filter looks for a specific type of group in a value in the AdditionalProperties property of each group. If you run Get-MgUserMemberOf without any other processing. the cmdlet appears to return a simple list of group identifiers. For example:

$Groups

Id                                   DeletedDateTime
--                                   ---------------
b62b4985-bcc3-42a6-98b6-8205279a0383
64d314bb-ea0c-46de-9044-ae8a61612a6a
87b6079d-ddd4-496f-bff6-28c8d02e9f8e
82ae842d-61a6-4776-b60d-e131e2d5749c

However, the AdditionalProperties property is also available for each group. This property contains a hash table holding other group properties that can be interrogated. For instance, here’s how to find out whether the group supports private or public access:

$Groups[0].AdditionalProperties['visibility']
Private

When looking up a property in the hash table, remember to use the exact form of the key. For instance, this works to find the display name of a group:

$Groups[0].AdditionalProperties['displayName']

But this doesn’t because the uppercase D creates a value not found in the hash table:

$Groups[0].AdditionalProperties['DisplayName']

People starting with the Microsoft Graph PowerShell SDK are often confused when they see just the group identifiers apparently returned by cmdlets like Get-MgUserMemberOf, Get-MgGroup, and Get-MgGroupMember because they don’t see or grasp the importance of the AdditionalProperties property. It literally contains the additional properties for the group excepting the group identifier.

Here’s another example of using information from AdditionalProperties. The details provided for a group don’t include its owners. To fetch the owner information for a group, run the Get-MgGroupOwner cmdlet like this:

$Group = $Groups[15]
[array]$Owners = Get-MgGroupOwner -GroupId $Group.Id | Select-Object -ExpandProperty AdditionalProperties
$OwnersOutput = $Owners.displayName -join ", "
Write-Host (“The owners of the {0} group are {1}” -f $Group.AdditionalProperties[‘displayName’], $OwnersOutput)

If necessary, use the Get-MgGroupTransitiveMember cmdlet to fetch transitive memberships of groups.

The Graph SDK Should be More Intelligent

It would be nice if the Microsoft Graph PowerShell SDK didn’t hide so much valuable information in AdditionalProperties and wasn’t quite so picky about the exact format of property names. Apparently, the SDK cmdlets behave in this manner because it’s how Graph API requests work when they return sets of objects. That assertion might well be true, but it would be nice if the SDK applied some extra intelligence in the way it handles data.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.

2 Replies to “Fetching Group Membership Information for an Azure AD Account”

  1. Great article as always, thank you! However, please correct me if I’m wrong, but the “server-side filter” example in the Get-MgUserMemberOf cmdlet is actually a client-side filter since you are filtering after the pipe (you receive all the data from Graph API and then filter the results locally). A server-side filter would be using the -Search or -Filter parameters of the Get-MgUserMemberOf cmdlet for example. Isn’t that true?

    1. There’s actually a combination of both server and client side filtering at work here. By definition, the Graph requests are server-side filers. A separate client-side filter is used to check what the server-side filter returned. The text is a tad clumsy, so I revised it.

Leave a Reply

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