
Remove Stale Exchange Mailbox Delegates and Restore Security
Before you run this
This guide finds and removes delegate access that a former or reassigned user still holds on other people's mailboxes in Exchange Online — the four kinds that actually let one account read or send as another: Full Access, Send As, Send on Behalf, and folder-level (calendar) delegate permissions. Its purpose is offboarding hygiene: when someone leaves or changes roles, their standing access to shared and personal mailboxes should go with them.
Privileges: You need an Exchange Online account with rights to manage recipient permissions — an Exchange Administrator role, or a custom RBAC role holding Mail Recipients and Mail Recipient Creation. It runs from a normal (non-elevated) PowerShell session; you do not need local admin on your workstation. You do need the ExchangeOnlineManagement module (v3 or later) installed.
This changes access control, so treat it carefully. Removing a permission is reversible — you can re-grant it — but if you strip access from a live delegate (an assistant who genuinely still needs a shared mailbox), you break their workflow until you notice. Orphaned SID entries (permissions left behind by a fully deleted account) cannot be re-granted once removed, because the account no longer exists; that removal is effectively permanent, though it is exactly what you want to clean up.
Test first. Read the script before running it. Run every removal with -WhatIf first and read the output. Then run it for real against one test mailbox and one test delegate before you point it at production mailboxes. Do not paste-and-run a bulk loop blind across your tenant.
I assume Exchange Online, the ExchangeOnlineManagement v3 module, and PowerShell 5.1 or 7. On-premises Exchange differs in two places I flag below (Send As uses Get-ADPermission/Remove-ADPermission, not Get-RecipientPermission).
Connect
# One-time install if you don't have it
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Connect-ExchangeOnline -UserPrincipalName admin@example.com
Replace admin@example.com with your admin account. This uses modern auth and will prompt for MFA.
Audit one mailbox before you touch anything
Start by seeing what a specific mailbox hands out. Point it at the mailbox you're cleaning, not the departed user.
$Mailbox = "shared-sales@example.com" # the mailbox being cleaned
# Full Access — exclude self, system accounts, and inherited rights
Get-MailboxPermission -Identity $Mailbox |
Where-Object { $_.IsInherited -eq $false -and
$_.User -notlike "NT AUTHORITY\*" } |
Select-Object User, AccessRights, Deny
# Send As
Get-RecipientPermission -Identity $Mailbox |
Where-Object { $_.Trustee -notlike "NT AUTHORITY\*" } |
Select-Object Trustee, AccessRights
# Send on Behalf
(Get-Mailbox -Identity $Mailbox).GrantSendOnBehalfTo
# Calendar delegate permissions (folder-level)
Get-MailboxFolderPermission -Identity "$($Mailbox):\Calendar" |
Where-Object { $_.User.DisplayName -notin @("Default","Anonymous") } |
Select-Object User, AccessRights
Look for two things: named delegates who have left or changed roles, and orphaned SIDs — entries where User shows as a raw string like S-1-5-21-... instead of a display name. Those are permissions left by deleted accounts, and they're the classic stale artefact.
On-premises Exchange: replace the
Get-RecipientPermissionblock withGet-ADPermission -Identity $Mailbox | Where-Object { $_.ExtendedRights -like "*Send-As*" }.
Remove access for a departed delegate
This function removes all four permission types for one delegate on one mailbox. It runs a dry run by default — nothing is removed unless you pass -Execute. Read the -WhatIf output first.
function Remove-MailboxDelegate {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string]$Mailbox, # mailbox to clean
[Parameter(Mandatory)] [string]$Delegate, # user/SID losing access
[switch]$Execute # omit for a dry run
)
# -WhatIf on a dry run, -Confirm:$false on the real run
$common = if ($Execute) { @{ Confirm = $false } } else { @{ WhatIf = $true } }
Write-Host "Full Access:" -ForegroundColor Cyan
Remove-MailboxPermission -Identity $Mailbox -User $Delegate `
-AccessRights FullAccess @common -ErrorAction SilentlyContinue
Write-Host "Send As:" -ForegroundColor Cyan
Remove-RecipientPermission -Identity $Mailbox -Trustee $Delegate `
-AccessRights SendAs @common -ErrorAction SilentlyContinue
Write-Host "Send on Behalf:" -ForegroundColor Cyan
if ($Execute) {
Set-Mailbox -Identity $Mailbox `
-GrantSendOnBehalfTo @{ Remove = $Delegate } -Confirm:$false `
-ErrorAction SilentlyContinue
} else {
Write-Host " (dry run) would remove $Delegate from GrantSendOnBehalfTo"
}
Write-Host "Calendar delegate:" -ForegroundColor Cyan
Remove-MailboxFolderPermission -Identity "$($Mailbox):\Calendar" `
-User $Delegate @common -ErrorAction SilentlyContinue
}
I use -ErrorAction SilentlyContinue deliberately: not every delegate holds every permission type, and a "permission doesn't exist" error on one type shouldn't stop the others. That's the trade-off — read the audit output so you know what you expect to remove.
Dry run, then execute:
# 1) See what would change
Remove-MailboxDelegate -Mailbox "shared-sales@example.com" -Delegate "jsmith@example.com"
# 2) Do it for real
Remove-MailboxDelegate -Mailbox "shared-sales@example.com" -Delegate "jsmith@example.com" -Execute
Set-Mailbox -GrantSendOnBehalfTo @{Remove=...} has no -WhatIf here that reports cleanly per-entry, which is why I gate the whole Send-on-Behalf action behind -Execute and just print intent on the dry run.
Cleaning orphaned SIDs
For a permission left by a deleted account, pass the SID string straight through as the delegate. Copy the exact User value from the audit output:
Remove-MailboxDelegate -Mailbox "shared-sales@example.com" `
-Delegate "S-1-5-21-1234567890-0987654321-1122334455-5001"
# add -Execute once the dry run looks right
Send As and Send on Behalf are unlikely to hold an orphaned SID, so expect those two lines to report nothing — that's normal.
Finding every mailbox a departed user can touch
To sweep a whole tenant for one departed person's Full Access rights, loop the mailboxes. This is read-only and only reports; feed the results into the removal function once you've reviewed them.
$Departed = "jsmith@example.com"
Get-Mailbox -ResultSize Unlimited | ForEach-Object {
$mbx = $_.PrimarySmtpAddress
Get-MailboxPermission -Identity $mbx |
Where-Object { $_.User -eq $Departed -and $_.IsInherited -eq $false } |
Select-Object @{n="Mailbox";e={$mbx}}, User, AccessRights
}
This is slow on a large tenant — it queries every mailbox — so run it off-hours and let it finish. It intentionally does not remove anything.
Verify
Re-run the audit block from the top against the same mailbox. The departed delegate or orphaned SID should no longer appear in any of the four listings:
Get-MailboxPermission -Identity "shared-sales@example.com" |
Where-Object { $_.User -eq "jsmith@example.com" } # expect no output
Get-RecipientPermission -Identity "shared-sales@example.com" |
Where-Object { $_.Trustee -eq "jsmith@example.com" } # expect no output
Note that Full Access auto-mapping (the automatic appearance of a mailbox in Outlook) can take time to clear from clients even after the permission is gone; the permission removal above is what matters for security.
Undo
Removals of a still-valid account are reversible with the matching Add- cmdlets. Re-grant only what you actually need back:
Add-MailboxPermission -Identity "shared-sales@example.com" -User "jsmith@example.com" -AccessRights FullAccess -AutoMapping:$true
Add-RecipientPermission -Identity "shared-sales@example.com" -Trustee "jsmith@example.com" -AccessRights SendAs
Set-Mailbox -Identity "shared-sales@example.com" -GrantSendOnBehalfTo @{ Add = "jsmith@example.com" }
Add-MailboxFolderPermission -Identity "shared-sales@example.com:\Calendar" -User "jsmith@example.com" -AccessRights Editor
An orphaned-SID removal cannot be undone — the account behind that SID is gone, so there is nothing to re-grant, and that is the point of clearing it.
For exact parameters and access-right values, see Microsoft Learn for Remove-MailboxPermission, Remove-RecipientPermission, Set-Mailbox, and Remove-MailboxFolderPermission. Confirm any value there before running it in production rather than trusting it from memory.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
Related guides
Bulk-Create Shared Mailboxes and Permissions in Exchange Online with PowerShell
This guide reads a CSV file and, for each row, creates a shared mailbox in Exchange Online and grants the people you list Full Access and Send As on it. The purpose is to onboard many shared mailboxes (team@, support@, invoices@) in one pass instead of clicking through the admin center dozens of times.
Automate a Weekly NTFS Share Permission Drift Audit
Share permissions drift. Someone grants a contractor "just temporary" access, a helpdesk ticket adds a group to a folder, an app installer rewrites an ACL — and six months later nobody remembers why Everyone can write to the finance share. This guide builds a simple, read-only baseline of your NTFS share ACLs and a weekly job that tells you exactly what has changed since.
Bulk-Update Exchange Mailbox Forwarding from a CSV
This guide sets mailbox-level forwarding (the ForwardingSmtpAddress / ForwardingAddress and DeliverToMailboxAndForward properties) on many mailboxes at once, reading the target address for each mailbox from a CSV file. It uses Set-Mailbox in Exchange Online PowerShell. This is not the same as an Outlook inbox rule — it is the server-side forwarding an admin sets, and it applies regardless of whether the user's Outlook is open.
Bulk-Set Out-of-Office Auto-Replies with Exchange PowerShell
This guide sets the Automatic Replies (out-of-office) configuration on multiple mailboxes at once, using Set-MailboxAutoReplyConfiguration in Exchange Online PowerShell. It turns auto-reply on (or schedules it), and writes the internal and external message text. It does not delete mail, move anything, or change mailbox permissions — but it does overwrite whatever auto-reply text and state each affected mailbox currently has, and there is no built-in "undo" that restores the previous message. If a user had their own carefully worded reply set, this replaces it. So capture the current state first (I show how below) and treat the change list as production data.



