All scripts
Governance 314

Disable External Mail Forwarding Rules

Scans every mailbox for inbox rules and forwarding settings that send mail to an outside domain, reports what it finds, and can disable them in one pass with a -Disable switch.

Disable-ExternalMailForwarding.ps1
<#
.SYNOPSIS
    Finds and disables mailbox inbox rules and forwarding settings that send mail to external domains.

.DESCRIPTION
    Many mailbox compromises stay quiet by adding a forwarding rule that copies mail out to an
    outside address. This script checks every mailbox for two things: inbox rules that forward or
    redirect to an external domain, and the mailbox-level ForwardingSmtpAddress setting. It reports
    what it finds, and with -Disable it will remove the forwarding action from matching inbox rules
    and clear the mailbox-level forwarding address. Your own accepted domains are read from the
    tenant automatically, so anything going outside those domains is treated as external.

.PARAMETER Disable
    When set, actually disables the matching inbox rules and clears external forwarding addresses.
    Without this switch, the script only reports what it would change.

.PARAMETER ReportOnly
    Alias-style safety switch, same behavior as omitting -Disable. Included so a scheduled task can
    call the script with an explicit "just report" flag for clarity.

.EXAMPLE
    .\Disable-ExternalMailForwarding.ps1

    Connects to Exchange Online and prints a report of every mailbox rule or forwarding setting that
    points to an external domain, without changing anything.

.EXAMPLE
    .\Disable-ExternalMailForwarding.ps1 -Disable

    Same check, but also disables the offending inbox rules and clears external forwarding addresses.

.AUTHOR
    Shehryar Hassan
#>

[CmdletBinding()]
param(
    [switch]$Disable,
    [switch]$ReportOnly
)

if (-not (Get-Module -ListAvailable -Name ExchangeOnlineManagement)) {
    Write-Error "The ExchangeOnlineManagement module is not installed. Run: Install-Module ExchangeOnlineManagement"
    return
}

Import-Module ExchangeOnlineManagement -ErrorAction Stop

try {
    Get-ConnectionInformation -ErrorAction Stop | Out-Null
}
catch {
    Connect-ExchangeOnline -ShowBanner:$false
}

$applyChanges = $Disable -and (-not $ReportOnly)

Write-Host "Loading accepted domains for this tenant..." -ForegroundColor Cyan
$acceptedDomains = (Get-AcceptedDomain).DomainName

if (-not $acceptedDomains -or $acceptedDomains.Count -eq 0) {
    Write-Error "Could not read accepted domains. Check your Exchange Online permissions and try again."
    return
}

function Test-ExternalAddress {
    param([string]$Address)

    if ([string]::IsNullOrWhiteSpace($Address)) {
        return $false
    }

    $domain = $Address -replace '^.*@', ''
    return -not ($acceptedDomains -contains $domain)
}

$findings = New-Object System.Collections.Generic.List[object]

Write-Host "Checking mailbox-level forwarding settings..." -ForegroundColor Cyan
$mailboxes = Get-EXOMailbox -ResultSize Unlimited -Properties ForwardingSmtpAddress, ForwardingAddress

foreach ($mailbox in $mailboxes) {

    if ($mailbox.ForwardingSmtpAddress) {
        $target = $mailbox.ForwardingSmtpAddress -replace '^smtp:', ''
        if (Test-ExternalAddress -Address $target) {
            $findings.Add([pscustomobject]@{
                Mailbox   = $mailbox.PrimarySmtpAddress
                Source    = 'Mailbox forwarding'
                RuleName  = 'ForwardingSmtpAddress'
                Target    = $target
            })

            if ($applyChanges) {
                Set-Mailbox -Identity $mailbox.Identity -ForwardingSmtpAddress $null -DeliverToMailboxAndForward $false
                Write-Host "  Cleared mailbox forwarding on $($mailbox.PrimarySmtpAddress)" -ForegroundColor Yellow
            }
        }
    }
}

Write-Host "Checking inbox rules on every mailbox, this can take a while..." -ForegroundColor Cyan

foreach ($mailbox in $mailboxes) {

    $rules = Get-InboxRule -Mailbox $mailbox.PrimarySmtpAddress -ErrorAction SilentlyContinue
    if (-not $rules) {
        continue
    }

    foreach ($rule in $rules) {

        $externalTargets = @()
        $externalTargets += $rule.ForwardTo | Where-Object { $_ -match '@' } | Where-Object { Test-ExternalAddress -Address ($_ -replace '^.*SMTP:([^\]]+).*$', '$1') }
        $externalTargets += $rule.RedirectTo | Where-Object { $_ -match '@' } | Where-Object { Test-ExternalAddress -Address ($_ -replace '^.*SMTP:([^\]]+).*$', '$1') }

        if ($externalTargets.Count -gt 0) {
            $findings.Add([pscustomobject]@{
                Mailbox  = $mailbox.PrimarySmtpAddress
                Source   = 'Inbox rule'
                RuleName = $rule.Name
                Target   = ($externalTargets -join '; ')
            })

            if ($applyChanges) {
                Disable-InboxRule -Mailbox $mailbox.PrimarySmtpAddress -Identity $rule.Identity -Confirm:$false
                Write-Host "  Disabled rule '$($rule.Name)' on $($mailbox.PrimarySmtpAddress)" -ForegroundColor Yellow
            }
        }
    }
}

if ($findings.Count -eq 0) {
    Write-Host "No external forwarding found. Mailboxes and inbox rules all point inside your accepted domains." -ForegroundColor Green
    return
}

Write-Host ""
Write-Host "Found $($findings.Count) item(s) pointing to an external domain:" -ForegroundColor Red
$findings | Format-Table -AutoSize

if (-not $applyChanges) {
    Write-Host ""
    Write-Host "This was a report only. Re-run with -Disable to clear these forwards." -ForegroundColor Cyan
}

$exportPath = Join-Path -Path (Get-Location) -ChildPath "ExternalForwardingReport-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
$findings | Export-Csv -Path $exportPath -NoTypeInformation
Write-Host "Full report saved to $exportPath" -ForegroundColor Cyan

Read it before you run it, and test in a safe tenant first.