Advanced KQL: Building Enterprise Hunting Queries.
The first query found a suspicious sign-in.
The second query found PowerShell. The third query found an email. The fourth query found a cloud application event.
Each query was useful, but none of them told the whole story.
Agent Foskett knew enterprise hunting was different. In a large environment, the best investigations do not stop at one table. They connect identity, endpoint, email and cloud evidence into one scalable hunting workflow.
Lesson overview
Learn how to build enterprise-scale hunting queries that correlate Microsoft Defender XDR telemetry across identity, endpoint, email, cloud applications and alert evidence.
Why enterprise hunting queries matter
The enterprise hunting mindset
Step 1 — Define the investigation scope
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
let Lookback = 14d;
let TargetUser = "user@contoso.com";
let TargetDevice = "device01.contoso.com";
let StartTime = ago(Lookback);
let InvestigationWindow =
range Timestamp from StartTime to now() step 1d;
InvestigationWindow
| project TimestampStep 2 — Build an identity evidence set
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let Lookback = 14d;
let TargetUser = "user@contoso.com";
let IdentityEvidence =
IdentityLogonEvents
| where Timestamp > ago(Lookback)
| where AccountUpn =~ TargetUser
| project Timestamp,
Source = "IdentityLogonEvents",
Account = AccountUpn,
DeviceName,
EvidenceType = ActionType,
EvidenceDetail = strcat(IPAddress, " ", Location, " ", FailureReason);
IdentityEvidence
| order by Timestamp ascStep 3 — Add endpoint process evidence
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let Lookback = 14d;
let TargetDevice = "device01.contoso.com";
let EndpointEvidence =
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where DeviceName =~ TargetDevice
| where FileName in~ ("powershell.exe", "cmd.exe", "rundll32.exe", "mshta.exe")
or ProcessCommandLine has_any ("-enc", "downloadstring", "iex", "http", "bypass")
| project Timestamp,
Source = "DeviceProcessEvents",
Account = InitiatingProcessAccountName,
DeviceName,
EvidenceType = FileName,
EvidenceDetail = ProcessCommandLine;
EndpointEvidence
| order by Timestamp ascStep 4 — Add email evidence
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let Lookback = 14d;
let TargetUser = "user@contoso.com";
let EmailEvidence =
EmailEvents
| where Timestamp > ago(Lookback)
| where RecipientEmailAddress =~ TargetUser
| where ThreatTypes has_any ("Phish", "Malware", "Spam")
or DeliveryAction in~ ("Delivered", "Junked", "Quarantined")
| project Timestamp,
Source = "EmailEvents",
Account = RecipientEmailAddress,
DeviceName = "",
EvidenceType = DeliveryAction,
EvidenceDetail = strcat(SenderFromAddress, " | ", Subject, " | ", NetworkMessageId);
EmailEvidence
| order by Timestamp ascStep 5 — Correlate email clicks
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let Lookback = 14d;
let TargetUser = "user@contoso.com";
EmailEvents
| where Timestamp > ago(Lookback)
| where RecipientEmailAddress =~ TargetUser
| project NetworkMessageId, EmailTime = Timestamp, SenderFromAddress, Subject
| join kind=leftouter (
UrlClickEvents
| where Timestamp > ago(Lookback)
| where AccountUpn =~ TargetUser
| project NetworkMessageId, ClickTime = Timestamp, Url, ActionType, IsClickedThrough
) on NetworkMessageId
| project EmailTime, ClickTime, AccountUpn = TargetUser, SenderFromAddress, Subject, Url, ActionType, IsClickedThrough
| order by EmailTime ascStep 6 — Add cloud application activity
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let Lookback = 14d;
let TargetUser = "user@contoso.com";
let CloudEvidence =
CloudAppEvents
| where Timestamp > ago(Lookback)
| where AccountDisplayName =~ TargetUser or AccountId =~ TargetUser
| project Timestamp,
Source = "CloudAppEvents",
Account = AccountDisplayName,
DeviceName = tostring(RawEventData.DeviceName),
EvidenceType = ActionType,
EvidenceDetail = strcat(Application, " | ", ObjectName, " | ", IPAddress);
CloudEvidence
| order by Timestamp ascStep 7 — Build a combined enterprise timeline
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let Lookback = 14d;
let TargetUser = "user@contoso.com";
let IdentityEvidence = IdentityLogonEvents
| where Timestamp > ago(Lookback)
| where AccountUpn =~ TargetUser
| project Timestamp, Source="IdentityLogonEvents", Account=AccountUpn, DeviceName, EvidenceType=ActionType, EvidenceDetail=strcat(IPAddress, " ", Location);
let EmailEvidence = EmailEvents
| where Timestamp > ago(Lookback)
| where RecipientEmailAddress =~ TargetUser
| project Timestamp, Source="EmailEvents", Account=RecipientEmailAddress, DeviceName="", EvidenceType=DeliveryAction, EvidenceDetail=strcat(SenderFromAddress, " | ", Subject);
let CloudEvidence = CloudAppEvents
| where Timestamp > ago(Lookback)
| where AccountDisplayName =~ TargetUser
| project Timestamp, Source="CloudAppEvents", Account=AccountDisplayName, DeviceName="", EvidenceType=ActionType, EvidenceDetail=strcat(Application, " | ", ObjectName);
union IdentityEvidence, EmailEvidence, CloudEvidence
| order by Timestamp ascStep 8 — Join alerts to evidence
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
let Lookback = 14d;
AlertInfo
| where Timestamp > ago(Lookback)
| project AlertId, AlertTime = Timestamp, Title, Severity, Category
| join kind=leftouter (
AlertEvidence
| where Timestamp > ago(Lookback)
| project AlertId, EntityType, EvidenceRole, DeviceName, AccountName, FileName, RemoteUrl
) on AlertId
| project AlertTime, Title, Severity, Category, EntityType, EvidenceRole, DeviceName, AccountName, FileName, RemoteUrl
| order by AlertTime descStep 9 — Summarise enterprise exposure
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let Lookback = 30d;
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where FileName in~ ("powershell.exe", "cmd.exe", "rundll32.exe", "mshta.exe")
| where ProcessCommandLine has_any ("-enc", "downloadstring", "iex", "http", "bypass", "hidden")
| summarize Events = count(),
Devices = dcount(DeviceName),
Users = dcount(InitiatingProcessAccountName),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
ExampleCommand = any(ProcessCommandLine)
by FileName
| order by Events descStep 10 — Build a reusable enterprise hunting query
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let Lookback = 14d;
let SuspiciousUsers =
IdentityLogonEvents
| where Timestamp > ago(Lookback)
| where Location !in ("Australia", "New Zealand") or FailureReason has "MFA"
| summarize IdentityEvents=count(), FirstIdentity=min(Timestamp), LastIdentity=max(Timestamp) by AccountUpn;
let SuspiciousProcesses =
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where ProcessCommandLine has_any ("-enc", "downloadstring", "iex", "bypass")
| summarize ProcessEvents=count(), Devices=dcount(DeviceName), ExampleCommand=any(ProcessCommandLine) by Account = InitiatingProcessAccountName;
SuspiciousUsers
| join kind=leftouter SuspiciousProcesses on $left.AccountUpn == $right.Account
| extend TotalSignal = IdentityEvents + coalesce(ProcessEvents, 0)
| project AccountUpn, TotalSignal, IdentityEvents, ProcessEvents, Devices, FirstIdentity, LastIdentity, ExampleCommand
| order by TotalSignal descEnterprise hunting design checklist
Common enterprise hunting patterns
Related Agent Foskett Academy lessons
Coming next
Final thought
Advanced KQL Building Enterprise Hunting Queries in Microsoft Defender XDR
Agent Foskett Academy Lesson 129 teaches defenders how to build enterprise-scale KQL hunting queries across Microsoft Defender XDR by correlating identity, endpoint, email, cloud application and alert telemetry.
Learn enterprise KQL threat hunting for Microsoft security investigations
Enterprise hunting queries help security analysts normalise evidence, combine multiple Defender XDR tables, optimise query logic and create scalable workflows for large Microsoft environments.
Microsoft Defender XDR enterprise hunting query tutorial
This lesson explains how to scope hunts, build evidence sets, join alerts, union timelines, summarise exposure and produce investigation-ready output for Microsoft Defender XDR threat hunting.
