Defender XDR: Custom Detections and Automation
The out-of-the-box detections in Microsoft Defender XDR are pretty good, I’ll give them that. But there always comes a point where you need something tailored to your own environment. Every organisation has its own quirks, its own risk profile, and specific threats that the default rules just aren’t going to catch.
I’ve been building a fair few custom detection rules and automated response playbooks in Defender XDR recently, so I wanted to share some of the patterns and KQL queries that have actually worked well. My first attempt at some of these was a bit of a mess — lots of false positives, some queries that ate through the hunting quota in no time — but I’ve refined them since then.
Custom Detection Rules in Defender XDR
Custom detection rules let you proactively hunt for threats using KQL (Kusto Query Language) queries on a schedule. When a query returns results, Defender generates alerts automatically and can kick off response actions.
You create them from the Hunting section in the Microsoft Defender portal. The process goes like this:
- Write and test your KQL query in Advanced Hunting
- Hit “Create detection rule” once you’re happy with what it’s returning
- Fill in the alert details — title, severity, category, MITRE ATT&CK mapping
- Pick a frequency (every 1, 3, 12, or 24 hours)
- Set up any automated response actions you want
That frequency setting matters more than you’d think. Running every hour gives you faster detection but chews through your query quota. For anything high-severity, I run hourly. Lower-priority monitoring? Every 12 or 24 hours does the job.
Full documentation here: Custom detection rules in Microsoft Defender XDR.
Practical KQL Examples
Here are some detection queries I’ve found actually useful in the real world. Treat them as starting points — you’ll definitely want to tweak the thresholds and filters for your own environment.
Detecting Impossible Travel
This one catches users authenticating from two geographically distant locations within a short window. Yes, Entra ID Protection does this natively, but having your own custom detection gives you way more control over the logic and what happens next.
let timeWindow = 30m;
IdentityLogonEvents
| where Timestamp > ago(1d)
| where Application != ""
| where ActionType == "LogonSuccess"
| extend City = tostring(Location.city), Country = tostring(Location.country),
Lat = todouble(Location.latitude), Lon = todouble(Location.longitude)
| where isnotempty(City)
| sort by AccountUpn, Timestamp asc
| serialize
| extend PrevCity = prev(City), PrevCountry = prev(Country),
PrevLat = prev(Lat), PrevLon = prev(Lon),
PrevTimestamp = prev(Timestamp), PrevAccount = prev(AccountUpn)
| where AccountUpn == PrevAccount
| extend TimeDiff = datetime_diff('minute', Timestamp, PrevTimestamp)
| where TimeDiff <= 30 and TimeDiff > 0
| where City != PrevCity
| project Timestamp, AccountUpn, City, Country, PrevCity, PrevCountry, TimeDiff
Mass File Downloads from SharePoint
If a user suddenly downloads a massive number of files in a short window, that’s worth looking at. Could be data exfiltration, could be a compromised account, could just be someone being overly enthusiastic with a migration script — but you want to know about it.
let threshold = 100;
CloudAppEvents
| where Timestamp > ago(1h)
| where ActionType == "FileDownloaded"
| where Application == "Microsoft SharePoint Online"
| summarize DownloadCount = count(), FileList = make_set(ObjectName, 10)
by AccountId, AccountDisplayName, bin(Timestamp, 15m)
| where DownloadCount > threshold
| project Timestamp, AccountDisplayName, DownloadCount, FileList
Suspicious Mailbox Forwarding Rules
This is one I always deploy. Attackers love setting up mailbox forwarding rules to quietly siphon off email data. This query picks up new or modified forwarding rules that send mail to external addresses.
CloudAppEvents
| where Timestamp > ago(1d)
| where ActionType in ("New-InboxRule", "Set-InboxRule", "Set-Mailbox")
| where Application == "Microsoft Exchange Online"
| extend RawData = tostring(RawEventData)
| where RawData has_any ("ForwardTo", "ForwardAsAttachmentTo", "RedirectTo",
"DeliverToMailboxAndForward")
| where RawData !has ".yourdomain.com" // Exclude internal forwarding
| project Timestamp, AccountDisplayName, ActionType,
IPAddress, RawData
Swap .yourdomain.com for your actual domain. You’ll probably want to add exclusions for any known legitimate forwarding addresses too.
Unusual PowerShell Execution on Endpoints
PowerShell downloading things from the internet is one of the oldest tricks in the book for initial access and lateral movement. This query catches it.
DeviceProcessEvents
| where Timestamp > ago(1d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("Invoke-WebRequest", "wget", "curl",
"DownloadString", "DownloadFile", "IEX", "Invoke-Expression",
"Net.WebClient", "Start-BitsTransfer")
| where ProcessCommandLine !has "WindowsUpdate"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine
Automated Investigation and Response
So your detection rules are firing alerts — now what? Automation is the next piece. Defender XDR has built-in automated investigation and response (AIR) that can investigate alerts and take remediation actions without anyone having to lift a finger.
For custom detections, you can wire up these automated actions directly in the rule:
- Isolate device — pulls the device off the network immediately
- Collect investigation package — grabs forensic data from the endpoint
- Run antivirus scan — kicks off a full scan on the device
- Restrict app execution — locks down which apps can run
- Disable user — disables the account in Entra ID
Thing is, you’ve got to match the response severity to how confident you are in the detection. The mailbox forwarding rule? Pretty strong signal — disabling the user might be justified. The PowerShell one? There could be legitimate use cases, so collecting an investigation package first is the smarter move. I learned that one the hard way after auto-isolating a developer’s machine because of a perfectly innocent build script.
Custom Playbooks with Logic Apps
When you need something more sophisticated, you can hook Defender XDR alerts into Azure Logic Apps. That opens up a lot of doors.
A few playbooks I’ve put together recently:
- Pinging a user’s manager via Teams when a high-severity insider risk alert fires, asking them to confirm whether the activity was expected
- Auto-creating ServiceNow tickets for medium-severity alerts that need someone to investigate manually
- Blocking a sender domain in Exchange Online when a phishing campaign gets detected, pulling the domain straight from the alert evidence
- Enriching alerts with HR data — querying an HR system API to check whether the user is in their notice period or recently changed roles
To wire it up, you use the Microsoft Defender XDR connector. The trigger is usually “When a Microsoft Defender XDR incident is created” or “When a Microsoft Defender XDR alert is created.”
The Logic Apps route gives you multi-step workflows with external systems, approval gates, conditional logic — basically a proper SOAR capability without bolting on a separate platform.
A Few Tips From Experience
Run your queries manually first. Seriously. Before creating any detection rule, run the query against a few weeks of data in Advanced Hunting to understand what normal looks like. Nothing kills trust in your alerting faster than a noisy rule that cries wolf every hour.
Map to MITRE ATT&CK properly. It sounds like admin busywork, but categorising your detections well makes it dead easy to spot coverage gaps across the kill chain.
Write down what each rule does and why it exists. Include the expected false positive rate. Future you will be very grateful when you’re staring at an alert at 11pm wondering what past you was thinking.
Keep an eye on your query quota. Custom detections consume Advanced Hunting resources. Go overboard and you’ll find your queries getting throttled — which is not what you want happening to your security detections.
Don’t jump straight to automated responses. Start with alert-only, get a feel for false positive rates, then layer on automation once you trust the detection. You really don’t want to be auto-isolating machines based on a rule that fires twenty times a day.
For more on Advanced Hunting and KQL, the Microsoft Defender XDR hunting documentation is thorough and well-maintained.
If you’ve built any interesting custom detections or playbooks, I’d genuinely like to hear about them. Drop a comment or get in touch — always good to share what’s actually working out there.