In this section

DE1.2 Anatomy of a Scheduled Rule

6-8 hours · Module 1 · Free
What you already know

You understand the five rule types, what each produces, and why scheduled rules are the detection engineer's primary tool. This section takes the scheduled rule apart — every configuration parameter, what it controls architecturally, and why each parameter is a design decision that affects detection quality. You need to understand how a rule executes before you build one.

Scenario

You open the Analytics blade, select Create → Scheduled query rule, and the wizard presents five tabs of configuration: General, Set rule logic, Incident settings, Automated response, and Review. You set the name and severity, paste a KQL query, leave everything else on defaults, and click Create. The rule runs. It fires 47 alerts on day one. Fourteen are true positives — but every single alert is missing entity mapping, so the investigation graph is empty. The incident queue shows 47 separate incidents instead of 3 correlated ones because alert grouping is disabled. And 12 of those true positives arrived 6 minutes after the rule executed because of ingestion delay, so the rule missed them on the first pass. Every default you accepted was a design decision you didn't make — and each one degraded the rule's operational value. This section teaches what every parameter does so you make those decisions deliberately.

A scheduled analytics rule is not a query with a timer. It's a pipeline — a sequence of configurable stages that transform raw log data into an actionable incident in the SOC queue. Each stage has parameters that control how the transformation works. Understanding the full pipeline before you configure any individual parameter is what separates a detection engineer from someone who pastes KQL into the wizard and clicks Create.

The pipeline has five stages: the query produces results, the results are evaluated against a threshold, matching results are enriched with entities and custom details, enriched results are packaged into alerts, and alerts are grouped into incidents. Every stage has parameters you control. Every parameter you leave on the default is a design decision with consequences.

Estimated time: 50 minutes.

THE SCHEDULED RULE PIPELINE 1. QUERY KQL against workspace Frequency + lookback 5-min execution delay → result set 2. THRESHOLD Result count vs trigger gt / lt / eq / neq 0–10,000 → fire / suppress 3. ENRICHMENT Entity mapping (≤10) Custom details Dynamic alert content → enriched results 4. EVENT GROUPING All results → 1 alert — OR — Each result → 1 alert → SecurityAlert(s) 5. INCIDENT CREATION Alert grouping criteria Time window (≤24h) Entity / alert detail match → Incident(s) The wizard maps to this pipeline — but the tabs don't match the stages General tab: name, severity, ATT&CK (metadata, not pipeline). Set rule logic tab: stages 1–4. Incident settings tab: stage 5. Every parameter you leave on default is a design decision you didn't make Default threshold = 0 (fire on any result). Default grouping = one incident per alert. Default entity mapping = none.

Figure DE1.2 — The five-stage scheduled rule pipeline. A KQL query produces results. Results are evaluated against a threshold. Matching results are enriched with entity mappings and custom details. Enriched results are packaged into alerts through event grouping. Alerts are grouped into incidents. The analytics rule wizard maps stages 1 through 4 onto the Set rule logic tab and stage 5 onto the Incident settings tab.

Stage 1 — The query and its timing

The query is the detection logic. It's a KQL expression that runs against one or more tables in your Log Analytics workspace. But the query alone is only half the design. The other half is timing — how often the query runs and how far back it looks.

Query frequency (Run query every)

This parameter controls how often Sentinel executes the query. The range is 5 minutes to 14 days. The value you choose determines your detection latency — the worst-case time between an attack occurring and the rule firing an alert.

A rule that runs every 5 minutes has a worst-case detection latency of 5 minutes (plus the 5-minute execution delay Sentinel adds for ingestion, so 10 minutes in practice). A rule that runs every hour has a worst-case latency of 65 minutes. A rule that runs every 24 hours has a worst-case latency of just over 24 hours. For most detection rules, 5 to 15 minutes is the right frequency. Hourly rules are appropriate for behavioral baselines where sub-hour precision doesn't change the response. Daily rules are appropriate for compliance checks and configuration drift monitoring.

The cost of high frequency is compute consumption. A rule that runs every 5 minutes executes 288 times per day. A rule that runs every hour executes 24 times. If your query scans a large table with complex joins, running it every 5 minutes may consume meaningful workspace resources. The results simulation in the wizard helps you evaluate this — it shows what the rule would have produced over the last 50 executions so you can assess both the detection rate and the compute load before deploying.

Lookback period (Lookup data from the last)

