Advanced KQL: Working with Dynamic Data.
The investigation seemed simple.
Agent Foskett had the alert, the user, the device and the timestamp.
But the most important evidence was buried inside a dynamic field full of JSON, arrays and nested properties.
The data was not missing. It was just hidden inside a structure that needed to be parsed, expanded and shaped into useful investigation evidence.

Lesson overview
Learn how to work with KQL dynamic data, JSON objects, arrays and nested Microsoft Defender XDR fields so complex telemetry becomes readable investigation evidence.
Why dynamic data matters
The dynamic data workflow
Step 1 — Identify dynamic-looking fields
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
CloudAppEvents
| where Timestamp > ago(7d)
| project Timestamp,
AccountDisplayName,
ActionType,
Application,
RawEventData,
AdditionalFields
| take 25Step 2 — Parse a JSON field
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
CloudAppEvents
| where Timestamp > ago(7d)
| extend ParsedEvent = parse_json(RawEventData)
| project Timestamp,
AccountDisplayName,
ActionType,
Application,
ParsedEventStep 3 — Extract nested properties
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
CloudAppEvents
| where Timestamp > ago(7d)
| extend ParsedEvent = parse_json(RawEventData)
| extend SourceIP = tostring(ParsedEvent.ClientIP),
UserAgent = tostring(ParsedEvent.UserAgent),
Operation = tostring(ParsedEvent.Operation)
| project Timestamp,
AccountDisplayName,
Application,
SourceIP,
UserAgent,
Operation
| order by Timestamp descStep 4 — Expand arrays into rows
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
AlertEvidence
| where Timestamp > ago(7d)
| summarize EvidenceTypes = make_set(EntityType, 50),
EvidenceRoles = make_set(EvidenceRole, 50),
Devices = make_set(DeviceName, 50)
by AlertId
| mv-expand EvidenceTypes
| project AlertId,
EvidenceType = tostring(EvidenceTypes),
EvidenceRoles,
DevicesStep 5 — Work with AuthenticationDetails
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
EmailEvents
| where Timestamp > ago(7d)
| where isnotempty(AuthenticationDetails)
| extend Auth = parse_json(AuthenticationDetails)
| project Timestamp,
SenderFromAddress,
SenderIPv4,
NetworkMessageId,
AuthenticationDetails,
Auth
| take 50Step 6 — Create investigation-ready columns
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
EmailEvents
| where Timestamp > ago(7d)
| where isnotempty(AuthenticationDetails)
| extend Auth = parse_json(AuthenticationDetails)
| extend SPF = tostring(Auth.SPF),
DKIM = tostring(Auth.DKIM),
DMARC = tostring(Auth.DMARC)
| project Timestamp,
SenderFromAddress,
SenderIPv4,
NetworkMessageId,
SPF,
DKIM,
DMARC,
DeliveryAction
| order by Timestamp descStep 7 — Build a reusable dynamic-data pattern
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let TimeFrame = 7d;
CloudAppEvents
| where Timestamp > ago(TimeFrame)
| extend ParsedEvent = parse_json(RawEventData)
| extend SourceIP = tostring(ParsedEvent.ClientIP),
UserAgent = tostring(ParsedEvent.UserAgent),
Operation = tostring(ParsedEvent.Operation)
| where isnotempty(SourceIP) or isnotempty(Operation)
| project Timestamp,
AccountDisplayName,
Application,
ActionType,
SourceIP,
UserAgent,
Operation
| order by Timestamp descCommon dynamic-data mistakes
Investigation checklist
Related Agent Foskett Academy lessons
Coming next
Final thought
Advanced KQL Working with Dynamic Data
Agent Foskett Academy Lesson 122 teaches security analysts how to work with dynamic data, JSON objects, arrays and nested Microsoft Defender XDR telemetry using KQL.
Learn dynamic data in KQL for Microsoft Defender XDR
This lesson explains parse_json, todynamic, mv-expand, bag_unpack, property accessors and investigation-ready extraction patterns for Microsoft security telemetry.
Microsoft Defender XDR dynamic JSON investigation tutorial
Defenders can use KQL dynamic data techniques to extract evidence from AlertEvidence, CloudAppEvents, AuthenticationDetails and other nested telemetry fields.
