How to Replace Group Owners When They Leave the Organization
Replace Group Owners to Avoid Ownerless Groups
An ownerless group is not a thing of beauty, but it can happen when someone leaves an organization and the remobal of their Entra ID user account results in some groups becoming ownerless. The Microsoft 365 group ownership policy helps, but it’s better to avoid the problem in the first place by proactively replacing the to-be-deleted account with a new group owner.
Writing PowerShell to Replace Group Owners
Which brings me to a PowerShell snippet posted in LinkedIn to address the problem. Here’s the code:
$OldOwnerUPN = "user@domain.com" $NewOwnerUPN = "user1@domain.com" $Groups = Get-UnifiedGroup -ResultSize Unlimited foreach ($Group in $Groups) { $Owners = Get-UnifiedGroupLinks -Identity $Group.Identity -LinkType Owners if ($Owners.PrimarySmtpAddress -contains $OldOwnerUPN) { Remove-UnifiedGroupLinks -Identity $Group.Identity -LinkType Owners -Links $OldOwnerUPN -Confirm:$false Add-UnifiedGroupLinks -Identity $Group.Identity -LinkType Members -Links $NewOwnerUPN -Confirm:$false Add-UnifiedGroupLinks -Identity $Group.Identity -LinkType Owners -Links $NewOwnerUPN -Confirm:$false Write-Output "$($Group.DisplayName): Replaced $OldOwnerUPN with $NewOwnerUPN as owner" } }#
This is a great example of proof-of-concept code that runs superbly in a small tenant where there are fewer than a hundred or so groups but rapidly runs out of steam rapidly as the number of objects to process escalates. The big performance sink is running the Get-UnifiedGroup cmdlet to fetch every Microsoft 365 group in the tenant. Because of the number of properties it retrieves, Get-UnifiedGroup is not a fast cmdlet. It’s a cmdlet that should be used judiciously rather than being the default method to fetch details of Microsoft 365 groups.
Use the Graph for Best Performance
The Get-MgUserOwnedObject cmdlet from the Microsoft Graph PowerShell SDK is faster and more scalable. Get-MgUserOwnedObject is based on the Graph list OwnedObjects API. Its purpose is to find the set of Entra ID directory objects owned by a user account and requires consent for the Directory.Read.All permission to find those objects.
This example fetches the set of Microsoft 365 Groups owned by a user account. Unfortunately, it’s not possible to use a server-side filter to extract the Microsoft 365 groups from the set of directory objects fetched by the cmdlet. The set of owned objects can include other group types such security groups and distribution lists and other objects like applications and service principals. Using a client-side filter isn’t a huge issue here because most of the fetched objects are likely to be Microsoft 365 groups.
[array]$Groups = Get-MgUserOwnedObject -UserId Kim.Akers@office365itpros.com -All | Where-Object {$_.additionalProperties.groupTypes -eq "unified"}
Like many Graph SDK cmdlets, the output for a group is determined by the underlying Graph request. If you look at the information returned for an object in the output array, you’ll see something like this:
$Groups[0] | Format-List DeletedDateTime : Id : f9b6dcb7-609d-48ca-83c1-5afbfe888fe0 AdditionalProperties : {[@odata.type, #microsoft.graph.group], [createdDateTime, 2020-06-08T16:59:06Z], [creationOptions, System.Object[]], [description, Sunny Days]…}
If you examine the details of the additionalProperties property for a group, you’ll see the “normal” properties that a cmdlet like Get-MgGroup returns for a group.
$Groups[0].additionalProperties @odata.type #microsoft.graph.group createdDateTime 2020-06-08T16:59:06Z creationOptions {SPSiteLanguage:1033, HubSiteId:00000000-0000-0000-0000-000000000000, SensitivityLabel:00000000-0000-0000-0000-000000000000, ProvisionGro… description Sunny Days displayName Sunny Days groupTypes {Unified} mail SunnyDays@office365itpros.com mailEnabled True mailNickname SunnyDays proxyAddresses {SMTP:SunnyDays@office365itpros.com, SPO:SPO_c5365af4-7636-4bca-a9d2-e1eb9bbfe9f6@SPO_b… renewedDateTime 2020-06-08T16:59:06Z resourceBehaviorOptions {} resourceProvisioningOptions {} securityEnabled False securityIdentifier S-1-12-1-4189510839-1221222557-4217028995-3767503102 visibility Private onPremisesProvisioningErrors {} serviceProvisioningErrors {}
The most important property is the group identifier because it’s needed to update group membership. The other group properties can be accessed by prefixing them with additionalProperties (which is case sensitive). For example:
$Groups[0].additionalProperties.displayName Office 365 Planner Tips
Rewriting the Original Code to Replace Group Owners
The original code can be adjusted to replace Get-UnifiedGroup with Get-MgUserOwnedObject. Apart from the performance boost gained by only finding the set of Microsoft 365 groups owned by the user instead of all groups, eliminating the need to run the Get-UnifiedGroupLinks cmdlet to check the ownership of each group improves code execution further:
[array]$Groups = Get-MgUserOwnedObject -UserId $OldOwnerUPN -All | Where-Object {$_.additionalProperties.groupTypes -eq "unified ForEach ($Group in $Groups) { Remove-UnifiedGroupLinks -Identity $Group.Id -LinkType Owners -Links $OldOwnerUPN -Confirm:$false Add-UnifiedGroupLinks -Identity $Group.Id -LinkType Members -Links $NewOwnerUPN -Confirm:$false Add-UnifiedGroupLinks -Identity $Group.Id -LinkType Owners -Links $NewOwnerUPN -Confirm:$false Write-Output "$($Group.additionalPropertiesDisplayName): Replaced $OldOwnerUPN with $NewOwnerUPN as owner" }
The lesson here is that you can mix and match cmdlets from different Microsoft 365 PowerShell modules to solve problems. In this case, Get-MgUserOwnedObject finds groups to process before the group memberships are updated using the Remove-UnifiedGroupLinks and Add-UnifiedGroupLinks cmdlets.
Running a Pure Microsoft Graph PowerShell SDK Version
You might want to write a script based solely on the Microsoft Graph PowerShell SDK instead of combining Exchange Online and Graph SDK cmdlets. To do this, we replace the Exchange cmdlets with the following SDK cmdlets:
- New-MgGroupMember: Add the new owner as a member of the group.
- New-MgGroupOwnerByRef: Add the new owner as a group owner. This should be done before removing the original owner to avoid the risk of failure because Entra ID won’t allow cmdlets to remove the last owner of a group
- Remove-MgGroupOwnerDirectoryObjectByRef: Remove the old owner as a group owner. This also removes the account from group membership.

To see how to use these cmdlets, check out this script, available from the Office 365 for IT Pros GitHub Repository. You’ll notice that I include calls to Get-MgGroupMember and Get-MgGroupOwner to avoid attempting to add the new owner as a group member and owner if they already hold these roles. The best thing of all is that the script (Figure 1) is extremely fast.
To complete the job, you should update any security groups, distribution lists, and dynamic distribution lists owned by the soon-to-depart account. The required code isn’t difficult, so I shall leave it to the reader to write.
Need some assistance to write and manage PowerShell scripts for Microsoft 365? 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.