This parameter determines how far back in time the query examines. The range is 5 minutes to 14 days. Sentinel enforces a validation rule: the lookback period must be greater than or equal to the query frequency. You cannot create a rule that runs every 30 minutes but only looks at the last 15 minutes — the portal rejects it because the gap between executions would leave events unexamined.

The relationship between frequency and lookback creates three scenarios. When the lookback equals the frequency (15 minutes / 15 minutes), each execution window is contiguous — no overlap, no gap. When the lookback exceeds the frequency (20 minutes / 15 minutes), the windows overlap by 5 minutes. This overlap is intentional for rules that need to catch events with ingestion delay, but it can cause duplicate alerts unless the query uses ingestion_time() deduplication. When the lookback equals the frequency and both are 5 minutes — the minimum — you get the highest detection cadence but the highest sensitivity to ingestion delay.

Here is what this looks like as a concrete configuration:

KQL
// Detection: multiple failed sign-ins followed by success
// Frequency: every 15 minutes. Lookback: 20 minutes.
// The 5-minute overlap catches events delayed by ingestion.
let threshold = 5;
SigninLogs
| where TimeGenerated > ago(20m)
| summarize FailedCount = count(iff(ResultType != 0, 1, int(null))),
    SuccessCount = count(iff(ResultType == 0, 1, int(null))),
    FailedIPs = make_set(IPAddress, 10)
    by UserPrincipalName
| where FailedCount >= threshold and SuccessCount > 0
| project UserPrincipalName, FailedCount, SuccessCount, FailedIPs

This query detects password spray or brute force patterns: 5 or more failed sign-ins followed by at least one success for the same user within a 20-minute window. The query runs every 15 minutes. The lookback is 20 minutes — 5 minutes longer than the frequency — to ensure that events ingested with slight delay are still caught. The summarize aggregates all sign-in attempts per user, so a single user with 8 failures and 1 success produces one result row, not 9.

Notice the architectural subtlety. The threshold of 5 is built into the KQL query itself (where FailedCount >= threshold), not in the rule's alert threshold configuration. This is a design choice. The KQL threshold gives you fine-grained control over the detection logic — you can apply different thresholds to different user types using a watchlist join, or adjust the threshold dynamically based on time of day. The rule's built-in alert threshold operates on the total number of result rows, not on the values within those rows.

Step through the query line by line to see how each operator narrows the data from thousands of sign-in events to the specific users with brute force patterns:

The 5-minute execution delay

Sentinel adds a built-in 5-minute delay to every scheduled rule execution. If your rule is configured to run at 10:00, it actually executes at 10:05. This delay exists because events generated at the source take time to arrive in the workspace — the time between event generation and ingestion varies by data source and can range from seconds to minutes. The 5-minute buffer ensures that most events are ingested before the rule queries for them.

For data sources with longer ingestion latency, the standard approach is to extend the lookback period beyond the frequency and use ingestion_time() to deduplicate:

KQL
// Ingestion delay compensation pattern
// Rule frequency: 5 min. Lookback: 7 min (+2 min for delay).
// ingestion_time() associates each event with the correct window.
let ingestion_delay = 2m;
let rule_look_back = 5m;
CommonSecurityLog
| where TimeGenerated >= ago(ingestion_delay + rule_look_back)
| where ingestion_time() > ago(rule_look_back)

The first where clause extends the lookback to 7 minutes — the 5-minute rule frequency plus 2 minutes of expected ingestion delay. The second where clause uses ingestion_time() to filter events by when they actually arrived in the workspace, not when they were generated. This means each event is associated with exactly one execution window: the window during which the event was ingested. Without the ingestion_time() filter, the extended lookback would cause events to appear in two consecutive execution windows, producing duplicate alerts.

Stage 2 — Alert threshold

After the query runs, Sentinel counts the number of result rows and compares that count to the alert threshold. The threshold has two parts: an operator (greater than, less than, equal to, not equal to) and a value (0 to 10,000).

The default configuration is greater than 0 — the rule fires when the query returns any results. For many detection rules, this is correct. A query that detects shadow copy deletion or inbox rule creation after a risky sign-in should fire on any match. But for detection rules that look for behavioral anomalies or volume-based indicators, the threshold is a tuning parameter.

