Getting members of Dynamic Groups in Office 365

Reporting on which user is member of which groups is a common task in Office 365, and there are many sample articles and scripts that detail how to achieve it. The “opposite” scenario can also be interesting in some cases, as in listing all the members of particular group. This task is straightforward for regular groups with “flat” membership, and a bit more challenging when reporting on groups with “nested” membership, but still easily doable via PowerShell.

When it comes to groups with dynamic membership, things are also not that complicated, but there are some caveats you might want to consider. First of all, it is important to understand just what type of group are we talking about.

Dynamic Distribution Groups in Exchange Online

Dynamic Distribution Groups (or DDGs for short) have been around for ages in Exchange and Exchange Online, and are relatively easy to manage and report on. For example, if you want to list all members of an Dynamic DG in Exchange, you can use the following PowerShell cmdlet:

Get-Recipient -RecipientPreviewFilter (Get-DynamicDistributionGroup DDG).RecipientFilter

This simple example will produce a list of all members of the DDG dynamic distribution group object, along with their properties, allowing you to generate a proper recipient report with just some minor modifications. One such modification would be to include Office 365 Groups in the output, as by default those are omitted from any results returned by the Get-Recipient cmdlet. An updated version of the above cmdlet will then look like this:

Get-Recipient -RecipientPreviewFilter (Get-DynamicDistributionGroup DDG).RecipientFilter -RecipientTypeDetails UserMailbox,SharedMailbox,RoomMailbox,EquipmentMailbox,TeamMailbox, DiscoveryMailbox,MailUser,MailContact,DynamicDistributionGroup,MailUniversalDistributionGroup,MailUniversalSecurityGroup,RoomList,GuestMailUser,PublicFolder,GroupMailbox | select DisplayName,PrimarySmtpAddress,RecipientTypeDetails

where we have also selected some more appropriate properties to be displayed. Of course, you can adjust the recipient types and properties as needed in order to generate just the type of report you are looking for.

Now, if you want to generate a report that includes members of all Dynamic distribution groups in Office 365, you can use something like this:

$DDGs = Get-DynamicDistributionGroup

foreach ($ddg in $DDGs) {Get-Recipient -RecipientPreviewFilter (Get-DynamicDistributionGroup $ddg.PrimarySmtpAddress).RecipientFilter -RecipientTypeDetails UserMailbox,SharedMailbox,RoomMailbox,EquipmentMailbox,TeamMailbox, DiscoveryMailbox,MailUser, MailContact, DynamicDistributionGroup, MailUniversalDistributionGroup,MailUniversalSecurityGroup,RoomList,GuestMailUser,PublicFolder,GroupMailbox | Add-Member -MemberType NoteProperty -Name DDG -Value $ddg.PrimarySmtpAddress -PassThru | select DDG,DisplayName, PrimarySmtpAddress,RecipientTypeDetails }

The resulting report will have the Primary SMTP Address of the dynamic distribution groups as the first column (you can replace it with any other property of the group of course), then the properties of each individual recipient which is a member of the DDG. If you are interested in the count of recipients per DDG, you can use the Group-Object cmdlet group the output per each DDG or simply export it to CSV using the Export-CSV cmdlet and modify it in Excel.

Azure AD groups with dynamic membership

In the world of Office 365, another type of Dynamic groups exists. The idea is the same – groups which membership is managed dynamically, based on some predefined rule. Unlike Exchange DDGs however, which are only used for email delivery, Dynamic groups in Office 365 can have other purposes. Two types of Dynamic groups are supported: Azure AD Security groups and Office 365 (“modern”) Groups. For the former, the membership can be based on user or device attributes, while for Dynamic Office 365 Groups only user-based queries are supported.

Reporting on membership for Dynamic Office 365 groups is unfortunately somewhat limited, compared to reporting on Exchange DDGs. The reason is the limited filtering capabilities of the underlying Graph API as well as the poor support for PowerShell. In fact, while you can list dynamic using “traditional” cmdlets such as Get-MsolGroup, nothing in the properties of the returned object will indicate they are indeed of type Dynamic. Moreover, to actually see the underlying membership rule, the only available option we have currently is to use the Preview version of the Azure AD PowerShell module, which you can download here.

