Shore Up
A caretaker taking a ring of old numbered keys off a wall of labelled mailboxes and dropping the outdated ones into a locked disposal bin, keeping the current keys.
MailSecurity

Remove Stale Exchange Mailbox Delegates and Restore Security

Ketan Aagja9 min read
No ratings yet

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-RecipientPermission block with Get-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.

Written by
Ketan Aagja

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.

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.

8 min read

Automate a Network Share Permissions Audit with PowerShell

This guide builds a read-only report . The script enumerates the SMB shares on a Windows file server, then lists two things for each one: the share-level permissions (the "who can connect" layer) and the NTFS permissions on the folder behind it (the "who can touch the files" layer). It writes both to CSV so you can review access in a spreadsheet instead of clicking through the Security tab share by share.

9 min read

Automate User Offboarding in Active Directory with PowerShell

This script offboards one leaving user in a single pass: it disables their AD account, records and removes their group memberships (except the primary group), and moves the account into a disabled-users OU. A separate, clearly marked step sets mail forwarding on their mailbox. The point is a consistent, logged procedure so nothing gets missed and you can reconstruct exactly what changed.

9 min read