Query Details
let query_frequency = 1h;
let query_period = 14d;
let scan_step = 5m;
let consecutive_failures_threshold = 2;
let _ScheduledRunTimeSeries = (start_time: datetime, end_time: datetime) {
let _Auxiliar = toscalar(
SentinelHealth
| where TimeGenerated between (start_time .. end_time)
| where OperationName == "Scheduled analytics rule run"
| make-series Count = count() default=0 on TimeGenerated step scan_step
| extend
TimeGenerated = array_slice(TimeGenerated, 0, toint(-(consecutive_failures_threshold * query_frequency / scan_step))),
Count = array_slice(Count, 0, toint(-(consecutive_failures_threshold * query_frequency / scan_step)))
| extend series_periods_detect(
Count,
0.0,
toint(24h / scan_step),
1)
| summarize Period = take_any(toint(series_periods_detect_Count_periods[0]))
);
let _PeriodStep = scan_step * abs(coalesce(_Auxiliar, 1));
SentinelHealth
| where TimeGenerated between (start_time .. end_time)
| where OperationName == "Scheduled analytics rule run"
// _PeriodStep cannot be used with make-series, so summarize has to be used instead
// | make-series Count = count() default=0 on TimeGenerated step _PeriodStep
// summarize does not generate zero values for count(), baseline noise has to be added, it will be "deleted" afterwards
| union (range TimeGenerated from start_time to end_time step _PeriodStep)
| summarize Count = count() by bin_at(TimeGenerated, _PeriodStep, end_time)
| extend Count = Count - 1
| summarize TimeGenerated = make_list(TimeGenerated), Count = make_list(Count)
| extend
TimeGenerated = array_slice(TimeGenerated, 0, -2),
Count = array_slice(Count, 0, -2)
| extend series_decompose_anomalies(Count)
| where array_sum(array_slice(series_decompose_anomalies_Count_ad_flag, -(consecutive_failures_threshold), -1)) == (-1 * consecutive_failures_threshold)
};
_ScheduledRunTimeSeries(ago(query_period), now())
// Uncomment the following line if you want only one alert (during the first query_frequency of the anomaly)
| where not(toscalar(_ScheduledRunTimeSeries(ago(query_period), ago(query_frequency)) | count) > 0)
| render timechart
This KQL query is designed to monitor the execution of scheduled analytics rule runs in the SentinelHealth data over a specified period and detect anomalies in their execution frequency. Here's a simplified breakdown:
Parameters Setup:
query_frequency: The frequency at which the query is run (1 hour).query_period: The total time period over which the data is analyzed (14 days).scan_step: The time interval for scanning data points (5 minutes).consecutive_failures_threshold: The number of consecutive failures needed to trigger an anomaly (2).Auxiliary Calculation:
_Auxiliar to determine the periodicity of the scheduled runs within the specified time frame.Data Processing:
SentinelHealth table for the specified time range and operation name ("Scheduled analytics rule run").summarize and make-series to count the occurrences of scheduled runs, adjusting for noise by adding and then subtracting a baseline value.Anomaly Detection:
series_decompose_anomalies to identify anomalies in the count of scheduled runs.consecutive_failures_threshold) indicating potential issues with the scheduled runs.Output:
render timechart) showing when anomalies occurred.In essence, this query helps identify periods where scheduled analytics rule runs are failing or not occurring as expected, potentially indicating issues that need attention.

Jose Sebastián Canós
Released: March 12, 2026
Tables
Keywords
Operators