Skip to main content

The Inbox Rule You're Not Checking

· 17 min read
tacticalBeard
Automation Enthusiast

Almost every M365 BEC investigation I have worked follows a similar pattern: someone clicks a link, credentials get harvested, and the attacker spends the next several days quietly reading email. The entry point is obvious by the time it surfaces. What takes longer to find is the persistence mechanism the attacker left behind. Most of the time, that mechanism is a single inbox rule sitting in the compromised mailbox with a name that looks like a typo.

Why attackers create inbox rules

Getting into a mailbox is step one. Staying without the user noticing is step two. Inbox rules are the low-friction answer to that problem because they require no malware, no additional tooling, and no elevated permissions. Any authenticated session can create one.

The two most common goals I see:

Suppressing security alerts. Security platforms send email notifications. If the attacker creates a rule that silently moves or marks-as-read anything from your email security vendor, your SIEM alerting address, or your IT helpdesk, the user never sees the warning that their account is being investigated. The attacker bought time.

Hiding financial activity. BEC almost always has a financial motive. Rules that redirect or delete email from payment processors, bank notification addresses, accounting software, or AP/AR contacts give the attacker a window to monitor wire transfer confirmations or intercept payment instructions without the user seeing the thread.

The naming trick and hidden rules

Attackers do not name rules "Definitely Malicious Forward." The names I find most often are a single character, a period (.), whitespace, or an underscore. These are easy to scroll past in the Outlook rules dialog and easy to miss in admin center views that truncate long or unusual strings.

The harder case is rules that do not appear in the UI at all. Certain Outlook clients and API calls can create rules that are invisible to standard admin tooling. The Exchange Online Get-InboxRule cmdlet will not return them unless you pass -IncludeHidden. I have seen IR engagements where the reactive investigation ran Get-InboxRule without that flag and concluded no rules were present. The attacker's rule was still there.

Connecting to Exchange Online

All of the cmdlets in this post come from the ExchangeOnlineManagement module. Install it once from an elevated PowerShell session if you don't have it:

Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser -Force

Then connect. Use -UserPrincipalName to pre-fill your account and skip the extra dialog:

Connect-ExchangeOnline -UserPrincipalName [email protected]

You'll get a browser-based auth prompt. The session stays active for the duration of your PowerShell session. When you're done:

Disconnect-ExchangeOnline -Confirm:$false
note

Search-UnifiedAuditLog requires the Security & Compliance module, which is included in ExchangeOnlineManagement v3+. If you are on an older version, run Update-Module -Name ExchangeOnlineManagement first.

Checking a specific mailbox

Start here during an active investigation. Run both blocks. The second one catches what the first misses.

# Standard rules -- what the user and most admin tools see
Get-InboxRule -Mailbox "[email protected]" |
Select-Object Name, Enabled, Description, MoveToFolder, MarkAsRead, From |
Format-List

# Include hidden rules -- these won't appear above
Get-InboxRule -Mailbox "[email protected]" -IncludeHidden |
Where-Object { $_.Name -ne "Junk E-Mail Rule" } |
Select-Object Name, Enabled, Description, MoveToFolder, MarkAsRead, From |
Format-List

The Junk E-Mail Rule exclusion keeps the output clean. Every mailbox has one and it is not interesting.

What rule creation tells you about the investigation

This is the part that changes how you frame an incident. Creating an inbox rule requires an authenticated session. If you find a rule the user did not create, that is not a phishing attempt that might have succeeded. That is a confirmed account compromise with interactive access. You are no longer asking "was the account phished?" You are asking "what did the attacker access, what did they read, and how long were they in?"

That shift matters for scope. It means pulling the full sign-in history, checking for mail exports or delegation changes, and extending your timeline.

Finding when the rule was created

The unified audit log records New-InboxRule, Set-InboxRule, and UpdateInboxRules events. This query pulls 90 days and extracts the fields you actually want during an investigation.

