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.