Synchronizing Security and Microsoft 365 Group Memberships

Use the Graph APIs to Synchronize Group Memberships

A September 2018 article describes how to synchronize the membership of security groups with Microsoft 365 groups (or Office 365 groups as they were then). Looking at the example code in the article, it’s obvious that much has changed. The demise of the Azure AD PowerShell module and its replacement by the Entra module or the Microsoft Graph PowerShell SDK is probably the biggest change.

A security group is used to manage access to shared resources. It’s often desirable to have a way for members of the security group to share information and collaborate. A Microsoft 365 group is the obvious choice, and that’s what creates the need to synchronize membership between the security group and Microsoft 365 group. The security group is the master, so the processing is to add and remove members of the Microsoft 365 group based on the membership of the security group.

I haven’t looked at the article for a long time and was surprised when a reader asked for an updated solution based on Microsoft Graph APIs. The reader supplied some code that they’d written and hadn’t managed to get working, probably because of the somewhat odd way that the Graph APIs return group membership. It’s just one of the foibles developers should understand when they work with the Graph.

Dealing with Group Membership

The documentation for the List Group Members API shows that member identifiers are returned rather than what you might expect, such as a display name or user principal name. This behavior follows through into the Microsoft Graph PowerShell SDK Get-MgGroupMember cmdlet:

Get-MgGroupMember -GroupId $M365Group.Id

Id                                   DeletedDateTime
--                                   ---------------
eff4cd58-1bb8-4899-94de-795f656b4a18
53f08764-07d4-418c-8403-a737a8fac7b3
6fd89e40-665a-4efa-9691-da07849cae91
…

Dealing with a set of object identifiers isn’t a problem when you expect to do so. However, if you fetch the additionalProperties property, PowerShell returns a hash table containing the expanded details of the group members.

Get-MgGroupMember -GroupId $M365Group.Id | Select-Object -ExpandProperty additionalProperties

Key               Value
---               -----
@odata.type       #microsoft.graph.user
businessPhones    {}
displayName       René Artois
givenName         René
jobTitle          Chief
mail              Rene.Artois@office365itpros.com
officeLocation    Nouvon
surname           Artois
userPrincipalName Rene.Artois@office365itpros.com
@odata.type       #microsoft.graph.user
businessPhones    {}
displayName       Otto Flick
givenName         Otto
jobTitle          Head of Department
mail              Otto.Flick@office365itpros.com
officeLocation    Nouvion
surname           Flick
userPrincipalName Otto.Flick@office365itpros.com
@odata.type       #microsoft.graph.user
businessPhones    {+33 4 94301766}
displayName       Lotte Vetler (Paris)
givenName         Lotte
jobTitle          Property Consultant
mail              Lotte.Vetler@office365itpros.com
mobilePhone       +33 6 7446 1554
officeLocation    Flayosc
surname           Vetler
userPrincipalName Lotte.Vetler@office365itpros.com

Note that the table doesn’t include the identifier for each user. However, if you create a hash table from additionalProperties, code can use it as a lookup table to extract details for a user identifier. Here’s an example of using the hash table to find the display name for a group member:

[array]$Members = Get-MgGroupMember -GroupId $M365Group.Id
$GroupMembers = $Members | Select-Object -ExpandProperty additionalProperties
[int]$i = 0
ForEach ($M in $Members.Id) {
   Write-output ("Processing member {0}" -f $GroupMembers[$i].displayName)
   $i++
}

Processing member René Artois
Processing member Otto Flick
Processing member Lotte Vetler (Paris)

Coding a Solution

Once we’re clear about how Graph APIs return group membership, it’s easy to proceed to code and create a replacement script. Unlike the original, which used both the Azure AD and Exchange Online modules, I chose to do everything with the Microsoft Graph PowerShell SDK to avoid the potential for irritating assembly clashes. The code is enhanced a tad with additional checks to make sure that the right kind of groups are involved and to record the additions and removals for the membership of the Microsoft 365 group (Figure 1).

Synchronizing group membership.
Figure 1: Synchronizing group membership

You can download the full script from the Office 365 for IT Pros GitHub repository.

Synchronizing Group Membership Automatically

The technique used in the script is not limited to synchronizing between a security group and a Microsoft 365 group. It could also be used to synchronize the membership of two Microsoft 365 groups or two security groups.

Processes like this are well suited to execution as a scheduled Azure Automation runbook. Running the synchronization job weekly seems like the correct cadence, but I’ll leave it to you to decide.


Need help to write and manage PowerShell scripts for Microsoft 365, including Azure Automation runbooks? Get a copy of the Automating Microsoft 365 with PowerShell eBook, available standalone or as part of the Office 365 for IT Pros eBook bundle.

2 Replies to “Synchronizing Security and Microsoft 365 Group Memberships”

  1. Great article! We can also on a dynamic group, use the user.memberof on the rule syntax and refer to the security group.

    1. You use a dynamic security group? That would work nicely… The restriction I have in the script is to block use of a dynamic Microsoft 365 group (just to make things simpler). This could be worked around if you wanted to use two dynnamic groups by making sure that both use the same membership rule. In which case, you wouldn’t need the script at all! (but you would need Entra P1 licenses for all of the members in both groups).

Leave a Reply

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