Mailbox forwarding is one of the most commonly abused email features in Microsoft 365 environments. While forwarding can support legitimate business workflows, it is also frequently leveraged in:
Attackers often configure forwarding rules to secretly redirect sensitive emails to external accounts without the user’s knowledge.
This PowerShell automation solution helps administrators perform Exchange Online mailbox forwarding security audits by detecting:
The script generates a detailed security governance report, calculates forwarding risk levels, exports results to CSV, and automatically emails a governance summary to administrators.
Try the M365Corner Microsoft 365 Reporting Tool — your DIY pack with 20+ out-of-the-box M365 reports for Users, Groups, and Teams.
Unauthorized mailbox forwarding can expose organizations to:
Forwarding rules are especially dangerous because they can silently redirect:
Regular forwarding audits help organizations detect suspicious forwarding activity before it escalates into a larger security incident.
Mailbox forwarding and inbox rule forwarding are often confused, but they work differently.
| Type | Description |
|---|---|
| Mailbox Forwarding | Configured at the mailbox level using forwarding addresses |
| Inbox Rule Forwarding | Configured using mailbox inbox rules created by users |
Attackers frequently abuse inbox rules because they are:
This script audits both forwarding methods.
Compromised accounts often use forwarding rules to:
Common attacker techniques include:
Regular forwarding governance reviews help detect these indicators early.
Install the required PowerShell modules:
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Install-Module Microsoft.Graph -Scope CurrentUser
Connect to Exchange Online:
Connect-ExchangeOnline
Connect to Microsoft Graph:
Connect-MgGraph -Scopes `
"User.Read.All",
"Mail.Read",
"Mail.Send"
# Import required modules
Import-Module ExchangeOnlineManagement
Import-Module Microsoft.Graph
# Connect to Exchange Online
Connect-ExchangeOnline
# Verify Exchange Online connection
$EXOConnection = Get-ConnectionInformation
if (-not $EXOConnection) {
Write-Host "Exchange Online connection failed. Please reconnect using Connect-ExchangeOnline." -ForegroundColor Red
return
}
# Connect to Microsoft Graph
Connect-MgGraph -Scopes `
"User.Read.All",
"Domain.Read.All",
"Mail.Send"
# Export report path
$ReportPath = "C:\Reports\MailboxForwardingSecurityAudit.csv"
# Email settings
$Sender = "admin@contoso.com"
$Recipient = "securityteam@contoso.com"
# Get accepted/internal domains using Microsoft Graph
$AcceptedDomains = (
Get-MgDomain |
Where-Object {
$_.IsVerified -eq $true
}
).Id
# High-risk consumer domains
$ConsumerDomains = @(
"gmail.com",
"yahoo.com",
"outlook.com",
"hotmail.com",
"icloud.com"
)
$AuditReport = @()
# Retrieve mailboxes
$Mailboxes = Get-EXOMailbox `
-ResultSize Unlimited `
-Properties ForwardingSMTPAddress,ForwardingAddress,RecipientTypeDetails
foreach ($Mailbox in $Mailboxes) {
try {
Write-Host "Processing mailbox: $($Mailbox.UserPrincipalName)" -ForegroundColor Cyan
$ForwardingMethods = @()
$Severity = "Low"
$RiskScore = 0
$Recommendations = @()
$ForwardDestination = $null
$ForwardingType = $null
$DestinationCategory = "None"
# Mailbox-level forwarding detection
if ($Mailbox.ForwardingSMTPAddress) {
$ForwardDestination = $Mailbox.ForwardingSMTPAddress.ToString()
$ForwardingType = "Mailbox Forwarding"
$ForwardingMethods += "Mailbox Forwarding"
$RiskScore += 30
$Recommendations += "Review mailbox-level forwarding"
# Clean forwarding address
$CleanForwardingAddress = $ForwardDestination -replace "smtp:", ""
# Extract domain
$ForwardDomain = (
$CleanForwardingAddress -split "@"
)[-1].ToLower()
# Internal vs external forwarding
if ($AcceptedDomains -contains $ForwardDomain) {
$DestinationCategory = "Internal"
if ($Severity -ne "Critical") {
$Severity = "Medium"
}
}
else {
$DestinationCategory = "External"
$Severity = "High"
$RiskScore += 40
$Recommendations += "Investigate external forwarding immediately"
if ($ConsumerDomains -contains $ForwardDomain) {
$DestinationCategory = "Consumer Email Domain"
$RiskScore += 20
$Recommendations += "Review suspicious consumer email forwarding"
}
}
}
# Inbox rule forwarding detection
$InboxRules = Get-InboxRule `
-Mailbox $Mailbox.UserPrincipalName `
-ErrorAction SilentlyContinue
$ForwardingRules = $InboxRules | Where-Object {
$_.ForwardTo -or
$_.RedirectTo -or
$_.ForwardAsAttachmentTo
}
if ($ForwardingRules) {
$ForwardingMethods += "Inbox Rule Forwarding"
$RiskScore += 35
if ($Severity -ne "Critical") {
$Severity = "High"
}
$Recommendations += "Review suspicious inbox forwarding rules"
}
# Multiple forwarding method detection
if ($ForwardingMethods.Count -gt 1) {
$Severity = "Critical"
$RiskScore += 30
$Recommendations += "Investigate possible compromise activity"
}
# Shared mailbox detection
if ($Mailbox.RecipientTypeDetails -eq "SharedMailbox") {
$RiskScore += 10
$Recommendations += "Validate forwarding necessity for shared mailbox"
}
# Add only mailboxes with forwarding activity
if ($ForwardingMethods.Count -gt 0) {
$AuditReport += [PSCustomObject]@{
Mailbox = $Mailbox.UserPrincipalName
DisplayName = $Mailbox.DisplayName
MailboxType = $Mailbox.RecipientTypeDetails
ForwardingType = $ForwardingMethods -join "; "
ForwardDestination = $ForwardDestination
DestinationCategory = $DestinationCategory
Severity = $Severity
ForwardingRiskScore = $RiskScore
Recommendations = $Recommendations -join "; "
}
}
}
catch {
$AuditReport += [PSCustomObject]@{
Mailbox = $Mailbox.UserPrincipalName
DisplayName = $Mailbox.DisplayName
MailboxType = $Mailbox.RecipientTypeDetails
ForwardingType = "Audit Failure"
ForwardDestination = "N/A"
DestinationCategory = "Unknown"
Severity = "High"
ForwardingRiskScore = 0
Recommendations = $_.Exception.Message
}
Write-Host "Error processing mailbox: $($Mailbox.UserPrincipalName)" -ForegroundColor Red
Write-Host $_.Exception.Message
}
}
# Export report
$AuditReport | Export-Csv `
-Path $ReportPath `
-NoTypeInformation `
-Encoding UTF8
Write-Host "Mailbox forwarding security report exported successfully." -ForegroundColor Green
# Governance statistics
$TotalForwardingMailboxes = $AuditReport.Count
$ExternalForwarding = (
$AuditReport |
Where-Object {
$_.DestinationCategory -match "External|Consumer"
}
).Count
$CriticalFindings = (
$AuditReport |
Where-Object {
$_.Severity -eq "Critical"
}
).Count
$InboxRuleForwarding = (
$AuditReport |
Where-Object {
$_.ForwardingType -match "Inbox Rule Forwarding"
}
).Count
# HTML preview
$HtmlPreview = (
$AuditReport |
Select-Object -First 10 |
ConvertTo-Html -Fragment
)
# Email body
$EmailBody = @"
<html>
<body>
<h2>Exchange Online Mailbox Forwarding Security Audit</h2>
<p>The automated mailbox forwarding security audit has completed successfully.</p>
<ul>
<li>Total Mailboxes with Forwarding: $TotalForwardingMailboxes</li>
<li>External Forwarding Detected: $ExternalForwarding</li>
<li>Critical Security Findings: $CriticalFindings</li>
<li>Inbox Rule Forwarding Detected: $InboxRuleForwarding</li>
</ul>
<p>Below is a preview of the first 10 mailboxes with forwarding activity:</p>
$HtmlPreview
</body>
</html>
"@
# Send email report
$params = @{
message = @{
subject = "Exchange Online Mailbox Forwarding Security Audit"
body = @{
contentType = "HTML"
content = $EmailBody
}
toRecipients = @(
@{
emailAddress = @{
address = $Recipient
}
}
)
attachments = @(
@{
"@odata.type" = "#microsoft.graph.fileAttachment"
name = "MailboxForwardingSecurityAudit.csv"
contentBytes = [System.Convert]::ToBase64String(
[System.IO.File]::ReadAllBytes($ReportPath)
)
}
)
}
saveToSentItems = "true"
}
Send-MgUserMail `
-UserId $Sender `
-BodyParameter $params
Write-Host "Mailbox forwarding security audit emailed successfully." -ForegroundColor Green
The script imports:
and establishes connections to:
using: Connect-ExchangeOnline and Connect-MgGraph
This enables the script to retrieve:
before the security audit begins.
Before retrieving mailboxes, the script validates whether the Exchange Online session is active using:
Get-ConnectionInformation
If the Exchange Online connection fails, the script stops execution and prompts the administrator to reconnect.
This prevents:
during execution.
Instead of using Get-AcceptedDomain, the script retrieves verified Microsoft 365 domains using Microsoft Graph:
Get-MgDomain
These domains are treated as:
This allows the script to accurately classify forwarding destinations as:
during risk evaluation.
The script retrieves all Exchange Online mailboxes using:
Get-EXOMailbox
This includes:
The script also retrieves:
required for the audit.
Detects Mailbox-Level Forwarding
If mailbox forwarding is configured, the script:
Mailbox-level forwarding is one of the most common indicators of:
The script extracts the forwarding domain from the forwarding address and compares it against verified Microsoft 365 domains.
Forwarding destinations are classified as:
Examples of consumer email domains include:
External and consumer-domain forwarding configurations receive higher risk scores because they may indicate:
Inbox rules are audited using:
Get-InboxRule
The script identifies forwarding-related inbox rules such as:
Inbox rule forwarding is especially important because attackers frequently use hidden forwarding rules to:
Detected inbox rule forwarding configurations receive elevated risk scores.
The script identifies mailboxes using multiple forwarding mechanisms simultaneously, such as:
Mailboxes containing multiple forwarding methods are automatically classified as:
Critical
because this behavior may indicate:
These mailboxes receive higher forwarding risk scores and remediation recommendations.
The script checks mailbox types using:
RecipientTypeDetails
Shared mailboxes receive additional risk weighting because:
This improves security visibility across non-user mailbox types.
Each mailbox receives a Forwarding Risk Score based on:
Higher scores indicate:
This helps administrators prioritize investigations and remediation activities.
The script generates a detailed mailbox forwarding security report containing:
The report is exported using:
Export-Csv
This provides:
The script automatically:
The email includes:
This makes the solution ideal for:
| Error | Cause | Solution |
|---|---|---|
| Access Denied | The account lacks Exchange Online or Graph permissions. | Ensure the account has:
|
| The term 'Get-InboxRule' is not recognized | Exchange Online PowerShell module is not installed or connected. | Reconnect using: Connect-ExchangeOnline |
| Insufficient privileges to complete the operation | Microsoft Graph permissions are missing. | Reconnect using: Connect-MgGraph -Scopes ` "User.Read.All", "Mail.Read", "Mail.Send" and ensure admin consent is granted. |
Mailbox forwarding remains one of the most important Exchange Online security indicators for detecting:
This PowerShell automation solution helps organizations:
By automating Exchange Online mailbox forwarding security audits, administrators can strengthen Microsoft 365 email security and improve visibility into suspicious forwarding activity across the organization.
© Created and Maintained by LEARNIT WELL SOLUTIONS. All Rights Reserved.