Search-UnifiedAuditLog `
-StartDate (Get-Date).AddDays(-90) `
-EndDate (Get-Date) `
-Operations "New-InboxRule","Set-InboxRule","UpdateInboxRules" `
-UserIds "[email protected]" `
-ResultSize 5000 |
ForEach-Object {
$Data = $_.AuditData | ConvertFrom-Json
[PSCustomObject]@{
Date = $_.CreationDate
Operation = $_.Operations
ClientIP = $Data.ClientIP
App = $Data.ClientInfoString
}
} | Format-Table -AutoSize

The ClientIP and ClientInfoString fields are what you want. The IP anchors the session to a specific source. The client string tells you whether the rule was created through the Outlook desktop client, OWA, or a direct API call. A rule created by a PowerShell client or a raw API call when the user has never done that before is worth flagging.

Audit log availability

Audit logging must be enabled on the tenant, and the unified audit log retains Exchange events for 90 days by default on standard plans. E3 and E5 licenses extend retention to one year. If you are investigating an incident that started more than 90 days ago on a standard plan, that window may already be closed.

Proactive tenant-wide hunt

Running this after an incident closes the immediate question. Running it periodically as a proactive hunt is what catches compromises before they escalate. This block iterates every mailbox in the tenant, pulls hidden rules, and filters for the patterns that show up most often in BEC cases.

Get-Mailbox -ResultSize Unlimited | ForEach-Object {
Get-InboxRule -Mailbox $_.PrimarySmtpAddress -IncludeHidden -ErrorAction SilentlyContinue |
Where-Object {
$_.Name -ne "Junk E-Mail Rule" -and (
# Single-character or whitespace-only names
$_.Name -match '^\s*[.\s_-]?\s*$' -or
# Rules moving to unusual folders
$_.MoveToFolder -match 'Conversation History|RSS Feeds|Deleted Items' -or
# Rules targeting security or financial notification senders
($_.From -and $_.From -match 'security|alert|noreply|payment|invoice|notification')
)
} | Select-Object @{N='Mailbox';E={$_.MailboxOwnerId}},
Name, Enabled, MoveToFolder, MarkAsRead, From
} | Where-Object { $_ } | Format-Table -AutoSize

The three filter conditions catch distinct attacker behaviors. Unusual folder destinations like Conversation History and RSS Feeds are common hiding spots because users rarely check them. The sender pattern match is intentionally broad (you will get some false positives on legitimate rules users have set up for mailing list management). Review the output with that in mind.

On a large tenant this will run slowly. Schedule it during off-peak hours or narrow the scope with -Filter if you have a specific OU or department to start with.

Check forwarding alongside rules

Inbox rules and mailbox-level forwarding are two separate mechanisms. Attackers use both. Run these at the same time you check rules.

Get-Mailbox -Identity "[email protected]" |
Select-Object DisplayName, ForwardingAddress, ForwardingSmtpAddress, DeliverToMailboxAndForward

# Tenant-wide forwarding audit
Get-Mailbox -ResultSize Unlimited |
Where-Object { $_.ForwardingSmtpAddress -or $_.ForwardingAddress } |
Select-Object DisplayName, PrimarySmtpAddress, ForwardingSmtpAddress, DeliverToMailboxAndForward

DeliverToMailboxAndForward set to True means mail is going to both the mailbox and an external address simultaneously. That is quiet exfiltration that can run for months without the user noticing anything is wrong because they still receive all their mail.

Add this to your playbook

Inbox rule auditing belongs on every M365 compromise investigation checklist as a standard step, not an optional one. The specific actions:

  • Run Get-InboxRule with -IncludeHidden on every compromised mailbox before you close the case.
  • Pull the unified audit log for rule creation events and correlate the source IP against the sign-in log.
  • Any rule suppressing mail from security tools or financial notification senders is an immediate escalation, regardless of whether the attacker is still active.
  • Run the tenant-wide hunt on a regular cadence. Monthly is reasonable. Quarterly is the minimum.

The attacker cleans up after themselves if they have time. Finding the rule before they do is the goal.


Full Script

<#
.SYNOPSIS
Hunts for malicious or suspicious inbox rules in Microsoft 365 mailboxes.

.DESCRIPTION
Provides functions for both targeted single-mailbox investigation during
an incident and proactive tenant-wide hunting to identify rules that
match common BEC (Business Email Compromise) persistence patterns.

BEC attackers frequently create inbox rules after account compromise to:
- Suppress security alert emails (quarantine notices, sign-in alerts)
- Hide payment processor notifications to enable financial fraud
- Hide communications with specific vendors to intercept wire transfers
- Maintain a window of undetected access

Rules created for malicious purposes share common patterns:
- Obfuscated names (single characters, whitespace, dots)
- Actions that hide emails rather than delete them (Conversation
History, RSS Feeds folders — less obvious than Deleted Items)
- Targeting senders that would alert the user to a compromise

REQUIREMENTS:
- ExchangeOnlineManagement module: Install-Module ExchangeOnlineManagement
- Connect first: Connect-ExchangeOnline -UserPrincipalName [email protected]
- Exchange Administrator role or Global Administrator
- Unified audit log must be enabled for audit log functions
(enabled by default on M365 Business/Enterprise plans)

.NOTES
Get-InboxRule does not return a creation date — that must be obtained
from the unified audit log (Search-UnifiedAuditLog).

Hidden rules (created via MAPI/Outlook desktop client) are not returned
by Get-InboxRule without the -IncludeHidden flag. Always use -IncludeHidden
during compromise investigations.

API-created rules (via Graph API or EWS) may appear in the audit log
as "UpdateInboxRules" rather than "New-InboxRule". Include that operation
in any audit log queries.
#>

#Requires -Modules ExchangeOnlineManagement
Set-StrictMode -Version Latest


#region ─── Single Mailbox Investigation ─────────────────────────────────────

function Get-MailboxRules {
<#
.SYNOPSIS
Retrieves all inbox rules for a specific mailbox, including hidden rules.
.DESCRIPTION
Uses -IncludeHidden to surface rules created via Outlook desktop client
(MAPI) that are invisible to standard admin tools and the Outlook web UI.
Excludes the built-in "Junk E-Mail Rule" which is present on every mailbox.
.PARAMETER Mailbox
UPN or primary SMTP address of the mailbox to inspect.
.EXAMPLE
Get-MailboxRules -Mailbox "[email protected]"
#>
param(
[Parameter(Mandatory = $true)]
[string]$Mailbox
)

Write-Host "`n[*] Retrieving inbox rules for: $Mailbox" -ForegroundColor Cyan