Consider the brute force query from Stage 1. The KQL already filters for users with 5+ failed attempts followed by a success. The alert threshold in this case can stay at the default (greater than 0) because the detection logic is in the query. But if you wrote a simpler query — all failed sign-ins in the last 15 minutes — and set the threshold to greater than 50, you'd be relying on the threshold to distinguish between normal authentication noise and a brute force attack. The query threshold approach is more precise because it operates on per-user patterns. The alert threshold approach operates on the total result count regardless of which users are involved.

What we see in 90% of template rules

The template comes with a threshold of 0 and a query that's too broad. It fires on any result. The SOC receives 40 alerts per day from the rule. An analyst triages each one individually, finds 36 are benign, marks them as false positives, and moves to the next alert. Nobody adjusts the threshold or tightens the query because "the template came from Microsoft, so the settings must be right." The template was designed as a starting point — a detection hypothesis to validate in your environment. The threshold of 0 was the author's way of saying "we don't know your environment's baseline — you need to calibrate this." Most organizations never do.

An important subtlety: for rules that use summarize, the threshold applies to the number of result rows returned by the query, not to the aggregated values within those rows. If your query summarizes failed logins by user and returns 3 rows (one per user), the threshold evaluates the count of 3 — not the sum of failed logins across those users. This distinction matters for threshold tuning.

Stage 3 — Alert enrichment

When the threshold condition is met, Sentinel generates an alert. But the alert's value depends entirely on how much information it contains. An alert with no entity mapping, no custom details, and a generic name is a row in a table. An alert with mapped entities, surfaced custom details, and a dynamic name that includes the affected user and IP address is an investigable finding.

Three enrichment mechanisms populate the alert:

Entity mapping connects query result fields to Sentinel-recognized entity types — Account, Host, IP, URL, File, Mailbox, Process, and others. You define up to 10 entity mappings per rule, and each mapping links a query field to an entity identifier. Sentinel uses these mappings to populate the investigation graph, correlate alerts across rules, and merge entities across incidents. Entity mapping is important enough that it gets its own section in Section 1.3.

Custom details surface any query field as a key-value pair visible in the alert and incident without drilling into raw events. If your brute force query produces FailedCount, SuccessCount, and FailedIPs, mapping these as custom details means the SOC analyst sees "FailedCount: 8, SuccessCount: 1, FailedIPs: [185.234.xx.xx, 91.215.xx.xx]" on the incident card without opening the alert or running a query. Custom details go into the ExtendedProperties field of the alert schema.

Alert details (dynamic content) customize the alert name, description, severity, and tactics based on query result values. Instead of a generic alert name "Brute force detection," you can template it as "Brute force — {UserPrincipalName} from {FailedCount} IPs" so the alert reads "Brute force — s.chen@contoso.com from 3 IPs." The analyst sees the essential information in the queue without opening the alert.

Here is how entity mapping looks in the rule configuration for the brute force example:

JSON
{
  "entityMappings": [
    {
      "entityType": "Account",
      "fieldMappings": [
        {"identifier": "FullName", "columnName": "UserPrincipalName"}
      ]
    }
  ],
  "customDetails": {
    "FailedAttempts": "FailedCount",
    "SourceIPs": "FailedIPs"
  },
  "alertDetailsOverride": {
    "alertDisplayNameFormat": "Brute force — {{UserPrincipalName}}",
    "alertDescriptionFormat": "{{FailedCount}} failed attempts followed by success"
  }
}

This is the JSON representation of what the wizard configures through the UI. The Account entity mapping uses the FullName identifier with the UserPrincipalName column from the query results. Custom details surface FailedCount and FailedIPs as alert properties. The alert name template injects the actual username. You configure this through the wizard dropdowns — but understanding the underlying structure is what enables you to evaluate whether a template rule's enrichment is adequate or whether you need to add mappings.

Stage 4 — Event grouping

Event grouping determines how query results become alerts. There are two modes:

Group all events into a single alert (the default). All result rows from a single query execution are bundled into one alert. If your brute force query returns 3 users, you get one alert containing all 3. The analyst opens the alert, sees all affected users, and investigates them together. This mode is appropriate when the results represent a single incident — a coordinated brute force campaign targeting multiple accounts from the same IP range, for example.

Alert per result (trigger an alert for each event). Each result row produces its own alert. If your query returns 3 users, you get 3 separate alerts. Each alert contains one user's data. This mode is appropriate when each result row represents an independent finding — 3 unrelated users experiencing brute force attacks from different sources.

