
Find and Remove Orphaned Exchange Forwarding Rules
When you delete a mailbox, the forwarding that pointed at it from elsewhere doesn't clean itself up. A mailbox with ForwardingAddress set to a now-deleted user, or an inbox rule in someone else's mailbox that redirects to a departed colleague, just quietly starts dropping or bouncing mail. This guide scans the whole tenant for that leftover forwarding, reports it, and removes the mailbox-level cases safely.
Before you run this
What this does. It connects to Exchange Online, enumerates every mailbox, and collects two kinds of forwarding: mailbox-level forwarding (ForwardingAddress / ForwardingSmtpAddress) and forwarding or redirect actions inside inbox rules. For each target it calls Get-Recipient to see whether the destination still exists. "Orphaned" here means the forwarding target no longer resolves to a recipient in your organization. The scan is read-only; a separate step removes the orphaned mailbox-level forwarding, and inbox-rule cleanup is done by hand after you've reviewed it.
Privileges. This is not local admin. You need an account with an Exchange administrator role (Organization Management covers it). Reading another user's inbox rules is gated by RBAC, so a plain Global Reader may not see everything. The ExchangeOnlineManagement module can be installed per-user with -Scope CurrentUser, no elevation needed.
Test first. Read the scripts before you run them. Run the report against your real tenant — it changes nothing. Then test the removal against a single throwaway test mailbox you've deliberately given a broken forwarding address, before you let it loose on the CSV.
This is destructive and only partly reversible. Clearing ForwardingAddress or ForwardingSmtpAddress, and deleting an inbox rule, cannot be undone with an "undo" button. The scripts here record the old values in the CSV first so you have a record, and I use -WhatIf and prefer Disable-InboxRule over Remove-InboxRule so the first pass is reversible. If the target was genuinely deleted, you can't restore ForwardingAddress to it anyway — the recipient is gone.
I'm assuming Exchange Online with the ExchangeOnlineManagement v3 module on PowerShell 5.1 or 7. On-premises Exchange uses the Exchange Management Shell but the same cmdlets (Get-Mailbox, Get-InboxRule, Set-Mailbox), so the logic carries over.
Install-Module ExchangeOnlineManagement -Scope CurrentUser # once
Connect-ExchangeOnline -UserPrincipalName admin@example.com # your admin UPN
Replace admin@example.com and every C:\path\to\... below with your own values.
Step 1 — Report (read-only)
This builds one row per forwarding target and flags whether it's orphaned. Note that Get-InboxRule runs once per mailbox, so in a large tenant this takes a while — run it off-hours.
$report = [System.Collections.Generic.List[object]]::new()
$acceptedDomains = (Get-AcceptedDomain).DomainName
# Get-ExoMailbox only returns default properties unless you ask for more.
$mailboxes = Get-ExoMailbox -ResultSize Unlimited `
-Properties ForwardingAddress,ForwardingSmtpAddress,DeliverToMailboxAndForward
foreach ($mbx in $mailboxes) {
# --- Mailbox-level: ForwardingAddress (an internal recipient) ---
if ($mbx.ForwardingAddress) {
$resolves = [bool](Get-Recipient -Identity $mbx.ForwardingAddress -ErrorAction SilentlyContinue)
$report.Add([pscustomobject]@{
Mailbox = $mbx.PrimarySmtpAddress; Source = 'Mailbox:ForwardingAddress'
RuleName = ''; Target = $mbx.ForwardingAddress
TargetExists = $resolves; Orphaned = (-not $resolves)
})
}
# --- Mailbox-level: ForwardingSmtpAddress (free SMTP, often external) ---
if ($mbx.ForwardingSmtpAddress) {
$smtp = ("$($mbx.ForwardingSmtpAddress)" -replace '^smtp:','') # strip the smtp: prefix
$domain = $smtp.Split('@')[-1]
$internal = $acceptedDomains -contains $domain
$resolves = [bool](Get-Recipient -Identity $smtp -ErrorAction SilentlyContinue)
$report.Add([pscustomobject]@{
Mailbox = $mbx.PrimarySmtpAddress; Source = 'Mailbox:ForwardingSmtpAddress'
RuleName = ''; Target = $smtp; TargetExists = $resolves
# Only call an *internal* SMTP target that no longer resolves "orphaned".
# External forwarding is a separate review (legitimate or malicious), not orphaned.
Orphaned = ($internal -and -not $resolves)
})
}
# --- Inbox rules that forward / redirect ---
$rules = Get-InboxRule -Mailbox $mbx.PrimarySmtpAddress -ErrorAction SilentlyContinue
foreach ($r in ($rules | Where-Object { $_.ForwardTo -or $_.RedirectTo -or $_.ForwardAsAttachmentTo })) {
$targets = @($r.ForwardTo) + @($r.RedirectTo) + @($r.ForwardAsAttachmentTo) | ForEach-Object { "$_" }
foreach ($t in $targets) {
# Best-effort: pull an SMTP address out of the display string, then test it.
$addr = ([regex]'[\w.%+\-]+@[\w.\-]+\.[A-Za-z]{2,}').Match($t).Value
$resolves = if ($addr) { [bool](Get-Recipient -Identity $addr -ErrorAction SilentlyContinue) } else { $null }
$report.Add([pscustomobject]@{
Mailbox = $mbx.PrimarySmtpAddress; Source = 'InboxRule'
RuleName = $r.Name; Target = $t; TargetExists = $resolves
Orphaned = ($resolves -eq $false)
})
}
}
}
$report | Export-Csv -NoTypeInformation -Path "C:\path\to\forwarding-report.csv"
$report | Where-Object Orphaned | Format-Table Mailbox,Source,RuleName,Target -AutoSize
A note on the inbox-rule parsing: ForwardTo and friends come back as display strings, not clean addresses, so I extract the SMTP part with a regex. That's good enough to flag candidates, but it's not reliable enough to delete on. So the script only auto-removes mailbox-level forwarding. Review the CSV before doing anything. Pay particular attention to any Mailbox:ForwardingSmtpAddress rows pointing externally — those aren't "orphaned" and may be an exfiltration rule worth investigating separately.
Step 2 — Remove orphaned mailbox-level forwarding
This reads the CSV you just reviewed and clears only the rows flagged Orphaned. It runs with -WhatIf so the first pass shows you what would happen and changes nothing.
$csv = Import-Csv "C:\path\to\forwarding-report.csv"
$targets = $csv | Where-Object { $_.Source -like 'Mailbox:*' -and $_.Orphaned -eq 'True' }
foreach ($row in $targets) {
Write-Host "Clearing $($row.Source) on $($row.Mailbox) -> $($row.Target)"
if ($row.Source -eq 'Mailbox:ForwardingAddress') {
Set-Mailbox -Identity $row.Mailbox -ForwardingAddress $null -WhatIf
} else {
Set-Mailbox -Identity $row.Mailbox -ForwardingSmtpAddress $null -WhatIf
}
}
When the -WhatIf output matches what you expect, remove the -WhatIf from both Set-Mailbox lines and run it again to apply.
Step 3 — Clean up
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
Remove Stale Exchange Mailbox Delegates and Restore Security
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.
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.
Disable, Move, and Delete an Active Directory User Safely with PowerShell
This guide walks the standard three-stage offboarding of a single Active Directory user account: disable it (so nobody can log in), move it to a holding OU, and — only after a retention period — delete it. That order is deliberate. Disabling is instant and fully reversible; deletion is not.
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.