# Standard rules — what most admin tools show
$Standard = Get-InboxRule -Mailbox $Mailbox -ErrorAction SilentlyContinue
Write-Host " Standard rules visible to admin tools: $($Standard.Count)"

# All rules including those hidden via MAPI (Outlook desktop client)
$AllRules = Get-InboxRule -Mailbox $Mailbox -IncludeHidden -ErrorAction SilentlyContinue |
Where-Object { $_.Name -ne 'Junk E-Mail Rule' }
Write-Host " Total rules including hidden (excl. Junk E-Mail Rule): $($AllRules.Count)"

$HiddenCount = $AllRules.Count - $Standard.Count
if ($HiddenCount -gt 0) {
Write-Host " [!] $HiddenCount hidden rule(s) detected — not visible in standard admin view" -ForegroundColor Yellow
}

# Output all rules with the fields most relevant to triage
$AllRules | Select-Object `
@{N='Mailbox'; E={ $Mailbox }},
Name,
Enabled,
Priority,
@{N='IsHidden'; E={ $Standard.Name -notcontains $_.Name }},
Description,
MoveToFolder,
MarkAsRead,
DeleteMessage,
ForwardTo,
RedirectTo,
@{N='From'; E={ $_.From -join '; ' }},
@{N='SentTo'; E={ $_.SentTo -join '; ' }},
@{N='SubjectContains';E={ $_.SubjectContainsWords -join '; ' }},
@{N='BodyContains'; E={ $_.BodyContainsWords -join '; ' }} |
Format-List
}


function Get-MailboxForwarding {
<#
.SYNOPSIS
Checks for email forwarding rules on a specific mailbox.
.DESCRIPTION
Attacker-configured forwarding is often set alongside inbox rules.
Checks both mailbox-level forwarding (ForwardingAddress,
ForwardingSmtpAddress) and auto-forwarding state.
.PARAMETER Mailbox
UPN or primary SMTP address of the mailbox to inspect.
.EXAMPLE
Get-MailboxForwarding -Mailbox "[email protected]"
#>
param(
[Parameter(Mandatory = $true)]
[string]$Mailbox
)

Write-Host "`n[*] Checking forwarding configuration for: $Mailbox" -ForegroundColor Cyan

Get-Mailbox -Identity $Mailbox |
Select-Object DisplayName, PrimarySmtpAddress,
ForwardingAddress, ForwardingSmtpAddress,
DeliverToMailboxAndForward |
Format-List

# Also check transport rules (tenant-wide forwarding — requires different perms)
Write-Host "[i] Note: Tenant-level transport rules (mail flow rules) should also be reviewed in the Exchange Admin Center or via Get-TransportRule." -ForegroundColor Gray
}


