Field note: a fake CEO email, a clean purge, and the leftover hybrid connectors it turned up
A CEO-impersonation email reached five people at a small Microsoft 365 shop. The purge had gotchas, and the message trace exposed connectors to a dead Exchange server.
Staff at a small company started forwarding the same message: the CEO needs a task done, reply from your personal email. It came from a random Gmail address using the CEO’s display name, the ordinary kind of impersonation scam. If you want a version of this to hand to office staff, how to spot a scam email covers that side.
It reached five people. Two copies landed in Junk and three failed to deliver. The cleanup was simple on paper, but it hit several command changes along the way, and the message trace turned up something more interesting than the phish: hybrid connectors still pointing at an on-prem Exchange server that had been shut down.
Did anyone reply?
That’s the first question, so I traced outbound mail to the sender’s address. It came back empty.
Get-MessageTraceV2 -RecipientAddress '[email protected]' -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date)
Know the limit on that answer. An empty trace means nobody replied from a company mailbox. This scam asked people to use their personal email, and a trace of your tenant can’t see that.
Use Get-MessageTraceV2 for this, not Get-MessageTrace. Microsoft’s reference says V2 replaces the old cmdlet. The V2 cmdlet searches up to 90 days back, with a maximum of 10 days in any one query.
Blocking the sender
Two Exchange transport rules: one quarantines the exact sender, and one quarantines outside mail with the CEO’s name in the From header. The parameter for the sender pattern is -FromAddressMatchesPatterns, not -SenderAddressMatchesPatterns, which doesn’t exist. It takes a regular expression, so escape the dots. Here’s the shape of the two rules, with placeholder names:
New-TransportRule -Name 'Quarantine impersonation sender' `
-FromAddressMatchesPatterns 'scammer@example\.com' -Quarantine $true
New-TransportRule -Name 'Quarantine outside mail using CEO name' `
-FromScope NotInOrganization -HeaderContainsMessageHeader 'From' `
-HeaderContainsWords 'Jane Owner' -Quarantine $true
The name rule will also catch real outside mail from anyone who shares that name, including the CEO’s own personal address, so someone needs to watch the quarantine.
Purging what was delivered
A compliance search for the sender found the two delivered copies, and a second search found the internal forward. SoftDelete purges removed all three with zero failures.
The search and the purge both need the search-only session, which takes version 3.9.0 or later of the ExchangeOnlineManagement module:
Update-Module ExchangeOnlineManagement
# Open a fresh PowerShell window, then:
Connect-IPPSSession -EnableSearchOnlySession
Then search, check the count, and purge:
New-ComplianceSearch -Name 'Fake CEO mail' -ExchangeLocation All `
-ContentMatchQuery '(From:[email protected])'
Start-ComplianceSearch -Identity 'Fake CEO mail'
# When it finishes, confirm what it found
Get-ComplianceSearch -Identity 'Fake CEO mail' | Format-List
New-ComplianceSearchAction -SearchName 'Fake CEO mail' -Purge -PurgeType SoftDelete
# Read the Results line for the item count, not just the Status
Get-ComplianceSearchAction -Identity 'Fake CEO mail_Purge' | Format-List
The last gotcha is the easy one to miss. A purge action can show Completed with an item count of 0, which means it removed nothing. When that happened, I removed the action and ran it again until the count matched the search:
Remove-ComplianceSearchAction -Identity 'Fake CEO mail_Purge'
SoftDelete leaves the message in the user’s Recoverable Items, where they can still get it back until the retention period ends. The Purge action also removes at most 10 items per mailbox and skips unindexed items. It’s meant for a phish like this, not for cleaning out mailboxes.
The side discovery: mail routed to a server that no longer exists
The trace also showed delivery failures for a few users, with an error along these lines:
DNS domain mail.contoso.com does not exist
Those users were AD-synced MailUser objects with no cloud mailbox, left over from an on-prem Exchange server that had been shut down. A HybridWizard outbound connector was still routing their mail to the dead server, and a matching inbound connector was still there too.
To list the MailUsers:
Get-MailUser -ResultSize Unlimited | Format-Table DisplayName, ExternalEmailAddress
Before touching anything, I wanted to know which of those addresses still mattered. One trace counted the bounces, and another showed who was sending and what. That separated the addresses still getting real vendor mail from the ones only getting marketing.
$failed = Get-MessageTraceV2 -Status Failed -StartDate (Get-Date).AddDays(-9) -EndDate (Get-Date) -ResultSize 5000
$failed | Group-Object RecipientAddress | Sort-Object Count -Descending | Format-Table Count, Name
$failed | Format-Table RecipientAddress, SenderAddress, Subject
Check the MX before you delete a connector
An old-looking connector isn’t automatically a dead one. If a third-party mail filter sits in front of the tenant, one of those connectors could be carrying your mail. So I checked two things first.
Resolve-DnsName contoso.com -Type MX
Get-OutboundConnector | Format-List Name, ConnectorSource, SmartHosts, TlsDomain
Get-InboundConnector | Format-List Name, ConnectorSource, TlsSenderCertificateName
The MX pointed at a third-party email filter. Both connectors had ConnectorSource set to HybridWizard, which is how Microsoft says to identify the ones its Hybrid Configuration Wizard created, and they used a certificate for the old server. Neither one belonged to the filter.
I backed up both, removed them, and sent test mail in both directions. Mail kept flowing.
Get-OutboundConnector 'Outbound connector name' | Format-List * | Out-File .\outbound-connector.txt
Get-InboundConnector 'Inbound connector name' | Format-List * | Out-File .\inbound-connector.txt
Remove-OutboundConnector 'Outbound connector name'
Remove-InboundConnector 'Inbound connector name'
This was for a server that was already off. If yours is still running, stop here and work from Microsoft’s decommissioning guides below, because order matters there and some setups need an Exchange server kept for recipient management.
What’s still open
Not everything was closed when I wrote this up. The sender still has to go on the mail filter’s block list, the client needs to tell me which stale MailUser accounts are former employees so they can be disabled in AD (they’re synced, so that’s where the change belongs), and the old Exchange system objects still need cleaning up.
What I took away from it
- Check the MX before assuming a connector is a leftover. It takes seconds and tells you what’s really carrying mail.
- Never trust “Completed” without the item count. A finished purge that removed nothing looks the same at a glance.
- Trace by sender and FromIP to see the path. With a filter in front of the tenant, that shows whether a message came through the filter.
- Back up connector settings before deleting them. A text file is a small price for knowing exactly what you removed.
- MailUser objects with no mailbox can bounce quietly for years. A message trace is what brought these to the surface.
Sources
- Microsoft Learn: Get-MessageTraceV2: parameters, Status values, 90-day history and 10-day query limits
- Microsoft Learn: Get-MessageTrace: replaced by Get-MessageTraceV2
- Microsoft Learn: New-TransportRule: FromAddressMatchesPatterns, FromScope, the header conditions, and Quarantine
- Microsoft Learn: Connect-IPPSSession: the EnableSearchOnlySession switch
- Microsoft Learn: Start-ComplianceSearch: module version and session requirements
- Microsoft Learn: New-ComplianceSearchAction: Purge, SoftDelete and HardDelete, 10-item limit, unindexed items
- Microsoft Learn: Get-ComplianceSearchAction: the Results line with Item count
- Microsoft Learn: Remove-ComplianceSearchAction: removing a search action
- Microsoft Learn: Find and delete email messages in eDiscovery: the full search-and-purge procedure, required roles, and what SoftDelete does
- Microsoft Learn: Decommission the last Exchange Server after transferring SOA to cloud: identifying HybridWizard connectors and removing them
- Microsoft Learn: How and when to decommission your on-premises Exchange servers in a hybrid deployment: which scenarios still need an on-premises Exchange server
- Microsoft Learn: Manage mail flow using a third-party cloud service with Exchange Online: how connectors work when MX points at a third-party filter
From Small Town Sysadmin
Get new guides by email
Plain-English tech tips, scam and security alerts, and fixes for small offices, whether you're the office manager or the IT pro. No spam, and you can unsubscribe anytime.