Disable, or remove, an inbox rule that forwards, redirects or deletes a user's mail

Build a workflow action that disables an inbox rule in a user's mailbox, keeping it as evidence, or removes it, so MSPs and IT teams can clear a rule an attacker created by asking SuperIT.

Runs in Cloud sandbox (PowerShell)
Uses Microsoft Graph credential with an Exchange Online scope
Suggested approval Require approval from superit:admins for every run
Category Security

Test it with SuperIT before you rely on it

This is an example, not a ready-made action, and its script hasn't been tested end to end in a SuperIT tenant. The Exchange Online credential it depends on hasn't been tested with SuperIT either, so it may not work as written. Before you use it, work through it with SuperIT: ask SuperIT to review the script against your environment and requirements, adjust it together, then test it on a test account or device. Test in the action editor runs the script for real.

Why use an action instead of asking SuperIT

SuperIT can already read a user's inbox rules through your Microsoft 365 connection and spot one that forwards mail outside the company or deletes messages. That connection is read-only, so SuperIT can't remove the rule itself: removing it is a write, which needs an action with an Exchange Online credential. This action disables or removes exactly one named rule, and prints the rule's details first, so the run history keeps a record of what it did.

To find suspicious rules, just ask SuperIT in chat, for example "Does Sam have any inbox rules that forward mail outside the company?". Build this action for the removal.

When to use it

  • "Remove the 'RSS Feeds' rule SuperIT found in Sam's mailbox."
  • "Sam's account was compromised, disable the rule forwarding his mail to that Gmail address."
  • "Delete the inbox rule called '.' from [email protected]."

What the action does

  1. Checks the inputs.
  2. Connects to Exchange Online with the credential.
  3. Finds the rule in the mailbox by its name, or by its Exchange rule identity number. If no rule matches, or more than one rule has that name, it stops and lists the mailbox's rules.
  4. Prints the rule's details: whether it's on, its description, and where it forwards or redirects mail.
  5. Turns the rule off and keeps it, so it stays available as evidence. Only if remove_rule is on does it remove the rule instead.
  6. Prints a summary.

What it doesn't do: it doesn't remove rules it isn't given by name, turn off mailbox-level forwarding (see Set up mail forwarding), restore mail the rule moved or deleted, or sign the attacker out. Lock the account down first with Revoke sessions for a compromised account. It doesn't see hidden rules or rules stored only in the Outlook desktop app.

The action uses Exchange Online (Disable-InboxRule and Remove-InboxRule), not Microsoft Graph's mail rule API. If Exchange stops the run with a message about other rules in the mailbox, such as rules turned off in the Outlook desktop app, deal with the rule in Outlook on the web or the Exchange admin center instead. Don't add -AlwaysDeleteOutlookRulesBlob to the script unless you mean to delete those Outlook rules too.

Before you begin

  • A Microsoft Graph credential for Exchange Online actions, named M365 Exchange, with the scope https://outlook.office365.com/Exchange.Manage, signed in with an account that holds Exchange Administrator. Link it only to Exchange Online actions. This credential setup hasn't been tested with SuperIT; if the credential won't register or the script can't connect, see Exchange Online tasks.
  • An authorization policy with one rule: Specific groups superit:engineers, superit:admins → Require approval from superit:admins. Don't add an Anyone rule: the script doesn't receive the requester's identity, so an end user could change someone else's rules.
  • The Engineer or Admin role to build the action in Workflows → Actions. Only admins can create actions in chat.

Build it with SuperIT

Create a workflow action called "Remove a suspicious inbox rule".
Runtime: PowerShell in the cloud sandbox. Credential: M365 Exchange
(Exchange Online, connect with Connect-ExchangeOnline -AccessToken).
Inputs:
- mailbox (email, required)
- rule_name (string, required) - the rule's name or Exchange rule identity
- remove_rule (boolean, optional) - remove the rule instead of turning it off
It should find exactly one matching rule with Get-InboxRule, stop and list the
mailbox's rules if none or several match, print the rule's description and
forwarding targets, then Disable-InboxRule (or Remove-InboxRule only when
remove_rule is "true"), and print a summary.
Authorization policy: engineers and admins, every run approved by superit:admins.

SuperIT repeats the brief back, drafts the script, checks it, and creates the action as a draft. Review the script in Workflows → Actions, test it, then publish it.