function Get-MailboxDelegates {
<#
.SYNOPSIS
Checks for unexpected delegate (full access) permissions on a mailbox.
.DESCRIPTION
Attackers may grant themselves or a compromised secondary account
persistent delegate access to a mailbox. This is separate from
inbox rules but is often set during the same compromise session.
.PARAMETER Mailbox
UPN or primary SMTP address of the mailbox to inspect.
.EXAMPLE
Get-MailboxDelegates -Mailbox "[email protected]"
#>
param(
[Parameter(Mandatory = $true)]
[string]$Mailbox
)

Write-Host "`n[*] Checking mailbox permissions (delegates) for: $Mailbox" -ForegroundColor Cyan

Get-MailboxPermission -Identity $Mailbox |
Where-Object {
$_.User -ne 'NT AUTHORITY\SELF' -and
$_.IsInherited -eq $false -and
$_.AccessRights -contains 'FullAccess'
} |
Select-Object Identity, User, AccessRights, IsInherited |
Format-Table -AutoSize
}

#endregion


#region ─── Audit Log: Rule Creation History ─────────────────────────────────

function Get-RuleCreationHistory {
<#
.SYNOPSIS
Queries the unified audit log for inbox rule creation/modification events.
.DESCRIPTION
Get-InboxRule returns current rules only — it does not show when a rule
was created or from what IP address. The unified audit log preserves
this history for 90 days (standard) or up to 1 year (E3/E5 plans).

Covers three operation types:
New-InboxRule — created via Outlook Web App or PowerShell
Set-InboxRule — modified via Outlook Web App or PowerShell
UpdateInboxRules — created/modified via Outlook desktop (MAPI) or Graph API

The ClientIP field in the audit data shows where the rule was created from.
A sign-in from an unexpected country or hosting provider around the same
time is a strong indicator of the compromise window.
.PARAMETER Mailbox
UPN of the target mailbox.
.PARAMETER DaysBack
How many days of audit log history to search. Default: 90 (max for standard).
.EXAMPLE
Get-RuleCreationHistory -Mailbox "[email protected]"
.EXAMPLE
Get-RuleCreationHistory -Mailbox "[email protected]" -DaysBack 30
#>
param(
[Parameter(Mandatory = $true)]
[string]$Mailbox,

[Parameter(Mandatory = $false)]
[int]$DaysBack = 90
)

$StartDate = (Get-Date).AddDays(-$DaysBack)
$EndDate = Get-Date

Write-Host "`n[*] Searching audit log for inbox rule events on: $Mailbox" -ForegroundColor Cyan
Write-Host " Window: $($StartDate.ToString('yyyy-MM-dd')) to $($EndDate.ToString('yyyy-MM-dd'))"
Write-Host " This may take 30-60 seconds..."

$Results = Search-UnifiedAuditLog `
-StartDate $StartDate `
-EndDate $EndDate `
-Operations 'New-InboxRule', 'Set-InboxRule', 'UpdateInboxRules' `
-UserIds $Mailbox `
-ResultSize 1000

if (-not $Results -or $Results.Count -eq 0) {
Write-Host " No inbox rule events found in this window." -ForegroundColor Gray
Write-Host " Verify unified audit logging is enabled: Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled" -ForegroundColor Gray
return
}

Write-Host " Found $($Results.Count) event(s):`n"

$Results | ForEach-Object {
$Data = $_.AuditData | ConvertFrom-Json

# Extract rule name from parameters if present
$RuleName = $null
if ($Data.Parameters) {
$NameParam = $Data.Parameters | Where-Object { $_.Name -eq 'Name' }
if ($NameParam) { $RuleName = $NameParam.Value }
}

# Extract key rule actions from parameters
$MoveToFolder = ($Data.Parameters | Where-Object { $_.Name -eq 'MoveToFolder' })?.Value
$ForwardTo = ($Data.Parameters | Where-Object { $_.Name -in 'ForwardTo','RedirectTo' })?.Value

[PSCustomObject]@{
Timestamp = $_.CreationDate
Operation = $_.Operations
RuleName = $RuleName ?? '(name not captured in log)'
ClientIP = $Data.ClientIP
AppUsed = $Data.ClientInfoString
MoveToFolder= $MoveToFolder
ForwardTo = $ForwardTo
ResultStatus= $Data.ResultStatus
}
} | Format-List

Write-Host "[i] Cross-reference ClientIP values against sign-in logs to confirm the compromise window." -ForegroundColor Gray
}