The choice affects the SOC's workflow directly. Group-all produces fewer alerts but requires the analyst to parse multiple entities from a single alert. Alert-per-result produces more alerts but each is self-contained. For detection rules that join across tables (like the CHAIN-HARVEST inbox rule detection from Section 1.1), alert-per-result is usually correct — each result row represents a distinct compromised user with their own investigation thread.

Stage 5 — Incident creation and alert grouping

After alerts are generated, Sentinel decides whether to create new incidents or add alerts to existing incidents. This is the Incident settings tab in the wizard.

Incident creation can be enabled or disabled. Disabled means alerts are generated and stored in the SecurityAlert table but no incident appears in the queue. This is useful for rules that generate signals consumed by other rules (a two-stage detection pattern) or for rules you're testing in production before enabling incident creation.

Alert grouping controls how alerts from multiple executions of the same rule are consolidated into incidents. When enabled, you define three parameters: a time window (how long to keep the incident open for new alerts, up to 24 hours), a grouping method (by entities, alert details, custom details, or any combination), and whether to reopen closed incidents when new matching alerts arrive.

Entity-based grouping is the most powerful option. If two alerts from separate executions both map the same Account entity — the same UserPrincipalName — Sentinel groups them into a single incident. The analyst sees one incident with a timeline showing when each alert fired, rather than two separate incidents for the same user.

The grouping limit is 150 alerts per incident. If more than 150 alerts match the grouping criteria, Sentinel creates a new incident with the same details and groups the excess alerts there.

Here is what happens with and without alert grouping for the brute force rule running every 15 minutes:

Without grouping (default): The rule fires at 10:00 and detects s.chen@contoso.com. A new incident is created. The rule fires again at 10:15 and detects s.chen@contoso.com again (the attack is continuing). A second incident is created. By 11:00, the analyst has 4 separate incidents for the same user, the same attack, and the same investigation.

With entity-based grouping (24-hour window): The rule fires at 10:00 and detects s.chen@contoso.com. A new incident is created. At 10:15, the same user is detected again. The new alert is added to the existing incident. By 11:00, the analyst has 1 incident with 4 alerts in its timeline — a complete picture of the attack's progression.

The difference between these two configurations is the difference between an incident queue the SOC can manage and one that drowns them. Section 1.6 teaches alert grouping configuration in detail.

Detection Engineering Principle

A scheduled rule is a five-stage pipeline: query, threshold, enrichment, event grouping, incident creation. The wizard organizes these stages across tabs that don't map neatly to the pipeline — query scheduling and entity mapping share the same tab, while incident grouping gets its own. Understanding the pipeline is what lets you evaluate every parameter as a design decision rather than a setting to leave on default.

Configure a test rule

Open your Sentinel workspace. Navigate to Analytics → +Create → Scheduled query rule. The wizard opens on the General tab.

Configure the following — this is the test rule you'll use throughout this module:

General tab:

  • Name: [TEST] Failed sign-ins followed by success
  • Description: Test rule — multiple failed authentications followed by a successful sign-in for the same user. Module 1 training rule.
  • Severity: Medium
  • MITRE ATT&CK: Credential Access → T1110 (Brute Force)
  • Status: Enabled

Set rule logic tab:

  • Query: Use the brute force detection query from Stage 1 above (paste the KQL).
  • Run query every: 15 minutes
  • Lookup data from the last: 20 minutes
  • Alert threshold: Greater than 0
  • Event grouping: Alert per result
  • Entity mapping: Account → FullName → UserPrincipalName
  • Custom details: FailedAttempts → FailedCount; SourceIPs → FailedIPs

Incident settings tab:

  • Create incidents from alerts: Enabled
  • Alert grouping: Enabled. Group by entities (Account). Time limit: 24 hours. Re-open closed incidents: Disabled.

Click Review and create. If validation passes, click Create.

The rule is now active. In a developer tenant with no sign-in data, it won't fire immediately — that's expected. If you're running against a production workspace, check the incident queue after 30 minutes. In either case, you now have a configured rule that you'll modify through the remaining sections of this module: adding entity mappings in Section 1.3, configuring severity and ATT&CK mapping in Section 1.4, converting it to NRT in Section 1.5, tuning alert grouping in Section 1.6, and documenting it as a full rule specification in Section 1.7.

Next

Section 3 teaches entity mapping — the enrichment mechanism that turns a query result into an investigable finding. You'll add Account, IP, and Host entity mappings to your test rule and see how each mapping changes the investigation experience.

Unlock the Full Course See Full Course Agenda