Reading width
Wide uses the full column for everything, text, diagrams, code, and exercises. Narrow keeps the standard reading width.
Text size
Scales the body text. Headings and code blocks keep their size.
In this section
0.3 The Data This Course Hunts In
Every hunt in this course reads tables, and each table is written by a particular product, holds a particular kind of event and stamps time in a particular column. A hunter who knows which table holds which rows can go straight to the evidence; one who does not spends the first hour of every hunt finding out.
This lesson is the map: the tables the course uses, how many rows each holds in the month, what one row looks like, how the window is applied, and how the same people appear under different names in different tables.
Northgate's data lives in two places. Defender XDR holds what its products see on the laptops, in mail and in identity, and its advanced hunting reads those tables directly. The Sentinel workspace holds the cloud sign-in logs, the audit logs, and the feeds from servers, domain controllers and the firewall.
Microsoft notes that "In the unified Microsoft Defender portal, you can run queries over Microsoft Sentinel tables by onboarding a workspace", which is how Northgate is set up, so one query can read both.
The two platforms differ in one detail that matters in every query: the name of the time column. Defender XDR tables stamp events in Timestamp; Sentinel tables in TimeGenerated. A query that filters on the wrong one fails or, worse, silently filters nothing. Every query in the course names the right column for each table it reads.
The lesson counts and reads ordinary rows only. The month's intrusions sit in these same tables, and the modules that teach the techniques to find them open them one at a time.
Count the Defender XDR tables
Rows in the window for each table the course reads, on TimestampMicrosoft's schema reference describes each Defender XDR table in a line: DeviceProcessEvents holds "Process creation and related events", DeviceNetworkEvents "Network connection and related events", DeviceLogonEvents "Sign-ins and other authentication events on devices", and so on. The nine the course reads, counted:
let start = datetime(2026-02-13 12:00);
let stop = datetime(2026-03-15 12:00);
union
(DeviceProcessEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "DeviceProcessEvents"),
(DeviceNetworkEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "DeviceNetworkEvents"),
(DeviceFileEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "DeviceFileEvents"),
(DeviceLogonEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "DeviceLogonEvents"),
(DeviceEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "DeviceEvents"),
(EmailEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "EmailEvents"),
(EmailUrlInfo
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "EmailUrlInfo"),
(UrlClickEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "UrlClickEvents"),
(IdentityLogonEvents
| where Timestamp >= start and Timestamp < stop
| summarize Rows = count()
| extend Table = "IdentityLogonEvents")
| project Table, Rows
| order by Rows desc
The nine Defender XDR tables, with their rows in the window, largest first:
Table Rows
------------------- -----
EmailUrlInfo 21209
EmailEvents 15188
DeviceProcessEvents 9394
DeviceNetworkEvents 5884
DeviceFileEvents 4279
DeviceEvents 3573
IdentityLogonEvents 2523
DeviceLogonEvents 1928
UrlClickEvents 1204
65,182 rows across nine tables. The endpoint tables, Device-prefixed, come from Defender for Endpoint on the laptops and the servers that have it. EmailEvents, EmailUrlInfo and UrlClickEvents come from Defender for Office 365: messages delivered or blocked, the links inside them, and the clicks on those links.
IdentityLogonEvents comes from Defender for Identity on the domain controllers, and Microsoft describes it as "Authentication events on Active Directory and Microsoft online services".
A count per table is also the first sanity check on any data source. A table that should hold thousands of rows and holds a dozen has a broken feed; a table that should be quiet and holds millions has a noisy one. Northgate's counts are in proportion to what each table records, which is what a healthy workspace looks like.
The sizes reflect what each table records. EmailUrlInfo writes a row for every link in every message, so it is the largest table even though mail is only one part of the estate. A process row is written every time any program starts on a covered device, so the process and network tables follow.
Click rows exist only when someone follows a link in a message, so they are few. A small table is not a less important one; UrlClickEvents is small, and Module 7's phishing hunts turn on it.
Defender XDR also holds tables this course does not read, about devices, alerts, vulnerabilities and more, and the schema reference lists them all. The nine here are the ones that hold the activity hunts are built on.
A table left out of a hunt is not a table forgotten; it is one whose rows would not answer the question, and part of planning a hunt is saying which tables are in and why.
Two of the nine are worth a note for anyone coming from the endpoint side. DeviceEvents is a catch-all for events that do not fit the other device tables: attack surface reduction audits, access to sensitive processes, driver loads and other actions the sensor records with an ActionType that says what happened.
EmailUrlInfo holds the links extracted from each message, one row per link, which is how a hunt finds every message that carried a given domain without reading the bodies.
The Defender XDR tables are also where the course's endpoint hunts get their precision. Because every process start is a row with its parent, its account and its command line, a hunt can ask very specific questions, such as which programs Word started this month or which accounts ran PowerShell remotely, and get an exact answer.
That precision is what the alert stream cannot offer, and it is the main reason the endpoint tables appear in more of the course's hunts than any others.
Widen The Defender side is counted. Widen to the Sentinel workspace.
Count the Sentinel tables
Rows in the window for each workspace table the course reads, on TimeGeneratedThe workspace collects the logs Defender XDR does not hold. The nine the course reads, counted on their own time column:
let start = datetime(2026-02-13 12:00);
let stop = datetime(2026-03-15 12:00);
union
(SigninLogs
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "SigninLogs"),
(AADNonInteractiveUserSignInLogs
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "AADNonInteractiveUserSignInLogs"),
(AuditLogs
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "AuditLogs"),
(OfficeActivity
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "OfficeActivity"),
(SecurityEvent
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "SecurityEvent"),
(Syslog
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "Syslog"),
(CommonSecurityLog
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "CommonSecurityLog"),
(SecurityAlert
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "SecurityAlert"),
(SecurityIncident
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count()
| extend Table = "SecurityIncident")
| project Table, Rows
| order by Rows desc
The nine Sentinel tables, with their rows in the window, largest first:
Table Rows
------------------------------- -----
OfficeActivity 15960
AADNonInteractiveUserSignInLogs 13332
SigninLogs 8062
Syslog 5244
CommonSecurityLog 2060
SecurityIncident 1262
SecurityEvent 1109
SecurityAlert 698
AuditLogs 521
48,248 rows across nine tables. SigninLogs holds interactive sign-ins and AADNonInteractiveUserSignInLogs the token refreshes and background sign-ins that happen without a person typing. AuditLogs records directory changes, OfficeActivity mailbox and SharePoint operations. SecurityEvent carries Windows Security events from the domain controllers and some servers, Syslog the Linux servers' logs, and CommonSecurityLog the firewall. SecurityAlert and SecurityIncident are the alerts and incidents 0.1 counted.
The two sign-in tables are a common trap. A person who signs in once in the morning produces one interactive row and then, through the day, many non-interactive rows as their applications refresh tokens. A hunt that reads only SigninLogs sees the first and misses the rest, and an attacker using a stolen token appears mostly in the non-interactive table. The capstone lab depends on reading both.
The figure is the map the rest of the course assumes. When a lesson names a table, it is one of these eighteen, and its time column is the one shown on its side. Module 3 goes deeper into each, including the columns that matter most for hunting and the ones that are empty in this tenant.
Each workspace table has its own way of naming things, and learning them is part of learning the data. AuditLogs records who made a change in a JSON column called InitiatedBy and what was changed in another called TargetResources. OfficeActivity names the user in UserId and the source address in ClientIP.
SecurityEvent identifies each Windows event by an EventID, the same number a Windows administrator knows from the event viewer. Syslog carries the Linux process name and the raw message. None of this needs memorizing; Module 3 and each lesson introduce the fields when a hunt first needs them.
The workspace also holds the alert and incident tables, which are a different kind of table from the rest. They record what the detection products concluded, not what happened. A hunt uses them for context and corroboration, as 0.1 did, and never as a substitute for the activity tables underneath.
Check coverage The tables are mapped. Check what one row actually holds.
Read one row field by field
An ordinary process row from a staff laptopA table is easier to hunt in once one of its rows has been read slowly. An ordinary row from a staff laptop on a working Wednesday, a document opened from Outlook in Word:
DeviceProcessEvents
| where DeviceName == "NE-DAVIS-LT" and InitiatingProcessFileName == "OUTLOOK.EXE"
| where Timestamp between (datetime(2026-03-11 11:46) .. datetime(2026-03-11 11:47))
| project Timestamp, DeviceName, ActionType, FileName, ProcessCommandLine,
AccountName, AccountUpn, InitiatingProcessFileName, ReportId
The row's fields, one per line, as the query projected them:
One process row, read field by field
DeviceProcessEvents records each process creation. This row is a person opening an attachment from Outlook in Word.
Each field answers a question a hunter asks. Timestamp says when, in UTC. DeviceName says where. ActionType says what kind of event: ProcessCreated here. FileName and ProcessCommandLine say what started and with what arguments, here Word opening a document from the Public attachments folder.
AccountName and AccountUpn say who, in the on-premises and cloud forms of the same name. InitiatingProcessFileName says what started it, Outlook, which is what makes this an attachment being opened rather than a person launching Word from the Start menu. ReportId identifies the event within the device's report.
The values also show what the row does not say. It does not say whether the document was safe, whether it came from inside or outside the company, or what Word did after it opened.
Those answers are in other rows: the mail table for where the attachment came from, the file table for what Word wrote, the process table again for anything Word started. A hunt assembles a picture from many rows, and each row contributes the few facts it records.
The parent field is the one the course leans on most. Almost every endpoint hunt asks what started a process, because the same program means different things under different parents: Word under Outlook is a person opening an attachment, and PowerShell under Word is something else entirely. Module 5 builds on exactly this.
Reading a row slowly is a habit worth keeping long after this lesson. Many hunting mistakes are misreadings of a single field: an AccountName that is the service the process ran as rather than the person at the keyboard, a LogonType that means a network share rather than a person signing in, an address that belongs to a proxy rather than a client.
The cure is the same each time: before building on a field, read what it holds in a handful of ordinary rows.
Check coverage One row is read. Check how the window is applied to a table.
See the half-open window at work
A table stamped at the window's excluded endThe course's window includes its start and excludes its end: Timestamp at or after 13 February 12:00, and before 15 March 12:00. Most tables are unaffected by the distinction, but one shows it vividly. The directory snapshot, IdentityInfo, counted with and without the window:
IdentityInfo
| summarize Rows = count(),
InsideWindow = countif(TimeGenerated >= datetime(2026-02-13 12:00)
and TimeGenerated < datetime(2026-03-15 12:00)),
Stamped = max(TimeGenerated)
The directory snapshot, counted once in full and once with the course's window applied:
Rows InsideWindow Stamped
---- ------------ ----------------
97 0 2026-03-15 12:00
97 rows, and none inside the window, because every row is stamped 2026-03-15 12:00, exactly the excluded end. A hunt that applies the window to IdentityInfo gets nothing and might conclude the directory is empty. The course therefore queries IdentityInfo without the window, and the lessons that use it say so.
Microsoft writes that "Advanced hunting uses UTC (Coordinated Universal Time) for all data" and that the portal converts results to the viewer's time zone for display. The course writes every datetime in UTC and shows results in UTC, so the window's 12:00 is noon UTC on both dates, and a time in a result can be compared with any other without conversion.
The broader lesson is about reading silence. An empty result has three possible meanings: nothing happened, the query cannot see what happened, or the query is filtering it away. This example is the third kind, and it looks exactly like the first. The course's rule, repeated from Module 4 onward, is that an empty result is not a finding until the query has been shown able to return a row.
The window's two edges have different jobs. The start, inclusive, makes sure the first event of the month is counted once. The end, exclusive, makes sure that two consecutive windows, this month and next, never count the same event twice.
The course uses one window for everything, so the second job rarely matters here, but a hunter who reruns a hunt monthly needs both edges right or will double count at the joins.
Snapshot tables like IdentityInfo are different from event tables in a way that matters beyond the window. An event table grows as things happen; a snapshot describes a state at one moment.
A hunt that asks what an account's job title was when something happened needs the title at that time, and a single snapshot only has the title at the end. In a month this rarely changes the answer, but the record should say which kind of table a fact came from.
Check coverage The window is understood. Check how the platforms name the same people.
Join the same people across platforms
Accounts that sign in to the cloud, matched to logons on endpointsThe cloud and the endpoints name a person differently: SigninLogs uses the full sign-in name, j.smith@ne.com, while DeviceLogonEvents uses the account name alone, j.smith. A join across the platforms has to make the keys match. Staff accounts that signed in, matched to their endpoint logons:
SigninLogs
| where TimeGenerated >= datetime(2026-02-13 12:00)
and TimeGenerated < datetime(2026-03-15 12:00)
| where UserPrincipalName !has "#EXT#"
| summarize SignIns = count() by Account = tostring(split(UserPrincipalName, "@")[0])
| join kind=leftouter (DeviceLogonEvents
| where Timestamp >= datetime(2026-02-13 12:00)
and Timestamp < datetime(2026-03-15 12:00)
| summarize Logons = count() by Account = AccountName) on Account
| summarize Accounts = count(), WithEndpointLogons = countif(Logons > 0),
CloudOnly = countif(isempty(Logons) or Logons == 0)
The staff accounts that signed in, and how many of them also have activity on a covered endpoint:
Accounts WithEndpointLogons CloudOnly
-------- ------------------ ---------
93 16 77
The join itself is a small piece of KQL worth reading. split(UserPrincipalName, "@")[0] takes the part of the sign-in name before the @, which matches the account name the endpoints use. A leftouter join keeps every cloud account, matched or not, so the count of unmatched accounts is as visible as the matched ones. An inner join would have dropped them silently and made the coverage look complete.
93 staff accounts signed in to the cloud; 16 of them also logged on to a device that reports to Defender. The rest are cloud-only in this data: people whose laptops are not among the sensor-equipped devices, or who work only in the browser.
That is a coverage fact as well as a join lesson. For most staff, the course can see what they do in Microsoft 365 but not what runs on their machines, and hunts that need both are possible only for the sixteen.
Key mismatches are the commonest reason a cross-platform join returns too few rows. The cloud name and the account name are the simplest case. Device names are another: the sign-in logs carry a device identifier, the endpoint tables a device name, and the firewall an address.
Addresses are the least reliable key of all, because devices change addresses and several devices can share one. The course shows each case where it first matters and always prefers the most stable key a pair of tables shares.
Microsoft's reference for ResultType notes that "0 indicates success; other values are failures", and the join above counts only accounts, not outcomes. A hunt that joins sign-ins to logons for a purpose, say to find cloud sign-ins with no matching activity on the user's laptop, would also filter on the result, and would need the laptop to be one of the sixteen for the absence to mean anything.
The hunt record
Outcome: provenThe record closes the data hunt. Proven here means each table was shown to hold rows in the window, so its silence in a later hunt means something.
The course reads eighteen tables: nine in Defender XDR on Timestamp, 65,182 rows in the month, and nine in the Sentinel workspace on TimeGenerated, 48,248 rows. A process row records when, where, what, with what arguments, by whom and started by what. The window includes its start and excludes its end, and IdentityInfo, stamped at the end, must be queried without it. Joining cloud sign-ins to endpoint logons needs the sign-in name split at @; 16 of 93 staff accounts have endpoint activity.
- Defender XDR: 65,182 rows; DeviceProcessEvents 9,394
- Sentinel: 48,248 rows; SigninLogs 8,062
- Row: WINWORD.EXE under OUTLOOK.EXE on NE-DAVIS-LT
- IdentityInfo: 97 rows, 0 inside the window
- Join: 16 of 93 accounts on endpoints
Counts and one ordinary row. The tables' columns in depth are Module 3's subject.
- Run a first hunt on these tables (0.4)
- Read each table's columns in depth (Module 3)
Counting the tables is also the simplest proof that the data is there. Every count above is above zero, which means every table the course uses holds rows in the window. When a later hunt queries one of these tables and gets nothing, the emptiness is about the question, not about a missing feed, and that is what the record's outcome of proven refers to.
The map will be filled in as the course goes. Module 3 adds each table's columns, its quirks and what it cannot see. Module 4 teaches the language for asking these tables questions properly.
Modules 5 to 7 take the endpoint, identity and mail tables in turn, and Module 8 joins them. What this lesson fixes is the habit: name the table, use its time column, apply the window deliberately, and make the keys match before joining.
Each module adds tables to this map in the order the hunts need them. Module 5 lives mostly in the four endpoint tables and DeviceEvents. Module 6 reads SigninLogs, the non-interactive log, AuditLogs, IdentityLogonEvents and SecurityEvent. Module 7 reads the mail tables and OfficeActivity. Module 8 reads them together, with the firewall and Syslog. By the capstone every table on the map has been used for a real question.
The tables are also a reminder of what a hunter can and cannot ask. A question about who opened a document can be answered from the process table on a covered laptop; the same question about an uncovered laptop cannot be answered at all. Choosing a hunt starts with checking that its question can be answered from the tables available.
Practice
How many rows does each table hold? hands onSteps 1 and 2 counted the tables on their own time columns. Build one count yourself, then try filtering a Sentinel table on Timestamp to see what happens.
Write down which platform each table belongs to and its time column. Then write what the wrong time column did.
Read the evidence
Reading the windowThe check gives you the directory snapshot counted with and without the window. Decide what the counts mean.
Resources
Publishers revise these pages; where a page and this lesson disagree, the page is newer.