Agent Foskett Academy • Lesson 29 • Classifying Evidence with case()

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.

Agent Foskett Academy lesson explaining how to use case in KQL investigations
Lesson overview

Learn how case() helps defenders create readable labels for investigation results based on conditions and evidence patterns.

Understand case()
Classify suspicious activity
Create readable evidence labels
🎯 case() helps you turn conditions into investigation labels.
Use it when you need to classify events, score evidence or make query output easier for analysts and clients to understand.
Review Lesson 28 →

Why case() matters

The case() function checks conditions in order and returns a value when a condition matches.

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.
Label investigation resultsTurn raw telemetry into readable categories that help analysts understand the evidence faster.
Prioritise suspicious activityCreate simple risk labels based on sender, location, command line, delivery action or sign-in behaviour.
Improve reportingMake query output easier to explain in briefings, reports and client-facing security reviews.

Investigation scenario

Agent Foskett is reviewing Defender XDR and Sentinel telemetry after several suspicious emails, unusual sign-ins and endpoint events were reported.

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

Start by creating a new evidence field that labels email events based on the delivery action.
case-classify-email-delivery.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 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

Use case() to classify senders based on suspicious words, authentication results or external domains.
case-classify-suspicious-senders.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 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

case() is also useful in Sentinel when you want to label sign-ins by result, location or conditional access outcome.
case-classify-signins.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 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 case() function reads each condition from top to bottom.

The first matching condition returns its matching value. If none of the conditions match, the final default value is returned.
ConditionThe rule that is checked, such as DeliveryAction == "Blocked" or ResultType != "0".
Returned labelThe value shown when the condition matches, such as Failed sign-in or Suspicious sender domain.
Default valueThe fallback label used when none of the earlier conditions match.

Step 4 — Score endpoint activity

Use case() to create practical risk labels for process activity during endpoint investigations.
case-score-endpoint-activity.kql
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 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

Email triageClassify delivered, junked, blocked and suspicious emails so phishing results are easier to review.
Identity investigationsLabel sign-ins by failed result, risky identity signal, conditional access outcome or location pattern.
Endpoint huntingScore command lines, filenames and execution paths so suspicious process activity stands out quickly.

Common mistakes

Putting broad checks firstcase() returns the first matching condition, so place the most specific and highest-risk rules first.
Forgetting the default valueAlways include a final fallback label so the output remains readable when no conditions match.
Overloading one labelKeep classifications simple. Too many rules in one field can make the investigation harder to explain.

What you learned

case() creates labelsYou learned how to classify investigation results using ordered conditions and readable output values.
Order mattersYou learned why the most specific conditions should appear before broader fallback conditions.
Labels improve investigationsYou learned how classification fields make Defender XDR and Sentinel evidence easier to triage and report.
Next lesson: Combining Evidence Fields for Better Hunting
Now that you can classify evidence with case(), the next step is combining calculated fields and labels into richer hunting logic.
Back to Academy →

Related Agent Foskett Academy lessons

Creating New Evidence Fields with extend Review how defenders create calculated evidence fields before applying classification logic.
Using has_any to Find Suspicious Text Review how multiple suspicious terms can support classification and triage labels.

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

Classifying Evidence with case()

Agent Foskett Academy Lesson 29 teaches defenders how to use the KQL case() function to classify events, label suspicious activity and create readable investigation output.

Learn KQL case() for Microsoft Defender XDR and Sentinel

This lesson explains how case() supports Microsoft Defender XDR and Microsoft Sentinel investigations by creating labels, risk categories and evidence classifications from raw telemetry.