Entra / Microsoft 365 · Groups
Sync M365 group with security group
Synchronizes membership between a Microsoft 365 group and a security group, adding and removing members to match.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes GroupMember.ReadWrite.All, Group.ReadWrite.All, User.ReadBasic.All -NoWelcome
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
[CmdletBinding()]param([Parameter(Mandatory)][string] $M365GroupName,[Parameter(Mandatory)][string] $SecurityGroupName)function Connect-GraphSession {$requiredScopes = @("GroupMember.ReadWrite.All","Group.ReadWrite.All", "User.ReadBasic.All")if (-not (Get-MgContext)) {Write-Host "Connecting to Microsoft Graph and requesting delegated scopes..." -ForegroundColor CyanConnect-MgGraph -Scopes $requiredScopes -NoWelcomereturn}# Ensure we have all scopes[array]$Scopes = Get-MgContext | Select-Object -ExpandProperty Scopes$MissingScopes = $RequiredScopes | Where-Object { $_ -notin $Scopes }if ($MissingScopes) {Write-Host "Disconnecting from Graph to allow the addition of missing scopes: $($MissingScopes -join ', ')" -ForegroundColor YellowDisconnect-MgGraphBreak}}# --- Main ---Connect-GraphSession[int]$Removals = 0[int]$Additions = 0# Get the Microsoft 365 Group and check that it is a unified group with static membership[array]$M365Group = Get-MgGroup -Filter "displayName eq '$M365GroupName'" -ConsistencyLevel eventual -CountVariable CountIf (!($M365Group)){Write-Output "Cannot find a unique Microsoft 365 Group named '$M365GroupName'" -ForegroundColor RedBreak}If ("Unified" -notin $M365Group.GroupTypes){Write-Output "'$M365GroupName' is not a Microsoft 365 Group." -ForegroundColor RedBreak}If ("DynamicMembership" -in $M365Group.GroupTypes){Write-Output "'$M365GroupName' is a dynamic Microsoft 365 Group and cannot be synchronized." -ForegroundColor RedBreak}# Get the Security Group and check that it is security and mail-enabled[array]$SecurityGroup = Get-MgGroup -Filter "displayName eq '$SecurityGroupName' and securityEnabled eq true" -ConsistencyLevel eventual -CountVariable CountIf (!($SecurityGroup)){Write-Output "Cannot find a unique Security Group named '$SecurityGroupName'" -ForegroundColor RedBreak}If ($SecurityGroup.SecurityEnabled -ne $true) {Write-Output "'$SecurityGroupName' is not a Security Group." -ForegroundColor RedBreak}Write-Output "Synchronizing membership of M365 Group '$M365GroupName' with Security Group '$SecurityGroupName'"# Get the members of both groups[array]$M365GroupMembers = Get-MgGroupMember -GroupId $M365Group.Id -All | Select-Object -ExpandProperty Id[array]$SecurityGroupMembers = Get-MgGroupMember -GroupId $SecurityGroup.Id -All | Select-Object -ExpandProperty IdWrite-Output "M365 Group has $($M365GroupMembers.Count) members and Security Group has $($SecurityGroupMembers.Count) members"Write-Output "Comparing memberships..."$Report = [System.Collections.Generic.List[Object]]::new()# The security group is the master, so we must add security group members to the M365 group if they're not already present[array]$NotInM365Group = $SecurityGroupMembers | Where-Object { $_ -notin $M365GroupMembers }ForEach ($Member in $NotInM365Group) {Write-Output "Adding member $Member to $M365GroupName"Try {New-MgGroupMember -GroupId $M365Group.Id -DirectoryObjectId $Member -ErrorAction Stop$Additions++$ReportLine = [PSCustomObject][Ordered]@{UserId = $MemberName = Get-MgUser -UserId $Member -Select displayName | Select-Object -ExpandProperty DisplayNameAction = "Added"'Microsoft 365 Group' = $M365GroupNameTimestamp = (Get-Date).ToString("u")}$Report.Add($ReportLine)} Catch {Write-Output "Failed to add member $Member to $M365GroupName. Error: $_" -ForegroundColor Red}}# And remove members from the M365 group who are not in the security group[array]$NotInSecurityGroup = $M365GroupMembers | Where-Object { $_ -notin $SecurityGroupMembers }ForEach ($Member in $NotInSecurityGroup) {Write-Output "Removing member $Member from $M365GroupName"Try {Remove-MgGroupMemberByRef -GroupId $M365Group.Id -DirectoryObjectId $Member -ErrorAction Stop$Removals++$ReportLine = [PSCustomObject][Ordered]@{UserId = $MemberName = Get-MgUser -UserId $Member -Select displayName | Select-Object -ExpandProperty DisplayNameAction = "Removed"'Microsoft 365 Group' = $M365GroupNameTimestamp = (Get-Date).ToString("u")}$Report.Add($ReportLine)} Catch {Write-Output "Failed to remove member $Member from $M365GroupName. Error: $_" -ForegroundColor Red}}Write-Host "Synchronization complete. Added $Additions members and removed $Removals members." -ForegroundColor Green$Report
Parameters
ParameterDefaultNotes
-M365GroupName""Display name of the Microsoft 365 group to synchronize.-SecurityGroupName""Display name of the security group whose membership should match the M365 group.Attribution
Author
Office365itpros