Entra / Microsoft 365 · Groups
Copy groups from one user to another
Copies all group memberships from one Microsoft 365 user to another.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "GroupMember.ReadWrite.All" -NoWelcome# Connect to Exchange Online if necessary
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
function Remove-UserFromM365Group {# Remove a user account from a Microsoft 365 group, checking if the user is an owner first. If the user is an owner and they are not the last owner,# the function removes the user from as both an owner and a member of the group.param ([Parameter(Mandatory = $true)][string]$UserId,[Parameter(Mandatory = $true)][string]$GroupId,[Parameter(Mandatory = $true)][string]$GroupName)$Outcome = $null[array]$Owners = Get-MgGroupOwner -GroupId $GroupId | Select-Object -ExpandProperty Idif ($UserId -in $Owners) {if ($Owners.count -eq 1) {Write-Host ("The {0} group has only one owner - can't remove the last owner" -f $GroupDisplayName) -Foregroundcolor Red$Outcome = "Failed - can't remove the last owner of a group"return $Outcome}Write-Host ("User {0} is an owner of group {1} - removing both owner and member links" -f $TargetDisplayName, $GroupDisplayName) -ForegroundColor Yellowtry {Remove-MgGroupOwnerByRef -DirectoryObjectId $UserId -GroupId $GroupId} catch {Write-Host ("Failed to remove user {0} as owner from group {1}: {2}" -f $TargetDisplayName, $GroupDisplayName, $_.Exception.Message) -ForegroundColor Red$Outcome = "Failed to remove owner"return $Outcome}}try {Remove-MgGroupMemberByRef -DirectoryObjectId $UserId -GroupId $GroupId} catch {Write-Host ("Failed to remove user {0} as member from group {1}: {2}" -f $TargetDisplayName, $GroupDisplayName, $_.Exception.Message) -ForegroundColor Red$Outcome = "Failed to remove member"return $Outcome}$Outcome = ("User {0} removed from group {1}" -f $SourceDisplayName, $GroupDIsplayName)return $Outcome}function Remove-UserFromDistributionList {param ([Parameter(Mandatory = $true)][string]$UserId,[Parameter(Mandatory = $true)][string]$GroupId,[Parameter(Mandatory = $true)][string]$GroupName)try {Remove-DistributionGroupMember -Identity $GroupId -Member $UserId -Confirm:$false -ErrorAction StopWrite-Host ("User {0} removed from distribution list {1}" -f $TargetDisplayName, $GroupDisplayName) -ForegroundColor Yellowreturn} catch {Write-Host ("Failed to remove mailbox {0} from distribution list {1}: {2}" -f $TargetDisplayName, $GroupDisplayName, $_.Exception.Message) -ForegroundColor Redreturn $_.Exception.Message}}# Connect to Microsoft GraphConnect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "GroupMember.ReadWrite.All" -NoWelcome# Connect to Exchange Online if necessary[array]$Modules = Get-Module | Select-Object -ExpandProperty NameIf ($Modules -notcontains "ExchangeOnlineManagement") {Write-Host "Connecting to Exchange Online" -ForegroundColor CyanConnect-ExchangeOnline -ShowBanner:$false -ErrorAction Stop}Write-Host "Making sure that the source and target users exist" -ForegroundColor Cyan# Check if the source user existsTry {$SourceUser = Get-MgUser -UserId $Source -ErrorAction Stop -Property DisplayName, UserPrincipalName, Id$Global:SourceDisplayName = $SourceUser.DisplayName} Catch {Write-Host "Source user not found. Please check the User Principal Name." -ForegroundColor RedBreak}# Check if the target user existsTry {$TargetUser = Get-MgUser -UserId $Target -ErrorAction Stop -Property DisplayName, UserPrincipalName, Id$Global:TargetDisplayName = $TargetUser.DisplayName} catch {Write-Host "Target user not found. Please check the User Principal Name." -ForegroundColor RedBreak}Write-Host ("Checking groups for user {0} to copy to {1}" -f $SourceUser.DisplayName, $TargetUser.DisplayName)Write-Host "Note: Groups with dynamic membership are not copied." -ForegroundColor Yellow[array]$SourceGroups = Get-MgUserMemberOf -UserId $SourceUser.Id -All -PageSize 500 | `Where-Object {($_.additionalProperties.'@odata.type' -eq '#microsoft.graph.group') -and(-not ($_.additionalProperties.groupTypes -contains "DynamicMembership"))} | Select-Object -ExpandProperty IdIf ($null -eq $SourceGroups) {Write-Host "No groups found for user $($SourceUser.DisplayName)." -ForegroundColor YellowBreak}# Check what groups the target user is already a member of[array]$CurrentTargetGroups = Get-MgUserMemberOf -UserId $TargetUser.Id -All -PageSize 500 | `Where-Object {$_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.group'} | `Select-Object -ExpandProperty Id$GroupsToProcess = [System.Collections.Generic.List[Object]]::new()ForEach ($GroupId in $SourceGroups) {If ($GroupId -notin $CurrentTargetGroups) {$GroupsToProcess.Add($GroupId)}}# If there are no groups to copy fromt the source user to the target user, exit the scriptIf ($GroupsToProcess.Count -eq 0) {Write-Host "No new groups to copy from $($SourceUser.DisplayName) to $($TargetUser.DisplayName)." -ForegroundColor YellowBreak}Write-Host ("Found {0} groups to copy from {1} to {2}" -f $GroupsToProcess.Count, $SourceUser.DisplayName, $TargetUser.DisplayName)Write-Host "Starting to copy groups..." -ForegroundColor Yellow$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($GroupId in $GroupsToProcess) {Try {$Group = Get-MgGroup -GroupId $GroupId -ErrorAction Stop -Property DisplayName, Id, GroupTypes, MailEnabled, SecurityEnabled$GroupDisplayName = $Group.DisplayName} Catch {Write-Host ("Failed to retrieve group {0}: {1}" -f $GroupId, $_.Exception.Message) -ForegroundColor Red$ReportLine = [PSCustomObject]@{GroupId = $GroupIdStatus = "Failed to fetch group details"Error = $_.Exception.Message}$Report.Add($ReportLine)Continue}# If you don't wnat to process Viva Engage communities, uncomment these lines#If ($Group.additionalProperties.creationOptions -contains 'YammerProvisioning') {# Write-Host ("Skipping Viva Engage group {0}" -f $Group.DisplayName) -ForegroundColor Yellow# $ReportLine = [PSCustomObject]@{# GroupId = $GroupId# Name = $Group.DisplayName# Type = "Viva Engage Community"# Status = "Skipped Viva Engage community"# Error = $null# }# $Report.Add($ReportLine)# Continue#}If ($Group.groupTypes -contains "Unified") { # Microsoft 365 groupWrite-Host ("Adding {0} to Microsoft 365 group {1}" -f $TargetUser.displayName, $GroupDisplayName) -ForegroundColor CyanTry {New-MgGroupMember -GroupId $Group.Id -DirectoryObjectId $TargetUser.Id -ErrorAction Stop$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $Group.DisplayNameType = "Microsoft 365 Group"Status = "Success"Error = $null}$Report.Add($ReportLine)If ($RemoveOriginalUser) {$Outcome = Remove-UserFromM365Group -UserId $SourceUser.Id -GroupId $Group.Id -GroupName $GroupDisplayNameWrite-Host $Outcome -ForegroundColor Yellow}} Catch {Write-Host ("Failed to add {0} to Microsoft 365 group {1}: {2}" -f $TargetUser.DisplayName, $GroupDisplayName, $_.Exception.Message) -ForegroundColor Red$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $GroupDisplayNameType = "Microsoft 365 Group"Status = "Failed"Error = $_.Exception.Message}$Report.Add($ReportLine)Continue}}If ($Group.SecurityEnabled -and $null -eq $Group.MailEnabled) { # Security groupWrite-Host ("Adding {0}} to securty group {1}" -f $TargetUser.DisplayName, $Group.DisplayName) -ForegroundColor CyanTry {New-MgGroupMember -GroupId $Group.Id -DirectoryObjectId $TargetUser.Id -ErrorAction Stop$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $GroupDisplayNameType = "Security Group"Status = "Success"Error = $null}$Report.Add($ReportLine)If ($RemoveOriginalUser) {$Outcome = Remove-UserFromM365Group -UserId $SourceUser.Id -GroupId $Group.Id -GroupName $GroupDisplayNameWrite-Host $Outcome -ForegroundColor Yellow}} Catch {Write-Host ("Failed to add {0} to security group {1}: {2}" -f $TargetUser.DisplayName, $GroupDisplayName, $_.Exception.Message) -ForegroundColor Red$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $Group.DisplayNameType = "Security Group"Status = "Failed"Error = $_.Exception.Message}$Report.Add($ReportLine)Continue}}If ($Group.SecurityEnabled -eq $false -and $Group.MailEnabled -eq $true -and -not($Group.groupTypes -contains "Unified")) { # distribution listWrite-Host ("Adding {0} to distribution list {1}" -f $TargetUser.DisplayName, $GroupDisplayName) -ForegroundColor CyanTry {Add-DistributionGroupMember -Identity $Group.Id -Member $TargetUser.Id -ErrorAction Stop$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $GroupDisplayNameType = "Distribution List"Status = "Success"Error = $null}$Report.Add($ReportLine)If ($RemoveOriginalUser) {$Outcome = Remove-UserFromDistributionList -UserId $SourceUser.Id -GroupId $Group.Id -GroupName $GroupDisplayName}} Catch {Write-Host ("Failed to add {0} to distribution list {1}: {2}" -f $TargetUser.DisplayName, $Group.DisplayName, $_.Exception.Message) -ForegroundColor Red$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $Group.DisplayNameType = "Distribution List"Status = "Failed"Error = $_.Exception.Message}$Report.Add($ReportLine)Continue}}If ($Group.SecurityEnabled -eq $true -and $Group.MailEnabled -eq $true -and -not($Group.groupTypes -contains "Unified")) { # mail-enabled security groupWrite-Host ("Adding {0} to mail-enabled security group {1}" -f $TargetUser.DisplayName, $Group.DisplayName) -ForegroundColor CyanTry {Add-DistributionGroupMember -Identity $Group.Id -Member $TargetUser.Id -ErrorAction Stop$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $Group.DisplayNameType = "Mail-enabled Security Group"Status = "Success"Error = $null}$Report.Add($ReportLine)If ($RemoveOriginalUser) {$Outcome = Remove-UserFromDistributionList -UserId $SourceUser.Id -GroupId $Group.Id -GroupName $GroupDisplayName}} Catch {Write-Host ("Failed to add {0} to mail-enabled security group {1}: {2}" -f $TargetUser.DisplayName, $Group.DisplayName, $_.Exception.Message) -ForegroundColor Red$ReportLine = [PSCustomObject]@{GroupId = $Group.IdName = $Group.DisplayNameType = "Mail-enabled Security Group"Status = "Failed"Error = $_.Exception.Message}$Report.Add($ReportLine)Continue}}}Write-Host ("Finished copying groups from {0} to {1}." -f $SourceUser.DisplayName, $TargetUser.DisplayName) -ForegroundColor GreenWrite-Host ("Membership was added to a total of {0} groups." -f $Report.Count) -ForegroundColor Green$Report | Group-Object -Property Type | ForEach-Object {$Type = $_.Name$Count = $_.CountWrite-Host ("{0} groups of type {1} were processed." -f $Count, $Type) -ForegroundColor Cyan}$Report | Out-GridView -Title ("Group memberships added for {0}" -f $TargetUser.DisplayName)
Attribution
Author
Office365itpros