Microsoft Sentinel in 2026: Build Your First Analytics Rules and Playbooks
Executive Snapshot
| Category | Recommendation |
|---|---|
| Rule type for custom detections | Scheduled Query Rule (KQL-based) |
| Minimum query frequency | 5 minutes (use sparingly; prefer 1–5 hours) |
| Alert grouping | Group by entity (Account, IP, Host) to reduce noise |
| Playbook trigger | Microsoft Sentinel Incident trigger (not Alert trigger) |
| Automation scope | Use Automation Rules to call Playbooks — keeps logic centralised |
| Cost control | Cap lookback window; avoid search * in scheduled rules |
| Testing approach | Replay synthetic events via SecurityEvent or custom table ingestion |
| 2026 feature to adopt | Unified SOC Platform — Defender XDR + Sentinel incidents in one queue |
TL;DR
- Microsoft Sentinel analytics rules are KQL queries that run on a schedule and generate alerts or incidents — mastering them is the core skill for any Sentinel deployment.
- The four rule types (Scheduled, NRT, Fusion, ML Behavioural) serve different detection needs; Scheduled rules give you the most control.
- Automation Rules act as the orchestration layer — they triage, assign, and call Playbooks without requiring direct Logic App triggers on every alert.
- Alert-to-incident grouping is the single biggest lever to pull for reducing analyst fatigue.
- The 2026 Unified SOC Platform merges Defender XDR and Sentinel incidents — build rules that enrich with Defender entity data from day one.
Introduction
Every Microsoft Sentinel deployment starts with the same question: "We have data — how do we make it do something?" Connectors pipe in terabytes of logs, but raw ingestion is just expensive storage without detection logic sitting on top of it. That detection logic lives in microsoft sentinel analytics rules, and building them well is the difference between a SIEM that pages your team on real threats and one that buries analysts in noise at 2 a.m.
In 2026, Sentinel's Unified SOC Platform has deepened the integration between Defender XDR signals and custom KQL detections, making analytics rules more powerful — and slightly more nuanced — than they were even eighteen months ago. Microsoft has also quietly raised the bar on playbook governance, pushing teams away from ad-hoc Logic App triggers toward centralised Automation Rules.
This tutorial walks you through building your first production-grade scheduled analytics rule from scratch, wiring up an Automation Rule to triage it, and deploying a Playbook that posts enriched incident details to Microsoft Teams. All examples are tested against the June 2026 Sentinel API surface. By the end, you will have a repeatable pattern you can clone for dozens of detections.
Prerequisites
- An active Azure subscription with Microsoft Sentinel enabled on a Log Analytics workspace
- Contributor role on the Sentinel workspace (or the built-in Microsoft Sentinel Contributor RBAC role)
- At least one data connector active and ingesting (the examples use Security Events via AMA and Microsoft Entra ID Sign-in Logs)
- Basic familiarity with KQL (Kusto Query Language) — you do not need to be an expert, but you should be able to read a
whereclause - A Microsoft Teams channel for playbook notifications (any team/channel works for the lab)
- Azure CLI (
az) version 2.60+ or access to the Azure Portal - Optional but recommended: VS Code with the Kusto extension for query authoring
Understanding Analytics Rule Types
Before writing a single line of KQL, pick the right rule type. Getting this wrong wastes compute budget and creates detection gaps.
| Rule Type | How It Works | Best For |
|---|---|---|
| Scheduled | KQL query on a cron schedule | Custom detections, threat hunting promoted to production |
| NRT (Near Real-Time) | Micro-batch every ~1 minute | High-priority alerts where seconds matter (e.g., MFA fatigue attacks) |
| Fusion | Microsoft ML correlates weak signals across connectors | Multi-stage attacks; zero configuration required |
| ML Behavioural Analytics | Baselines entity behaviour, fires on deviation | Insider threat, impossible travel |
For the rest of this tutorial, we focus on Scheduled Query Rules because they expose every tuning knob and teach the mental model that transfers to all other types.
Section 1 — Writing the Detection Query in KQL
1.1 The Detection Scenario
We will detect password spray attacks against Entra ID: a single source IP producing more than 20 failed sign-in attempts across 10 or more distinct accounts within a 1-hour window, followed by at least one success from the same IP.
This is a classic, high-fidelity detection. It combines volume, breadth, and a success indicator — three signals that together almost always mean malicious activity.
1.2 Build the Query
Open your Sentinel workspace → Logs and paste this query. Run it interactively first to validate it returns data (or returns zero rows with no errors if your environment is clean).
// Password Spray Detection — Entra ID Sign-in Logs
// Looks back 1 hour; tune thresholds to your baseline
let LookbackWindow = 1h;
let FailureThreshold = 20;
let AccountThreshold = 10;
let SuccessAfterSpray = true;
let Failures =
SigninLogs
| where TimeGenerated > ago(LookbackWindow)
| where ResultType != "0" // non-zero = failure
| where ResultType !in ("50140", "70044") // exclude expected MFA interrupts
| summarize
FailureCount = count(),
DistinctAccounts = dcount(UserPrincipalName),
Accounts = make_set(UserPrincipalName, 50),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by IPAddress, AppDisplayName;
let Successes =
SigninLogs
| where TimeGenerated > ago(LookbackWindow)
| where ResultType == "0"
| summarize SuccessCount = count(), SuccessAccounts = make_set(UserPrincipalName, 10)
by IPAddress;
Failures
| where FailureCount >= FailureThreshold
| where DistinctAccounts >= AccountThreshold
| join kind=leftouter Successes on IPAddress
| extend SprayWithSuccess = isnotempty(SuccessCount) and SuccessCount > 0
| where SprayWithSuccess == SuccessAfterSpray or SuccessAfterSpray == false
| project
IPAddress,
AppDisplayName,
FailureCount,
DistinctAccounts,
Accounts,
SuccessCount,
SuccessAccounts,
SprayWithSuccess,
FirstSeen,
LastSeen
| order by FailureCount desc
Why
make_setwith a cap? The 50/10 limits onmake_setprevent the field from blowing past the 32 KB alert detail limit that Sentinel enforces. Uncapped sets on busy tenants silently truncate or drop alerts.
1.3 Map MITRE ATT&CK Tactics
Before saving the rule, note which MITRE tactics apply. Sentinel uses these for the incident timeline and Workbook heat maps:
- Tactic: Credential Access
- Technique: T1110.003 — Password Spraying
Section 2 — Creating the Scheduled Analytics Rule
2.1 Via the Azure Portal
- Navigate to Microsoft Sentinel → Analytics → + Create → Scheduled query rule.
- General tab — fill in:
- Name:
CRED-001 — Entra ID Password Spray - Description: Detects password spray pattern: 20+ failures across 10+ accounts with subsequent success from one IP.
- Tactics: Credential Access
- Techniques: T1110.003
- Severity: High
- Name:
- Set rule logic tab — paste the KQL from Section 1.2.
- Set Query scheduling:
- Run every:
1 hour - Lookup data from the last:
1 hour
- Run every:
- Alert threshold: Generate alert when number of query results is
> 0. - Event grouping: Group all events into a single alert (the query already summarises by IP).
- Incident settings tab — ensure Create incidents from alerts triggered by this analytics rule is On.
- Alert grouping: Group alerts into one incident if the
IPAddressentity matches within a5 hourwindow.
2.2 Via Bicep / ARM (GitOps-friendly)
If you manage Sentinel as code — and you should — deploy rules via Bicep. This template is parameterised and ready for a CI/CD pipeline.
// analytics-rule-password-spray.bicep
param workspaceName string
param location string = resourceGroup().location
var ruleName = 'CRED-001-Entra-Password-Spray'
resource sentinelWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = {
name: workspaceName
}
resource analyticsRule 'Microsoft.SecurityInsights/alertRules@2024-09-01' = {
name: guid(ruleName)
kind: 'Scheduled'
scope: sentinelWorkspace
properties: {
displayName: 'CRED-001 — Entra ID Password Spray'
description: 'Detects password spray: 20+ failures across 10+ accounts with a subsequent success from the same IP.'
severity: 'High'
enabled: true
query: loadTextContent('queries/password-spray.kql') // store KQL in a separate file
queryFrequency: 'PT1H'
queryPeriod: 'PT1H'
triggerOperator: 'GreaterThan'
triggerThreshold: 0
suppressionEnabled: false
tactics: [
'CredentialAccess'
]
techniques: [
'T1110.003'
]
eventGroupingSettings: {
aggregationKind: 'SingleAlert'
}
incidentConfiguration: {
createIncident: true
groupingConfiguration: {
enabled: true
reopenClosedIncident: false
lookbackDuration: 'PT5H'
matchingMethod: 'Selected'
groupByEntities: [
'IP'
]
}
}
}
}
Deploy it:
# Deploy the analytics rule via Azure CLI
az deployment group create \
--resource-group rg-sentinel-prod \
--template-file analytics-rule-password-spray.bicep \
--parameters workspaceName=law-sentinel-prod \
--confirm-with-what-if
Section 3 — Entity Mapping (The Step Most Tutorials Skip)
Entity mapping is what turns a raw alert into a rich incident card. Without it, Sentinel cannot link your alert to the Entities pane, UEBA timelines, or Defender XDR identity pages.
Go back to your rule's Set rule logic tab → scroll to Entity mapping → add:
| Entity Type | Identifier | Column |
|---|---|---|
| IP Address | Address | IPAddress |
| Account | UPNSuffix | (leave blank — multi-account) |
| Account | Name | (map from SuccessAccounts if single-account pivot needed) |
For the password spray rule, the IP is the primary pivot entity. Map it correctly and every incident card will show geolocation, threat intelligence enrichment, and related incidents automatically.
Section 4 — Building the Automation Rule
Automation Rules are the traffic cops of Sentinel incident response. They run synchronously when an incident is created or updated, before any Playbook fires. Use them to:
- Auto-assign incidents to the on-call queue
- Add tags (e.g.,
password-spray,needs-MFA-review) - Set incident severity or status
- Call a Playbook for enrichment or notification
4.1 Create the Automation Rule (Portal)
- Sentinel → Automation → + Create → Automation rule
- Name:
AUTO-CRED-001 — Route Password Spray Incidents - Trigger: When incident is created
- Conditions:
- Analytic rule name → Contains →
CRED-001
- Analytic rule name → Contains →
- Actions (in order):
- Assign owner → SOC-Tier1 (or your group)
- Add tags →
password-spray - Run playbook → (we create this next)
- Order:
100(lower numbers run first if you have multiple automation rules)
Section 5 — Deploying the Teams Notification Playbook
Playbooks are Azure Logic Apps with Sentinel-specific connectors. We will use the Microsoft Sentinel Incident trigger (not the legacy Alert trigger) because it receives the full enriched incident object.
5.1 Playbook Logic App Definition (ARM JSON, abbreviated)
Rather than clicking through the Logic Apps designer, deploy this ARM template. It creates the Logic App, sets the Sentinel Incident trigger, and posts an Adaptive Card to Teams.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"PlaybookName": { "type": "string", "defaultValue": "Notify-Teams-PasswordSpray" },
"TeamsChannelId": { "type": "string" },
"TeamsGroupId": { "type": "string" },
"WorkspaceId": { "type": "string" }
},
"resources": [
{
"type": "Microsoft.Logic/workflows",
"apiVersion": "2019-05-01",
"name": "[parameters('PlaybookName')]",
"location": "[resourceGroup().location]",
"identity": { "type": "SystemAssigned" },
"properties": {
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"triggers": {
"Microsoft_Sentinel_incident": {
"type": "ApiConnectionWebhook",
"inputs": {
"host": { "connection": { "name": "@parameters('$connections')['azuresentinel']['connectionId']" } },
"body": { "callback_url": "@{listCallbackUrl()}" },
"path": "/incident-creation"
}
}
},
"actions": {
"Post_adaptive_card_to_Teams": {
"type": "ApiConnection",
"inputs": {
"host": { "connection": { "name": "@parameters('$connections')['teams']['connectionId']" } },
"method": "post",
"path": "/v3/beta/teams/@{encodeURIComponent(parameters('TeamsGroupId'))}/channels/@{encodeURIComponent(parameters('TeamsChannelId'))}/messages",
"body": {
"messageType": "message",
"attachments": [{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{ "type": "TextBlock", "text": "🚨 Sentinel Incident: @{triggerBody()?['object']?['properties']?['title']}", "weight": "Bolder", "size": "Medium" },
{ "type": "FactSet", "facts": [
{ "title": "Severity", "value": "@{triggerBody()?['object']?['properties']?['severity']}" },
{ "title": "Status", "value": "@{triggerBody()?['object']?['properties']?['status']}" },
{ "title": "Incident Number", "value": "@{triggerBody()?['object']?['properties']?['incidentNumber']}" },
{ "title": "Entities", "value": "@{string(triggerBody()?['object']?['properties']?['relatedEntities'])}" }
]}
],
"actions": [{
"type": "Action.OpenUrl",
"title": "Open in Sentinel",
"url": "@{triggerBody()?['object']?['properties']?['incidentUrl']}"
}]
}
}]
}
}
},
"Add_comment_to_incident": {
"type": "ApiConnection",
"runAfter": { "Post_adaptive_card_to_Teams": ["Succeeded"] },
"inputs": {
"host": { "connection": { "name": "@parameters('$connections')['azuresentinel']['connectionId']" } },
"method": "post",
"path": "/Incidents/subscriptions/@{encodeURIComponent(triggerBody()?['workspaceInfo']?['SubscriptionId'])}/resourceGroups/@{encodeURIComponent(triggerBody()?['workspaceInfo']?['ResourceGroupName'])}/workspaces/@{encodeURIComponent(triggerBody()?['workspaceInfo']?['WorkspaceName'])}/comments",
"body": {
"incidentArmId": "@triggerBody()?['object']?['id']",
"message": "Teams notification sent to SOC channel. Automated triage in progress."
}
}
}
}
}
}
}
]
}
5.2 Grant the Playbook Permissions
The Logic App's managed identity needs the Microsoft Sentinel Responder role on the workspace to write comments back to incidents.
# PowerShell — Assign Sentinel Responder to the Playbook's managed identity
$playbookName = "Notify-Teams-PasswordSpray"
$resourceGroup = "rg-sentinel-prod"
$workspaceRgId = "/subscriptions/<sub-id>/resourceGroups/rg-sentinel-prod/providers/Microsoft.OperationalInsights/workspaces/law-sentinel-prod"
# Get the managed identity object ID
$identityObjId = (Get-AzLogicApp -ResourceGroupName $resourceGroup -Name $playbookName).Identity.PrincipalId
# Assign Sentinel Responder
New-AzRoleAssignment `
-ObjectId $identityObjId `
-RoleDefinitionName "Microsoft Sentinel Responder" `
-Scope $workspaceRgId
Section 6 — Testing End-to-End
6.1 Synthetic Event Injection
Do not test by actually spraying passwords. Instead, inject synthetic SigninLogs rows using the Custom Logs Ingestion API (Log Analytics Data Collection Rule):
# Inject 25 synthetic failed sign-in events via the Logs Ingestion API
# Requires a DCR endpoint and a registered Entra app with Monitoring Metrics Publisher role
ENDPOINT="https://<dce-name>.<region>.ingest.monitor.azure.com"
DCR_IMMUTABLE_ID="dcr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
STREAM_NAME="Custom-SigninLogs_CL"
BEARER_TOKEN=$(az account get-access-token --resource "https://monitor.azure.com" --query accessToken -o tsv)
python3 - <<'EOF'
import requests, json, datetime, random
endpoint = "<ENDPOINT>"
dcr_id = "<DCR_IMMUTABLE_ID>"
stream = "<STREAM_NAME>"
token = "<BEARER_TOKEN>"
spray_ip = "198.51.100.42" # TEST-NET-2, safe for lab use
users = [f"user{i:03d}@contoso.com" for i in range(1, 26)]
events = [
{
"TimeGenerated": datetime.datetime.utcnow().isoformat() + "Z",
"IPAddress": spray_ip,
"UserPrincipalName": u,
"ResultType": "50126", # invalid credentials
"AppDisplayName": "Microsoft 365",
"CorrelationId": f"test-{random.randint(10000,99999)}"
}
for u in users
]
# Add one success at the end
events.append({
"TimeGenerated": datetime.datetime.utcnow().isoformat() + "Z",
"IPAddress": spray_ip,
"UserPrincipalName": users[0],
"ResultType": "0",
"AppDisplayName": "Microsoft 365",
"CorrelationId": f"test-success-{random.randint(10000,99999)}"
})
resp = requests.post(
f"{endpoint}/dataCollectionRules/{dcr_id}/streams/{stream}?api-version=2023-01-01",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
data=json.dumps(events)
)
print(resp.status_code, resp.text)
EOF
Wait up to 5 minutes for ingestion, then either wait for the next scheduled run or manually Run query in the rule editor to verify results > 0.
6.2 Validation Checklist
- Alert appears in Sentinel → Incidents within one query cycle
- Incident card shows
IPAddressentity with geolocation - Tags
password-sprayvisible on incident - Teams channel shows the Adaptive Card with a working Open in Sentinel link
- Incident comments show "Teams notification sent to SOC channel"
Pitfalls to Avoid
| Mistake | Why It Hurts | Fix |
|---|---|---|
Using search * in rule queries |
Full-table scans; cost spikes and slow execution | Always scope with | where TimeGenerated > ago(...) and target specific tables |
| Setting query frequency = lookback period | Creates overlapping windows — double-counting events | Use frequency ≤ 50% of lookback, or use | where TimeGenerated between (ago(1h)..now()) with deduplication |
| Alert trigger instead of Incident trigger on Playbooks | Alert trigger fires per alert row, not per incident — storms your Logic App runs | Always use the Sentinel Incident trigger; call via Automation Rule |
| No entity mapping | Incidents look empty; UEBA and TI enrichment don't fire | Map at least one entity (IP, Account, or Host) on every rule |
| Skipping MITRE mapping | Coverage gaps invisible; executive dashboards misleading | Even approximate mapping is better than none |
| Hardcoding thresholds in KQL | Thresholds need tuning per tenant; PR for every change | Externalise thresholds to Watchlists and reference them in KQL |
Not capping make_set |
Fields silently truncate above 32 KB, losing data | Always cap: make_set(field, 50) or similar |
Troubleshooting
"My rule fires but creates no incident"
Cause: Incident creation is turned off on the rule's Incident settings tab, or a Microsoft Defender XDR connector is set to bi-directional sync and is suppressing duplicate incidents.
Fix: Check Analytics → Active Rules → rule → Edit → Incident settings → ensure toggle is On. If Defender XDR sync is active, check the connector's incident creation settings.
"Automation Rule runs but Playbook never fires"
Cause 1: The Logic App's managed identity does not have Microsoft Sentinel Playbook Operator on the workspace.
Cause 2: The Playbook is in a different subscription than the Sentinel workspace — cross-subscription calls require explicit permission grants.
Fix:
# Check Logic App run history for trigger failures
az logicapp show \
--name Notify-Teams-PasswordSpray \
--resource-group rg-sentinel-prod \
--query "state"
# View last 5 run outcomes
az rest \
--method GET \
--url "https://management.azure.com/subscriptions/<sub-id>/resourceGroups/rg-sentinel-prod/providers/Microsoft.Logic/workflows/Notify-Teams-PasswordSpray/runs?api-version=2016-06-01&\$top=5" \
--query "value[].{name:name,status:properties.status,startTime:properties.startTime}"
"KQL query returns results in Logs but zero alerts in Incidents"
Cause: The alert threshold is set incorrectly (e.g., Greater than 100 when your query returns 1 row), or query lookback does not overlap with data freshness window.
Fix: Temporarily set threshold to Greater than 0 and re-run manually. Check data connector Health (Sentinel → Settings → Workspace settings → Agents) to confirm data is flowing.
"Teams card posts but shows no entity data"
Cause: The Logic App expression triggerBody()?['object']?['properties']?['relatedEntities'] is correct, but entity mapping on the rule was added after the incident was created.
Fix: Entity mapping changes only apply to new incidents. Close the test incident, re-trigger the rule, and inspect the new incident.
Key Takeaways
- Scheduled Query Rules + KQL are the foundation of custom detection in Microsoft Sentinel — invest time in query quality before you worry about automation.
- Entity mapping is non-negotiable: it unlocks UEBA, threat intelligence correlation, and the Unified SOC Platform's cross-product incident timelines.
- Automation Rules → Playbooks is the correct architecture: Automation Rules handle deterministic triage logic, Playbooks handle external actions. Never wire a Playbook directly to every alert.
- Alert grouping by entity (IP, Account, Host) is the highest-ROI configuration change for reducing incident noise on high-volume detections.
- Deploy analytics rules as code (Bicep/ARM in a Git repo) — SOC rules are production infrastructure and deserve version control, peer review, and CI/CD deployment.
- Externalise thresholds to Watchlists so tier-1 analysts can tune detection sensitivity without touching KQL or raising a change request.
- The 2026 Unified SOC Platform means your Sentinel incidents now surface directly in Defender XDR — build entity mappings that align with Defender's identity and device schemas from the start.
Next Steps
- Promote your threat hunts: Any KQL query that finds something interesting in Logs can be saved directly as a new analytics rule — build the habit of hunting first, rule second.
- Deploy the Microsoft Sentinel GitHub content hub: Microsoft publishes hundreds of community rules at github.com/Azure/Azure-Sentinel — use them as templates, not production rules, until you validate thresholds against your own baseline.
- Externalise thresholds with Watchlists: Create a Watchlist called
DetectionThresholdswith columnsRuleName,FailureThreshold,AccountThresholdand reference it in KQL with_GetWatchlist('DetectionThresholds'). - Enable the Anomaly rules: Turn on at least the Anomalous Sign-In Activity and Unusual Process Execution ML Behavioural rules — they require zero KQL and catch what scheduled rules miss.
- Instrument your SOC metrics: Use Sentinel's Workbooks → SOC Efficiency to track mean-time-to-triage and alert-to-incident ratio week-over-week.
Related Articles
- [Placeholder] Microsoft Sentinel KQL Masterclass: 15 Queries Every SOC Analyst Should Know —
/security/sentinel-kql-queries-soc-analysts/ - [Placeholder] Sentinel Watchlists Deep Dive: Dynamic Allow/Block Lists and Threshold Management —
/security/sentinel-watchlists-guide/ - [Placeholder] Zero-Trust Network Segmentation with Azure Firewall and Sentinel Integration —
/security/azure-firewall-sentinel-zero-trust/
Leave a Reply