Before listing the members of individual dynamic group, one might also be interested in how you can list all such groups in the tenant. If using the Azure AD PowerShell module, the following cmdlet will return just dynamic groups:

Get-AzureADMSGroup -Filter "groupTypes/any(c:c eq 'DynamicMembership')"

If using a recent version of the Azure AD module, the above cmdlet will also work, however as mentioned above the membership rule will not be returned. If using the old MSOnline module, there is no way to report on just groups with dynamic membership as no server- or client-side filters exist to distinguish them from “regular” groups.

Now that we know how to get a list of all the dynamic groups, what about reporting on their membership? Here’s where the troubles start, as the syntax used by the membership rule differs from the syntax used for the filtering operations in the Graph API. For example, if you have created a group that automatically includes all enabled users, it might look something like this:

DisplayName : Enabled Users
MailEnabled : False
SecurityEnabled : True
GroupTypes : {DynamicMembership}
MembershipRule : (user.accountEnabled -eq true)
MembershipRuleProcessingState : On

While you can easily get the membership rule via the corresponding property (again, only available if you use the Azure AD Preview module), reusing the filter against the Get-AzureADUser cmdlet is not a simple copy/paste operation. Trying to reuse the membership rule as filter directly will fail:

To get a proper filter syntax, change the “-eq” operator to “eq” and also remove the “user” prefix from the accountEnabled property. Some other adjustments will most likely be required, depending on the type of filter you have configured, and things can easily get out of control with more complicated filters. On top of this, the syntax accepted by the membership rule object seems to include filters not yet supported by the Graph API for any Azure AD objects. A more detailed article detailing those limitations can be found here. In a nutshell, you can only use very basic filters, with operators such as “eq”, “ge” and “le”. Even basic constructs such as the “ne” operator and the logical “not” operator are NOT supported.

To put it another way, there are only a handful of filters that can be “translated” for use with the Get-AzureADUser cmdlet, and even for those you can still expect some issues. Another thing to have in mind is the special type of filters, such as “All users” or “direct reports”. Those also require special treatment. The following PowerShell code attempts to work around some of the limitations/special cases and obtain the list of users for at least some of the management rules (don’t expect wonders):

$DynamicGroups = Get-AzureADMSGroup -Filter "groupTypes/any(c:c eq 'DynamicMembership')"

$allusers = @()
foreach ($group in $DynamicGroups) {
$filter = $group.MembershipRule
if ($filter -eq "All Users") { $users = Get-AzureADUser -All:$true | Add-Member -MemberType NoteProperty -Name GroupName -Value $group.DisplayName -PassThru | select DisplayName, UserPrincipalName,GroupName }
elseif ($filter -match "assignedPlans|assignedLicenses|provisionedPlans|\bne\b|\bnot\b|\bgt\b|\blt\b|contains|notcontains|match|\bin\b|\bnotin\b|null") { Write-Host "Unsuported filter syntax for group $($group.DisplayName), skipping..." -ForegroundColor Yellow; continue }
elseif ($filter -match "device") { Write-Host "The script only covers User filters, skipping group $($group.DisplayName) as it contains device filter..." -ForegroundColor Yellow; continue }
else {
$users = Get-AzureADUser -All:$true -Filter (FixFilter $filter) | Add-Member -MemberType NoteProperty -Name GroupName -Value $group.DisplayName -PassThru | select DisplayName, UserPrincipalName,GroupName

$allusers += $users

function FixFilter ($filter) {
$filter = $filter -replace '(-)(and|or|eq|ge|le)','$2' #trim dashes
$filter = $filter -replace '(-)(any|all)','/$2' #fix -any/-all
$filter = $filter -replace '^(?<p>.*)-startsWith(?<a>.*)$','startswith($1,$2)' #replace startswith formatting
$filter = $filter.Replace("user.","").Replace("`"","'") #other fixes
return $filter

Another frustration is the absence Get-AzureADObject cmdlet or any analogue of Get-Recipient, which means that you will have to run the query against Get-AzureADUser and Get-AzureADDevice individually, if you want to cover all object types. To simplify the example, device-based rules are excluded from the code above, but feel free to amend it to account for device objects too. Group objects are not supported as members of dynamic groups, thus you can ignore them.

This entry was posted in Azure AD, Microsoft 365, Office 365, PowerShell. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

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