Defender impersonation not working – Mailflow protection

https://security.microsoft.com/reportsubmission

This builds an exchange rule to take the names out the defender impersonation list and creates a rule

<#
.SYNOPSIS
    Builds/updates an Exchange Online mail flow (transport) rule that quarantines
    external mail impersonating your organization's "protected" VIP display names,
    using the TargetedUsersToProtect list from your Defender for Office 365
    Anti-Phishing policies.

.DESCRIPTION
    This is a backstop for Defender's built-in impersonation protection: it
    quarantines any external message whose 'From' header display name exactly
    matches one of your protected VIPs' names, unless the message genuinely
    came from that person's real mailbox address.

    Logic:
      1. Connects to Exchange Online (if not already connected).
      2. Reads every ENABLED Anti-Phish policy and pulls its TargetedUsersToProtect
         list (format: "Display Name;[email protected]", comma-separated entries).
      3. Merges and de-duplicates the display names and email addresses across
         all enabled policies.
      4. Creates the transport rule if it doesn't exist, or updates it in place
         if a rule with the same name already exists (conditions/exceptions are
         refreshed to match the current protected-user list; other settings such
         as Mode are left untouched on update unless you pass -Force).
      5. The rule is created/left in AUDIT mode (Exchange calls this "Test" mode
         under the hood; the admin center displays it as "Audit"). No mail is
         actually quarantined while in this mode - only match events are logged.
         Pass -Enforce to switch it to enforce mode instead.

.PARAMETER RuleName
    Name of the transport rule to create/update.
    Default: "Quarantine - External Sender Impersonating Protected Names"

.PARAMETER Enforce
    Switch. If specified, sets the rule to Enforce mode instead of Audit/Test mode.
    Omit this to keep the rule safely in audit-only mode.

.PARAMETER Priority
    Rule priority (lower number = higher priority). Default: 3, matching the
    example rule this script is modeled on. Adjust to fit your existing rule set.

.PARAMETER WhatIf
    Preview the protected names/addresses and the rule that would be
    created/updated, without making any changes in Exchange Online.

.EXAMPLE
    .\New-ImpersonationQuarantineRule.ps1 -WhatIf
    Shows what would happen without changing anything.

.EXAMPLE
    .\New-ImpersonationQuarantineRule.ps1
    Creates or updates the rule in Audit mode.

.EXAMPLE
    .\New-ImpersonationQuarantineRule.ps1 -Enforce
    Creates or updates the rule and sets it to Enforce mode (will actually quarantine mail).

.NOTES
    Requires the ExchangeOnlineManagement module and an account with Exchange
    Online / Security & Compliance admin permissions (e.g. Organization
    Management or Security Administrator role).

    Review the -WhatIf output before running for real, and review the rule in
    the Exchange admin center after creation. Audit mode is intentionally the
    default so you can check for false positives (e.g. legitimate external
    senders who happen to share a display name with a protected VIP) before
    switching to Enforce.
#>

[CmdletBinding(SupportsShouldProcess = $true)]
param(
    [string]$RuleName = "Quarantine - External Sender Impersonating Protected Names",
    [switch]$Enforce,
    [int]$Priority = 3
)

$ErrorActionPreference = "Stop"

function Ensure-ExchangeOnlineConnection {
    $connected = $false
    try {
        $null = Get-ConnectionInformation -ErrorAction Stop
        $connected = $true
    } catch {
        $connected = $false
    }

    if (-not $connected) {
        Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan
        if (-not (Get-Module -ListAvailable -Name ExchangeOnlineManagement)) {
            throw "The ExchangeOnlineManagement module is not installed. Install it first with: Install-Module ExchangeOnlineManagement -Scope CurrentUser"
        }
        Import-Module ExchangeOnlineManagement -ErrorAction Stop
        Connect-ExchangeOnline -ShowBanner:$false
    } else {
        Write-Host "Already connected to Exchange Online." -ForegroundColor DarkGray
    }
}