Or build it yourself

Name Type Required Example
mailbox email Yes [email protected]
rule_name string Yes RSS Feeds
remove_rule boolean No off

The main function at the top lists every step in order; each function below it does one of them, and the last line runs it with the action's inputs.

# Goal: find one named inbox rule in a mailbox in Exchange Online, print its details,
# then turn it off (or remove it only when remove_rule is on), and output what was done.
$token = $env:CREDENTIAL_M365_EXCHANGE_TOKEN

function Invoke-InboxRuleRemoval {
    param([string]$Mailbox, [string]$RuleName, [string]$RemoveRule)

    $Mailbox = "$Mailbox".Trim()
    $RuleName = "$RuleName".Trim()
    Assert-Inputs -Mailbox $Mailbox -RuleName $RuleName

    Connect-ExchangeWithToken
    try {
        $rules = Get-InboxRules -Mailbox $Mailbox
        $rule = Select-TargetRule -Mailbox $Mailbox -Rules $rules -RuleName $RuleName
        Write-RuleDetails -Mailbox $Mailbox -Rule $rule
        # Disabling is the default because it keeps the rule as evidence for the investigation.
        if ($RemoveRule -eq 'true') {
            Remove-SuspiciousRule -Mailbox $Mailbox -Rule $rule
            $action = 'removed'
        } else {
            Disable-SuspiciousRule -Mailbox $Mailbox -Rule $rule
            $action = 'disabled'
        }
        Write-RemovalSummary -Mailbox $Mailbox -Rule $rule -Action $action
    } finally {
        Disconnect-ExchangeOnline -Confirm:$false
    }
}

function Assert-Inputs {
    param([string]$Mailbox, [string]$RuleName)

    if (-not $token) { throw "Link the M365 Exchange credential to this action." }
    if ($Mailbox -notmatch '^[^@\s]+@[^@\s]+$') { throw "mailbox must be an email address." }
    if (-not $RuleName) { throw "rule_name is required." }
}

function Connect-ExchangeWithToken {
    $upn = Get-TokenUpn
    Import-Module ExchangeOnlineManagement
    try {
        # A delegated token must be paired with the signed-in admin's UPN, read from the token itself.
        Connect-ExchangeOnline -AccessToken $token -UserPrincipalName $upn -ShowBanner:$false
    } catch {
        Write-Error "Failed to connect to Exchange Online as '$upn': $_"
        exit 1
    }
}

function Get-TokenUpn {
    $part = $token.Split('.')[1].Replace('-', '+').Replace('_', '/')
    $part += '=' * ((4 - $part.Length % 4) % 4)
    ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($part)) | ConvertFrom-Json).upn
}

function Get-InboxRules {
    param([string]$Mailbox)

    try {
        @(Get-InboxRule -Mailbox $Mailbox -ErrorAction Stop)
    } catch {
        Write-Error "Failed to read inbox rules for '$Mailbox': $_"
        exit 1
    }
}

function Select-TargetRule {
    param([string]$Mailbox, [object[]]$Rules, [string]$RuleName)

    $found = @($Rules | Where-Object { $_.Name -eq $RuleName -or "$($_.RuleIdentity)" -eq $RuleName })
    if ($found.Count -eq 1) { return $found[0] }

    # Removing the wrong rule destroys evidence, so refuse to guess between duplicates.
    Write-RuleList -Mailbox $Mailbox -Rules $Rules
    if ($found.Count -eq 0) {
        Write-Error "No inbox rule named '$RuleName' in '$Mailbox'."
    } else {
        Write-Error "$($found.Count) rules in '$Mailbox' are named '$RuleName'. Run again with the rule's identity number."
    }
    exit 1
}

function Write-RuleList {
    param([string]$Mailbox, [object[]]$Rules)

    # Write-Host, not Write-Output: this runs inside Select-TargetRule, whose output is captured as the rule.
    Write-Host "=== INBOX RULES IN $Mailbox ==="
    $Rules | ForEach-Object { Write-Host "Rule : $($_.Name) (identity $($_.RuleIdentity), enabled $($_.Enabled))" }
}

