Exchange Online

Automating GrantSendOnBehalfTo at Scale in Hybrid Exchange

April 2025 8 min read Exchange Online · PowerShell · Hybrid

If you manage a hybrid Exchange environment long enough, you'll eventually run into a scenario where Set-Mailbox -GrantSendOnBehalfTo throws an error that makes no immediate sense — particularly when the mailbox in question appears perfectly healthy. The culprit is almost always an orphaned entry lurking in the permission list.

What Is an Orphaned Entry?

When a user or group is granted Send on Behalf access and that object is later deleted from Active Directory without the permission being explicitly removed, the GrantSendOnBehalfTo attribute retains a dangling reference — a Distinguished Name that points to nothing. Exchange Online tolerates these silently until you attempt a full list replacement, at which point the write operation fails entirely.

Key insight: The failure only surfaces during a write operation, not during a read. This means your Get-Mailbox output looks clean, but any attempt to update the list throws a resolution error.

Detecting the Problem

The first step is to identify which entries in the list are no longer resolvable. We can do this by fetching the raw list and cross-referencing each entry against Get-EORecipient:

POWERSHELL
# Requires: Connect-ExchangeOnline -Prefix "EO"

function Get-OrphanedSendOnBehalf {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $MailboxIdentity
    )

    $mailbox = Get-EOMailbox -Identity $MailboxIdentity -ErrorAction Stop
    $currentList = $mailbox.GrantSendOnBehalfTo

    if (-not $currentList) {
        Write-Host "No Send on Behalf entries found." -ForegroundColor Yellow
        return
    }

    $orphaned = @()
    $valid    = @()

    foreach ($entry in $currentList) {
        try {
            $resolved = Get-EORecipient -Identity $entry -ErrorAction Stop
            $valid += $resolved.PrimarySmtpAddress
        }
        catch {
            $orphaned += $entry
            Write-Warning "Orphaned entry detected: $entry"
        }
    }

    [PSCustomObject] @{
        Mailbox         = $mailbox.PrimarySmtpAddress
        ValidEntries    = $valid
        OrphanedEntries = $orphaned
    }
}

Remediating Automatically

Once you've identified the orphaned entries, remediation is straightforward — rebuild the list using only the valid, resolvable entries and write it back in a single operation. Using -WhatIf first is strongly recommended in production.

POWERSHELL
function Repair-SendOnBehalf {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory)]
        [string] $MailboxIdentity
    )

    $result = Get-OrphanedSendOnBehalf -MailboxIdentity $MailboxIdentity

    if ($result.OrphanedEntries.Count -eq 0) {
        Write-Host "No orphaned entries found. No action required." -ForegroundColor Green
        return
    }

    Write-Host "`nOrphaned entries to remove:" -ForegroundColor Yellow
    $result.OrphanedEntries | ForEach-Object { Write-Host "  - $_" }

    if ($PSCmdlet.ShouldProcess($MailboxIdentity, "Remove orphaned SendOnBehalf entries")) {
        Set-EOMailbox -Identity $MailboxIdentity `
                       -GrantSendOnBehalfTo $result.ValidEntries

        Write-Host "Done. List updated successfully." -ForegroundColor Green
    }
}

# Usage
Repair-SendOnBehalf -MailboxIdentity "shared-finance@corp.com" -WhatIf
Repair-SendOnBehalf -MailboxIdentity "shared-finance@corp.com"

Why Not Just Clear the Entire List?

You might wonder why we don't simply clear GrantSendOnBehalfTo and start fresh. In many cases that's acceptable, but in environments where permissions were set historically and not tracked elsewhere, clearing the entire list risks losing legitimate delegations. The filtered replacement approach above preserves all valid entries while surgically removing the broken ones.

Bulk Remediation Across All Mailboxes

In large environments, running this against every shared mailbox is essential. Pipe all shared mailboxes into the repair function and log the output:

POWERSHELL
Get-EOMailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited |
    ForEach-Object {
        Repair-SendOnBehalf -MailboxIdentity $_.PrimarySmtpAddress
    }
Hybrid note: In a hybrid environment, GrantSendOnBehalfTo must ultimately be set on-premises via Set-RemoteMailbox for AD Connect to sync it upstream. The EXO approach above is correct for cloud-only mailboxes; for synced mailboxes, apply the same logic against your on-premises Exchange and let AD Connect handle the writeback.

Key Takeaways