function Get-ProtectedNamesAndAddresses {
    <#
        Pulls TargetedUsersToProtect from every ENABLED Anti-Phish policy and
        returns a de-duplicated list of [pscustomobject]@{ DisplayName; Email }
    #>
    Write-Host "Retrieving enabled Anti-Phish policies..." -ForegroundColor Cyan
    $policies = Get-AntiPhishPolicy | Where-Object { $_.Enabled -eq $true }

    if (-not $policies -or $policies.Count -eq 0) {
        Write-Warning "No enabled Anti-Phish policies were found. Nothing to build the rule from."
        return @()
    }

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

    foreach ($policy in $policies) {
        Write-Host "  Policy: $($policy.Identity)" -ForegroundColor DarkGray
        $entries = $policy.TargetedUsersToProtect
        if (-not $entries) { continue }

        foreach ($entry in $entries) {
            # Each entry is typically formatted as "Display Name;[email protected]"
            $parts = $entry -split ';'
            if ($parts.Count -ge 2) {
                $name  = $parts[0].Trim()
                $email = $parts[1].Trim()
            } else {
                # Fallback: some tenants store just the display name or just the address
                $name  = $entry.Trim()
                $email = $null
            }

            if ([string]::IsNullOrWhiteSpace($name)) { continue }

            $protected.Add([pscustomobject]@{
                DisplayName = $name
                Email       = $email
            })
        }
    }

    # De-duplicate by DisplayName+Email pair (case-insensitive)
    $unique = $protected |
        Group-Object { "$($_.DisplayName.ToLower())|$($_.Email.ToLower())" } |
        ForEach-Object { $_.Group[0] }

    return $unique
}

# --- Main ---

Ensure-ExchangeOnlineConnection

$protectedUsers = Get-ProtectedNamesAndAddresses

if ($protectedUsers.Count -eq 0) {
    Write-Warning "No protected users found across enabled Anti-Phish policies. Exiting without changes."
    return
}

$displayNames = $protectedUsers |
    Select-Object -ExpandProperty DisplayName -Unique |
    Where-Object { $_ }

$emailAddresses = $protectedUsers |
    Select-Object -ExpandProperty Email -Unique |
    Where-Object { $_ }

Write-Host "`nFound $($displayNames.Count) unique protected display name(s) and $($emailAddresses.Count) associated address(es):" -ForegroundColor Green
$protectedUsers | Sort-Object DisplayName | Format-Table DisplayName, Email -AutoSize

$modeDescription = if ($Enforce) { "Enforce (will actually quarantine matching mail)" } else { "Audit/Test (logs matches only, no mail is quarantined)" }
Write-Host "`nRule to be created/updated:" -ForegroundColor Green
Write-Host "  Name          : $RuleName"
Write-Host "  Priority      : $Priority"
Write-Host "  Mode          : $modeDescription"
Write-Host "  Condition     : 'From' header contains one of the protected display names"
Write-Host "                  AND sender is from outside the organization"
Write-Host "  Exception     : sender's address contains one of the protected users' real addresses"
Write-Host "  Action        : Deliver the message to the hosted quarantine"

if ($WhatIfPreference) {
    Write-Host "`n-WhatIf specified: no changes were made in Exchange Online." -ForegroundColor Yellow
    return
}

$existingRule = $null
try {
    $existingRule = Get-TransportRule -Identity $RuleName -ErrorAction Stop
} catch {
    $existingRule = $null
}

$ruleMode = if ($Enforce) { "Enforce" } else { "Test" }  # "Test" mode = "Audit" in the admin center UI

$ruleParams = @{
    Name                          = $RuleName
    HeaderContainsMessageHeader   = "From"
    HeaderContainsWords           = $displayNames
    FromScope                     = "NotInOrganization"
    ExceptIfFromAddressContainsWords = $emailAddresses
    Quarantine                    = $true
    Mode                          = $ruleMode
    Priority                      = $Priority
    RuleErrorAction               = "Ignore"
    Comments                      = "Backstop for Defender impersonation protection: quarantines external mail whose display name exactly matches a protected VIP name, even when Defender's own confidence scoring lets it through. Auto-generated/updated by New-ImpersonationQuarantineRule.ps1 from enabled Anti-Phish policies' TargetedUsersToProtect lists on $(Get-Date -Format 'yyyy-MM-dd')."
}

if ($existingRule) {
    Write-Host "`nExisting rule '$RuleName' found - updating it in place." -ForegroundColor Cyan
    if ($PSCmdlet.ShouldProcess($RuleName, "Update transport rule")) {
        Set-TransportRule @ruleParams
        Write-Host "Rule updated successfully." -ForegroundColor Green
    }
} else {
    Write-Host "`nNo existing rule named '$RuleName' - creating a new one." -ForegroundColor Cyan
    if ($PSCmdlet.ShouldProcess($RuleName, "Create transport rule")) {
        New-TransportRule @ruleParams
        Write-Host "Rule created successfully." -ForegroundColor Green
    }
}

Write-Host "`nDone. Review the rule in the Exchange admin center (Mail flow > Rules) before relying on it," -ForegroundColor Yellow
Write-Host "and keep it in Audit mode until you've confirmed there are no false positives." -ForegroundColor Yellow
(No Ratings Yet)