function Write-RuleDetails {
    param([string]$Mailbox, [object]$Rule)

    $targets = @($Rule.ForwardTo) + @($Rule.ForwardAsAttachmentTo) + @($Rule.RedirectTo) | Where-Object { $_ }
    Write-Output "=== RULE FOUND ==="
    Write-Output "Mailbox      : $Mailbox"
    Write-Output "Rule         : $($Rule.Name) (identity $($Rule.RuleIdentity))"
    Write-Output "Enabled      : $($Rule.Enabled)"
    Write-Output "Sends mail to: $(if ($targets) { $targets -join ', ' } else { 'nobody' })"
    Write-Output "Deletes mail : $($Rule.DeleteMessage)"
    Write-Output "Description  : $("$($Rule.Description)".Trim() -replace '\s+', ' ')"
}

function Remove-SuspiciousRule {
    param([string]$Mailbox, [object]$Rule)

    try {
        # -ErrorAction Stop: Exchange cmdlets run in their own module and ignore the script's $ErrorActionPreference.
        Remove-InboxRule -Identity "$($Rule.RuleIdentity)" -Mailbox $Mailbox -Confirm:$false -ErrorAction Stop
    } catch {
        Write-Error "Failed to remove rule '$($Rule.Name)' from '$Mailbox': $_"
        exit 1
    }
}

function Disable-SuspiciousRule {
    param([string]$Mailbox, [object]$Rule)

    try {
        Disable-InboxRule -Identity "$($Rule.RuleIdentity)" -Mailbox $Mailbox -Confirm:$false -ErrorAction Stop
        $after = Get-InboxRule -Identity "$($Rule.RuleIdentity)" -Mailbox $Mailbox -ErrorAction Stop
    } catch {
        Write-Error "Failed to disable rule '$($Rule.Name)' in '$Mailbox': $_"
        exit 1
    }
    if ($after.Enabled) {
        Write-Error "Rule '$($Rule.Name)' in '$Mailbox' is still enabled after Disable-InboxRule."
        exit 1
    }
}

function Write-RemovalSummary {
    param([string]$Mailbox, [object]$Rule, [string]$Action)

    Write-Output "=== INBOX RULE $($Action.ToUpper()) ==="
    Write-Output "Mailbox : $Mailbox"
    Write-Output "Rule    : $($Rule.Name)"
    # $script: so the runner's wrapper sees it; a plain assignment stays local to this function.
    $script:result = @{ mailbox = $Mailbox; rule = "$($Rule.Name)"; rule_identity = "$($Rule.RuleIdentity)"; action = $Action }
}

Invoke-InboxRuleRemoval -Mailbox $ctx.inputs.mailbox -RuleName $ctx.inputs.rule_name `
    -RemoveRule $ctx.inputs.remove_rule

Test it

Test runs for real

Test skips authorization and approval, uses the real M365 Exchange credential, and really turns off or removes the rule. Use a test mailbox.

The script is untested, so treat your first test runs as debugging, and don't publish the action until a test run does exactly what you expect.

  1. Sign in to Outlook on the web as a test user and create an inbox rule called Test forward that forwards mail to an internal test address.
  2. On the action, select Test. Enter the test mailbox and Test forward, and leave remove_rule off.
  3. Read the This is not a dry run. warning, tick "I understand this is a real execution that may change real systems." and select Start test run.
  4. Check Stdout shows the rule's forwarding target, and that the rule is turned off in Outlook on the web.
  5. Test again with remove_rule on, and check the rule is gone.

Use it

Publish the action. In an incident, an engineer first asks SuperIT to look, for example "Check Sam's inbox rules for anything suspicious". SuperIT reads the rules and reports any that forward, redirect or delete mail. The engineer then asks "Turn off the 'RSS Feeds' rule in Sam's mailbox", and the run waits for an admin to approve it.

By default the rule is turned off and kept for the investigation. Once the investigation no longer needs it, run the action again with remove_rule on. Ask SuperIT to check the rules again afterwards to confirm the change.

For MSPs, publish to managed teams from the Publish dialog; an engineer or admin in each team authorizes its own M365 Exchange credential.

Variations

  • Remove every rule that forwards or redirects mail outside your domains in one run, after printing them all. Keep approval on that version, because it can remove rules the user set up on purpose.
  • Add -IncludeHidden to Get-InboxRule to find rules hidden from Outlook.
  • When ticket automation investigates a phishing ticket, it can recommend this action. Leave Run automatically off so an engineer confirms the rule before it goes.