#endregion


#region ─── Tenant-Wide Proactive Hunt ────────────────────────────────────────

function Find-SuspiciousRules {
<#
.SYNOPSIS
Scans all mailboxes in the tenant for inbox rules matching BEC patterns.
.DESCRIPTION
Designed for proactive periodic hygiene checks, not just incident
response. Flags rules that match one or more of the following patterns:

PATTERN 1 — Obfuscated name
Single character, whitespace-only, or dot/underscore-only names.
Attackers use these to avoid notice during casual review.

PATTERN 2 — Moves to obscure folders
Conversation History and RSS Feeds are rarely checked by users
and are preferred over Deleted Items precisely because the email
technically still exists — just invisibly.

PATTERN 3 — Targets security or financial notification senders
Any rule that processes email FROM senders containing keywords
associated with security platforms or payment services.
Note: This is a broad heuristic — legitimate rules exist that
target these senders. Analyst review is required.

PATTERN 4 — Hidden rules
Rules not visible in standard Get-InboxRule output (created
via MAPI / Outlook desktop client).

.PARAMETER IncludeAll
If specified, returns all rules with pattern matches. By default,
only mailboxes with at least one flagged rule are shown.

.EXAMPLE
Find-SuspiciousRules
Find-SuspiciousRules | Export-Csv "suspicious-rules-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation
#>
param(
[switch]$IncludeAll
)

Write-Host "`n[*] Starting tenant-wide inbox rule scan..." -ForegroundColor Cyan
Write-Host " Retrieving all mailboxes (this may take a moment)..."

$Mailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox
Write-Host " Found $($Mailboxes.Count) mailboxes to scan."
Write-Host " Scanning... (progress shown every 50 mailboxes)`n"

$Findings = [System.Collections.Generic.List[PSCustomObject]]::new()
$Count = 0

foreach ($Mbx in $Mailboxes) {
$Count++
if ($Count % 50 -eq 0) { Write-Host " ... $Count / $($Mailboxes.Count)" }

try {
$Standard = Get-InboxRule -Mailbox $Mbx.PrimarySmtpAddress -ErrorAction SilentlyContinue
$AllRules = Get-InboxRule -Mailbox $Mbx.PrimarySmtpAddress -IncludeHidden -ErrorAction SilentlyContinue |
Where-Object { $_.Name -ne 'Junk E-Mail Rule' }
}
catch {
Write-Verbose "Could not retrieve rules for $($Mbx.PrimarySmtpAddress): $_"
continue
}

foreach ($Rule in $AllRules) {

$Flags = [System.Collections.Generic.List[string]]::new()
$IsHidden = $Standard.Name -notcontains $Rule.Name

# Pattern 1: Obfuscated or minimal rule name
if ($Rule.Name -match '^[\s._\-]{0,3}$' -or $Rule.Name.Length -le 1) {
$Flags.Add("Obfuscated name: '$($Rule.Name)'")
}

# Pattern 2: Moves to low-visibility folders
if ($Rule.MoveToFolder -match 'Conversation History|RSS Feeds') {
$Flags.Add("Moves to low-visibility folder: '$($Rule.MoveToFolder)'")
}

# Pattern 3: Targets security or financial notification senders
$FromSenders = ($Rule.From ?? @()) -join ' '
$SubjWords = ($Rule.SubjectContainsWords ?? @()) -join ' '
if ($FromSenders -match 'security|alert|quarantine|noreply|notification|payment|invoice|transaction|bank|payroll') {
$Flags.Add("Targets security/financial sender keywords: '$FromSenders'")
}

# Pattern 4: Hidden rule
if ($IsHidden) {
$Flags.Add('Rule is hidden (created via MAPI/Outlook desktop — not visible in standard admin view)')
}

# Pattern 5: External forwarding (always flag regardless of other patterns)
if ($Rule.ForwardTo -or $Rule.RedirectTo) {
$Destinations = (($Rule.ForwardTo ?? @()) + ($Rule.RedirectTo ?? @())) -join '; '
$Flags.Add("Forwards/redirects to: $Destinations")
}

if ($Flags.Count -gt 0 -or $IncludeAll) {
$Findings.Add([PSCustomObject]@{
Mailbox = $Mbx.PrimarySmtpAddress
DisplayName = $Mbx.DisplayName
RuleName = $Rule.Name
Enabled = $Rule.Enabled
IsHidden = $IsHidden
MoveToFolder = $Rule.MoveToFolder
MarkAsRead = $Rule.MarkAsRead
DeleteMsg = $Rule.DeleteMessage
ForwardTo = ($Rule.ForwardTo ?? @()) -join '; '
RedirectTo = ($Rule.RedirectTo ?? @()) -join '; '
FromSenders = $FromSenders
SubjWords = $SubjWords
FlagCount = $Flags.Count
Flags = $Flags -join ' | '
})
}
}
}

Write-Host "`n[*] Scan complete. $($Findings.Count) flagged rule(s) found across $($Mailboxes.Count) mailboxes." -ForegroundColor Cyan

if ($Findings.Count -gt 0) {
Write-Host "`n--- FLAGGED RULES ---" -ForegroundColor Yellow
$Findings | Sort-Object FlagCount -Descending | Format-Table `
Mailbox, RuleName, IsHidden, Enabled, MoveToFolder, MarkAsRead, FlagCount, Flags `
-AutoSize -Wrap
}

return $Findings
}


