How to Get, Remove Owner & Remove Member from Exchange Online Groups Using PowerShell
As an M365 administrator, you often need to audit or clean up group ownership and membership — especially during employee offboarding or role transitions. The challenge? Microsoft 365 has three different group types, each managed by different cmdlets.
In this post, I'll share three ready-to-use PowerShell scripts that cover:
- Get owners of a group (all three types)
- Remove a user as an owner
- Remove a user as a member
All scripts read group names from a CSV file, making them ideal for bulk operations.
The Three Group Types in Exchange Online
| Group Type | RecipientTypeDetails | Cmdlet Used |
|---|---|---|
| Microsoft 365 Group | GroupMailbox | Get-UnifiedGroup |
| Distribution Group | MailUniversalDistributionGroup | Get-DistributionGroup |
| Mail-enabled Security Group | MailUniversalSecurityGroup | Get-DistributionGroup |
Each type stores ownership and membership differently, which is why a single cmdlet won't work for all.
Prerequisites
Install the required modules if not already installed:
Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber Install-Module -Name Microsoft.Graph -Force -AllowClobber
Connect before running any script:
Set-MgGraphOption -DisableLoginByWAM $true # Fix WAM broker error on Windows Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
Set-MgGraphOption -DisableLoginByWAM $true is required on some Windows environments to avoid a known MSAL authentication error.Input: groups.csv
All three scripts read from a CSV with one column — GroupEmail:
GroupEmail hr-team@contoso.com all-staff@contoso.com it-security@contoso.com
Script 1: Get Group Owners
This script detects the group type automatically and lists all current owners.
# ============================================================
# Get Group Owners from CSV
# ============================================================
Set-MgGraphOption -DisableLoginByWAM $true
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
$CsvPath = "C:\Scripts\groups.csv"
foreach ($row in (Import-Csv $CsvPath)) {
$GroupEmail = $row.GroupEmail
Write-Host "`n========== Detecting Group Type: $GroupEmail ==========`n" -ForegroundColor Cyan
# Detect group type
$unifiedGroup = Get-UnifiedGroup -Identity $GroupEmail -ErrorAction SilentlyContinue
$distGroup = Get-DistributionGroup -Identity $GroupEmail -ErrorAction SilentlyContinue
$groupType = if ($unifiedGroup) { "M365Group" }
elseif ($distGroup.RecipientTypeDetails -eq "MailUniversalDistributionGroup") { "DistributionGroup" }
elseif ($distGroup.RecipientTypeDetails -eq "MailUniversalSecurityGroup") { "SecurityGroup" }
else { "Unknown" }
Write-Host "Detected Type: $groupType" -ForegroundColor Yellow
# Get owners based on type
$owners = @()
if ($groupType -eq "M365Group") {
$owners = Get-UnifiedGroupLinks -Identity $GroupEmail -LinkType Owners -ResultSize Unlimited |
Select-Object DisplayName, PrimarySmtpAddress,
@{N="GroupType"; E={"M365Group"}},
@{N="GroupEmail"; E={$GroupEmail}}
}
elseif ($groupType -in "DistributionGroup", "SecurityGroup") {
$owners = $distGroup.ManagedBy | ForEach-Object {
$recipient = Get-Recipient $_ -ErrorAction SilentlyContinue
[PSCustomObject]@{
DisplayName = $recipient.DisplayName
PrimarySmtpAddress = $recipient.PrimarySmtpAddress
GroupType = $groupType
GroupEmail = $GroupEmail
}
}
}
if ($owners) {
Write-Host "`nOwners found:" -ForegroundColor Green
$owners | Format-Table DisplayName, PrimarySmtpAddress, GroupType, GroupEmail -AutoSize
} else {
Write-Host "No owners found or group type is Unknown." -ForegroundColor Red
}
}Script 2: Remove a User as Owner
This script removes a specific user from the owners list only — membership is not affected. It also includes safety checks: skips if the user is not an owner, and blocks removal if they are the only owner.
# ============================================================
# Remove Owner from Groups (CSV)
# ============================================================
Set-MgGraphOption -DisableLoginByWAM $true
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
$CsvPath = "C:\Scripts\groups.csv"
$RemoveOwner = "john.doe@contoso.com"
foreach ($row in (Import-Csv $CsvPath)) {
$GroupEmail = $row.GroupEmail
Write-Host "`n========== Detecting Group Type: $GroupEmail ==========`n" -ForegroundColor Cyan
$unifiedGroup = Get-UnifiedGroup -Identity $GroupEmail -ErrorAction SilentlyContinue
$distGroup = Get-DistributionGroup -Identity $GroupEmail -ErrorAction SilentlyContinue
$groupType = if ($unifiedGroup) { "M365Group" }
elseif ($distGroup.RecipientTypeDetails -eq "MailUniversalDistributionGroup") { "DistributionGroup" }
elseif ($distGroup.RecipientTypeDetails -eq "MailUniversalSecurityGroup") { "SecurityGroup" }
else { "Unknown" }
Write-Host "Detected Type: $groupType" -ForegroundColor Yellow
# Get owners
$owners = @()
if ($groupType -eq "M365Group") {
$owners = Get-UnifiedGroupLinks -Identity $GroupEmail -LinkType Owners -ResultSize Unlimited |
Select-Object DisplayName, PrimarySmtpAddress,
@{N="GroupType"; E={"M365Group"}},
@{N="GroupEmail"; E={$GroupEmail}}
}
elseif ($groupType -in "DistributionGroup", "SecurityGroup") {
$owners = $distGroup.ManagedBy | ForEach-Object {
$recipient = Get-Recipient $_ -ErrorAction SilentlyContinue
[PSCustomObject]@{
DisplayName = $recipient.DisplayName
PrimarySmtpAddress = $recipient.PrimarySmtpAddress
GroupType = $groupType
GroupEmail = $GroupEmail
}
}
}
if ($owners) {
Write-Host "`nOwners found:" -ForegroundColor Green
$owners | Format-Table DisplayName, PrimarySmtpAddress, GroupType, GroupEmail -AutoSize
} else {
Write-Host "No owners found or group type is Unknown." -ForegroundColor Red
}
# --------------------------------------------------------
# Remove Owner
# --------------------------------------------------------
Write-Host "`n========== Removing Owner: $RemoveOwner ==========`n" -ForegroundColor Cyan
if ($groupType -eq "M365Group") {
$currentOwners = Get-UnifiedGroupLinks -Identity $GroupEmail -LinkType Owners -ResultSize Unlimited
$isOwner = $currentOwners | Where-Object { $_.PrimarySmtpAddress -eq $RemoveOwner }
if (-not $isOwner) {
Write-Host "$RemoveOwner is not an owner of this group. Skipping." -ForegroundColor Yellow
}
elseif ($currentOwners.Count -eq 1) {
Write-Host "Cannot remove $RemoveOwner - they are the only owner. Assign another owner first." -ForegroundColor Red
}
else {
Remove-UnifiedGroupLinks -Identity $GroupEmail -LinkType Owners -Links $RemoveOwner -Confirm:$false
Write-Host "Owner removed successfully from M365 Group." -ForegroundColor Green
}
}
elseif ($groupType -in "DistributionGroup", "SecurityGroup") {
$isOwner = $distGroup.ManagedBy | Where-Object { $_ -like "*$($RemoveOwner.Split('@')[0])*" }
if (-not $isOwner) {
Write-Host "$RemoveOwner is not an owner of this group. Skipping." -ForegroundColor Yellow
}
elseif ($distGroup.ManagedBy.Count -eq 1) {
Write-Host "Cannot remove $RemoveOwner - they are the only owner. Assign another owner first." -ForegroundColor Red
}
else {
Set-DistributionGroup -Identity $GroupEmail -ManagedBy @{Remove = $RemoveOwner}
Write-Host "Owner removed successfully from $groupType." -ForegroundColor Green
}
}
else {
Write-Host "Cannot remove owner - Group type is Unknown." -ForegroundColor Red
}
}Script 3: Remove a User as Member
This script removes a specific user from the members list only — ownership is not affected. It checks if the user is actually a member before attempting removal, so no other members are touched.
# ============================================================
# Remove Member from Groups (CSV)
# ============================================================
Set-MgGraphOption -DisableLoginByWAM $true
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
$CsvPath = "C:\Scripts\groups.csv"
$RemoveMember = "john.doe@contoso.com"
foreach ($row in (Import-Csv $CsvPath)) {
$GroupEmail = $row.GroupEmail
Write-Host "`n========== Detecting Group Type: $GroupEmail ==========`n" -ForegroundColor Cyan
$unifiedGroup = Get-UnifiedGroup -Identity $GroupEmail -ErrorAction SilentlyContinue
$distGroup = Get-DistributionGroup -Identity $GroupEmail -ErrorAction SilentlyContinue
$groupType = if ($unifiedGroup) { "M365Group" }
elseif ($distGroup.RecipientTypeDetails -eq "MailUniversalDistributionGroup") { "DistributionGroup" }
elseif ($distGroup.RecipientTypeDetails -eq "MailUniversalSecurityGroup") { "SecurityGroup" }
else { "Unknown" }
Write-Host "Detected Type: $groupType" -ForegroundColor Yellow
# Get owners
$owners = @()
if ($groupType -eq "M365Group") {
$owners = Get-UnifiedGroupLinks -Identity $GroupEmail -LinkType Owners -ResultSize Unlimited |
Select-Object DisplayName, PrimarySmtpAddress,
@{N="GroupType"; E={"M365Group"}},
@{N="GroupEmail"; E={$GroupEmail}}
}
elseif ($groupType -in "DistributionGroup", "SecurityGroup") {
$owners = $distGroup.ManagedBy | ForEach-Object {
$recipient = Get-Recipient $_ -ErrorAction SilentlyContinue
[PSCustomObject]@{
DisplayName = $recipient.DisplayName
PrimarySmtpAddress = $recipient.PrimarySmtpAddress
GroupType = $groupType
GroupEmail = $GroupEmail
}
}
}
if ($owners) {
Write-Host "`nOwners found:" -ForegroundColor Green
$owners | Format-Table DisplayName, PrimarySmtpAddress, GroupType, GroupEmail -AutoSize
} else {
Write-Host "No owners found or group type is Unknown." -ForegroundColor Red
}
# --------------------------------------------------------
# Remove Member
# --------------------------------------------------------
Write-Host "`n========== Removing Member: $RemoveMember ==========`n" -ForegroundColor Cyan
if ($groupType -eq "M365Group") {
$isMember = Get-UnifiedGroupLinks -Identity $GroupEmail -LinkType Members -ResultSize Unlimited |
Where-Object { $_.PrimarySmtpAddress -eq $RemoveMember }
if ($isMember) {
try {
Remove-UnifiedGroupLinks -Identity $GroupEmail -LinkType Members -Links $RemoveMember -Confirm:$false -ErrorAction Stop
Write-Host "Member removed successfully from M365 Group." -ForegroundColor Green
} catch {
Write-Host "ERROR removing member from M365 Group: $_" -ForegroundColor Red
}
} else {
Write-Host "$RemoveMember is not a member of this group. Skipping." -ForegroundColor Yellow
}
}
elseif ($groupType -in "DistributionGroup", "SecurityGroup") {
$isMember = Get-DistributionGroupMember -Identity $GroupEmail -ResultSize Unlimited |
Where-Object { $_.PrimarySmtpAddress -eq $RemoveMember }
if ($isMember) {
try {
Remove-DistributionGroupMember -Identity $GroupEmail -Member $RemoveMember -Confirm:$false -ErrorAction Stop
Write-Host "Member removed successfully from $groupType." -ForegroundColor Green
} catch {
Write-Host "ERROR removing member from $groupType : $_" -ForegroundColor Red
}
} else {
Write-Host "$RemoveMember is not a member of this group. Skipping." -ForegroundColor Yellow
}
}
else {
Write-Host "Cannot remove member - Group type is Unknown." -ForegroundColor Red
}
}Key Points to Remember
| Scenario | What Happens |
|---|---|
| M365 Group — Remove Owner | Uses Remove-UnifiedGroupLinks -LinkType Owners. Member link is kept. |
| M365 Group — Remove Member | Uses Remove-UnifiedGroupLinks -LinkType Members. Owner link is kept. |
| DG / Security Group — Remove Owner | Uses Set-DistributionGroup -ManagedBy @{Remove=...} |
| DG / Security Group — Remove Member | Uses Remove-DistributionGroupMember |
| Only one owner left | Script blocks removal and shows a red warning |
| User not in group | Script skips with a yellow message — no error thrown |
| On-premises synced group | Must be managed via Active Directory, not Exchange Online |
Conclusion
Managing Exchange Online group ownership and membership across multiple group types doesn't have to be complex. By detecting the group type dynamically and applying the right cmdlet per type, these scripts handle all three group types reliably from a simple CSV input. The built-in safety checks — solo owner protection, membership validation, and skip-on-not-found logic — make them safe to run in bulk during offboarding or access reviews.
#PowerShell #ExchangeOnline #Microsoft365 #M365Admin #MicrosoftTeams #M365Groups #DistributionGroups #SecurityGroups #ITAdmin #CloudAdmin #Office365 #M365 #PowerShellAutomation #ExchangeAdmin