Using extract() for Flexible Pattern Matching
Sometimes the evidence is easy to see, but difficult to separate.
A URL might be buried inside a command line. An IP address might be hiding inside a longer message. A ticket number might be mixed into an email subject.
In the previous lessons, you learned how to find patterns and extract structured evidence. But not every field follows a simple format.
In this Agent Foskett Academy lesson, you will learn how defenders use the KQL extract() function to pull useful values from flexible patterns using regular expressions.
Lesson overview
Learn how extract() helps defenders pull evidence from command lines, URLs, subjects and flexible text patterns.
Why extract() matters
This is useful when the data has a recognisable pattern, but the surrounding text is not always the same.
Instead of relying on fixed separators, extract() lets you search for the value pattern itself.
Investigation scenario
Some evidence appears in email subjects. Some appears in URLs. Some appears inside command lines. The patterns are similar, but the surrounding text keeps changing.
The analyst uses extract() to create clean investigation columns from those messy fields.
Step 1 — Extract an IP address from a URL or text field
- 1
- 2
- 3
- 4
- 5
- 6
- 7
DeviceNetworkEvents | where Timestamp > ago(7d) | extend ExtractedIP = extract(@"(\d{1,3}(?:\.\d{1,3}){3})", 1, RemoteUrl) | where isnotempty(ExtractedIP) | project Timestamp, DeviceName, ActionType, ExtractedIP, RemoteUrl | sort by Timestamp desc
Step 2 — Extract a domain from a clicked URL
- 1
- 2
- 3
- 4
- 5
- 6
UrlClickEvents | where Timestamp > ago(30d) | extend ClickedDomain = extract(@"https?://([^/]+)", 1, Url) | project Timestamp, AccountUpn, ClickedDomain, Url, ThreatTypes, ActionType | sort by Timestamp desc
Step 3 — Extract a ticket number from an email subject
- 1
- 2
- 3
- 4
- 5
- 6
- 7
EmailEvents | where Timestamp > ago(30d) | extend TicketNumber = extract(@"Ticket[- ]?(\d+)", 1, Subject) | where isnotempty(TicketNumber) | project Timestamp, SenderFromAddress, RecipientEmailAddress, TicketNumber, Subject | sort by Timestamp desc
What extract() does
The first number after the regex tells KQL which capture group to return. Most beginner examples use 1, which means return the first captured value.
Step 4 — Extract a script name from a command line
- 1
- 2
- 3
- 4
- 5
- 6
- 7
DeviceProcessEvents | where Timestamp > ago(7d) | where ProcessCommandLine has_any (".ps1", ".vbs", ".js") | extend ScriptName = extract(@"([^\\\s]+\.(?:ps1|vbs|js))", 1, ProcessCommandLine) | project Timestamp, DeviceName, AccountName, FileName, ScriptName, ProcessCommandLine | sort by Timestamp desc
Step 5 — Extract a suspicious encoded command
- 1
- 2
- 3
- 4
- 5
- 6
- 7
DeviceProcessEvents | where Timestamp > ago(7d) | where ProcessCommandLine has_any ("-enc", "-EncodedCommand") | extend EncodedValue = extract(@"(?i)-(?:enc|encodedcommand)\s+([^\s]+)", 1, ProcessCommandLine) | project Timestamp, DeviceName, AccountName, FileName, EncodedValue, ProcessCommandLine | sort by Timestamp desc
Step 6 — Use extract() with investigation parameters
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
let Lookback = 30d; let UrlPattern = @"https?://([^/]+)"; UrlClickEvents | where Timestamp > ago(Lookback) | extend ClickedDomain = extract(UrlPattern, 1, Url) | where isnotempty(ClickedDomain) | project Timestamp, AccountUpn, ClickedDomain, Url, ThreatTypes, ActionType | sort by Timestamp desc
Investigator notes
Start with a small result set and validate the extracted column before relying on it in joins, summaries or reports.
What you learned
Continue your investigation
Continue learning with Using matches regex, Using has_any, KQL Threat Hunting Guide and Microsoft Security.
Develop IT. Protect IT. GEMXIT PTY LTD | GEMXIT UK LTD