Classifying Evidence with case()
Raw investigation results are useful, but they are not always easy to read quickly.
Defenders often need to label events as suspicious, normal, risky, internal, external, noisy or high priority before the evidence can be understood clearly.
This is where case() becomes useful. It allows analysts to create readable classification fields based on conditions inside the query.
In this Agent Foskett Academy lesson, you will learn how defenders use the KQL case() function to classify Microsoft Defender XDR and Microsoft Sentinel evidence into meaningful investigation labels.
Lesson overview
Learn how case() helps defenders create readable labels for investigation results based on conditions and evidence patterns.
Why case() matters
This helps defenders create clear labels such as High Risk, Suspicious Sender, External Login, After Hours Activity or Normal Activity.
Instead of reading raw values one row at a time, analysts can create a new classification field and quickly understand what each result means.
Investigation scenario
The raw results show many events, but the analyst needs a cleaner way to explain which rows need urgent attention.
By using case() with extend, each row can be given a readable investigation label before the final results are reviewed.
Step 1 — Classify email delivery actions
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
EmailEvents | where Timestamp > ago(7d) | extend DeliveryLabel = case( DeliveryAction == "Blocked", "Blocked by protection", DeliveryAction == "Junked", "Delivered to junk", DeliveryAction == "Delivered", "Delivered to mailbox", "Other delivery action" ) | project Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, DeliveryAction, DeliveryLabel
Step 2 — Create risk labels from sender evidence
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
EmailEvents | where Timestamp > ago(30d) | extend SenderDomain = tostring(split(SenderFromAddress, "@")[1]) | extend SenderRisk = case( SenderDomain has_any ("login", "verify", "secure"), "Suspicious sender domain", Subject has_any ("password", "invoice", "payment"), "Suspicious subject theme", DeliveryAction == "Blocked", "Blocked suspicious email", "No obvious sender risk" ) | project Timestamp, SenderFromAddress, SenderDomain, Subject, DeliveryAction, SenderRisk
Step 3 — Classify sign-in behaviour
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
SigninLogs | where TimeGenerated > ago(7d) | extend SignInLabel = case( ResultType != "0", "Failed sign-in", ConditionalAccessStatus == "failure", "Conditional Access failure", RiskState in ("atRisk", "confirmedCompromised"), "Risky identity signal", "Successful sign-in" ) | project TimeGenerated, UserPrincipalName, IPAddress, Location, ResultType, ConditionalAccessStatus, RiskState, SignInLabel
How case() works
The first matching condition returns its matching value. If none of the conditions match, the final default value is returned.
Step 4 — Score endpoint activity
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
DeviceProcessEvents | where Timestamp > ago(7d) | extend LowerCommand = tolower(ProcessCommandLine) | extend ActivityRisk = case( LowerCommand has_any ("encodedcommand", "downloadstring"), "High risk command pattern", FileName in ("powershell.exe", "cmd.exe", "wscript.exe"), "Admin or scripting tool", FolderPath contains "\\temp\", "Executed from temp path", "Standard process activity" ) | project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, ActivityRisk
Common investigation uses
Common mistakes
What you learned
Related Agent Foskett Academy lessons
Continue learning with Using in to Search Multiple Indicators, KQL Threat Hunting Guide and Microsoft Security.
Develop IT. Protect IT. GEMXIT PTY LTD | GEMXIT UK LTD