function Get-TenantForwarding {
<#
.SYNOPSIS
Reports all mailboxes with forwarding configured (tenant-wide).
.DESCRIPTION
External forwarding is one of the most common BEC exfiltration methods.
Run this alongside Find-SuspiciousRules as part of periodic hygiene audits.
DeliverToMailboxAndForward = False means mail is forwarded but NOT kept
in the original mailbox — a strong indicator of attacker-configured forwarding.
.EXAMPLE
Get-TenantForwarding
Get-TenantForwarding | Export-Csv "forwarding-audit-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation
#>

Write-Host "`n[*] Scanning for mailboxes with forwarding configured..." -ForegroundColor Cyan

Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox |
Where-Object { $_.ForwardingAddress -or $_.ForwardingSmtpAddress } |
Select-Object DisplayName, PrimarySmtpAddress,
ForwardingAddress, ForwardingSmtpAddress,
DeliverToMailboxAndForward |
Format-Table -AutoSize
}

#endregion


#region ─── Usage Examples ────────────────────────────────────────────────────

<#
QUICK REFERENCE — HOW TO USE THESE FUNCTIONS

─── INCIDENT RESPONSE (single mailbox) ───────────────────────────────────────

# 1. Connect to Exchange Online
Connect-ExchangeOnline -UserPrincipalName [email protected]

# 2. Check all rules including hidden
Get-MailboxRules -Mailbox "[email protected]"

# 3. Check forwarding configuration
Get-MailboxForwarding -Mailbox "[email protected]"

# 4. Check for unexpected delegates
Get-MailboxDelegates -Mailbox "[email protected]"

# 5. Find when rules were created and from what IP
Get-RuleCreationHistory -Mailbox "[email protected]"

─── PROACTIVE HUNT (tenant-wide) ─────────────────────────────────────────────

# Scan all mailboxes and export findings to CSV
Find-SuspiciousRules | Export-Csv "rule-audit-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation

# Check for any mailboxes with forwarding configured
Get-TenantForwarding | Export-Csv "forwarding-audit-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation

─── REMEDIATION (after confirming a rule is malicious) ───────────────────────

# Review the rule first
Get-InboxRule -Mailbox "[email protected]" -Identity "RuleName"

# Remove the specific rule
Remove-InboxRule -Mailbox "[email protected]" -Identity "RuleName" -Confirm:$false

# Remove a hidden rule (must use -IncludeHidden to identify it first, then remove by Identity)
$HiddenRules = Get-InboxRule -Mailbox "[email protected]" -IncludeHidden |
Where-Object { $_.Name -ne "Junk E-Mail Rule" -and (Get-InboxRule -Mailbox "[email protected]").Name -notcontains $_.Name }
$HiddenRules | ForEach-Object { Remove-InboxRule -Mailbox "[email protected]" -Identity $_.RuleIdentity -Confirm:$false }

# After removing malicious rules, revoke all active sessions
# (password reset alone does not invalidate existing OAuth tokens)
Revoke-MgUserSignInSession -UserId "[email protected]"
# Or via Azure AD module:
# Set-AzureADUser -ObjectId "[email protected]" -AccountEnabled $false
# Then re-enable after investigation is complete.

#>

#endregion

Additional Resources