What it does
The Ops Script Schedules page (under the Configure nav group) lets you run library scripts on a recurrence - hourly, daily, weekly, or monthly. Each run is captured with full per-target output, and consecutive runs are compared for drift. When drift or failure is detected, configurable action rules fire email notifications or open Tasker tasks for follow-up.
Use cases
- Configuration drift detection - Run a "report current state" script daily; get alerted when output changes (registry key flipped, service stopped, group membership changed).
- Compliance evidence collection - Capture proof of control state on a recurring basis. Each run is a timestamped snapshot in OpsScheduleRuns with full stdout/stderr.
- Remediation drift watch - After remediating a finding, schedule a daily verification script. Get a Tasker task opened the moment the remediation reverts.
- Tenant-wide M365 enforcement - Cloud-target scripts (โ๏ธ) run against Graph/EXO from a runbook context, not on endpoints. Example: ensure mailbox Calendar default permission stays Reviewer (Caron Bletzer's first use case).
Script execution-target taxonomy ADO #498
Every library script is tagged with an executionTarget field that determines where and how it runs:
| Target | Icon | Where it runs | Requires targets? |
|---|---|---|---|
device | ๐ฅ๏ธ | Dispatched to Windows hostnames via the TATER Agent's command-poll loop | Yes - one or more lowercase hostnames |
cloud | โ๏ธ | Runs against an M365/Entra tenant via Graph or EXO from a runbook or interactive admin session | No - leave targets empty |
hybrid | ๐ | Author opt-in for both modes (rare) | Yes when used in device mode |
Why this matters: a cloud script attempted via the device-execute path will fail at runtime - there's no agent to receive it. The scheduler enforces the contract at save time: cloud scripts cannot have device targets, device/hybrid scripts must have at least one. Existing scripts default to device (matches prior behavior).
Cloud auth context (optional companion)
Cloud scripts can additionally declare cloudAuthContext to document what auth the runbook host must provide. Values:
exo-managed-identity- Exchange Online cmdlets via Automation Account managed identitygraph-managed-identity- Microsoft Graph via Automation Account managed identityexo-interactive- Exchange Online cmdlets via interactive admin sign-ingraph-interactive- Microsoft Graph via interactive admin sign-inazure-managed-identity- Azure Management API via managed identity
Creating a schedule
- Open TATER Ops โ Configure โ Script Schedules and click + New Schedule.
- Pick a script. The dropdown shows the execution-target icon next to each script.
- For device/hybrid scripts: enter target hostnames (one per line, lowercase). Cloud scripts leave this empty.
- Set the recurrence - frequency, UTC time, and (where applicable) day of week / day of month.
- Add action rules. Trigger options: on-drift, on-failure, or always. Action options: email (with recipients) or tasker-task (with category + priority).
- Save. The cron checks every 5 minutes and runs schedules whose
nextRunAthas passed.
How drift detection works
When a run completes, the scheduler queries the most recent previous run for the same schedule and compares normalized stdout per target. Any host whose stdout differs from the prior run is flagged. The run record stores:
driftDetected: true/falsedriftSummary.changedTargets- list of hostnames that changeddriftSummary.sampleDiff- first ~2KB of difference (truncated)
When drift is found, run status rolls up to Drift (instead of Success) so it shows up amber in the runs list and triggers any on-drift action rules.
Drift detection precision
Phase 1 compares whitespace-normalized stdout text. Scripts that emit timestamps, GUIDs, or other always-changing data will appear to drift every run. Best practice: structure your script output so the stable signal (config values, group membership IDs, permission strings) is on its own lines, separated from the noisy metadata. Phase 2 will add JSON diff with per-property Before/After.
Action rules
Each schedule supports up to 20 action rules. They fire after a run finalizes, in order of trigger:
- on-drift - fires when
driftDetected = true. Use to flag configuration changes for review. - on-failure - fires when run status is
FailedorMixed. Use to escalate broken automation. - always - fires on every completed run. Use for low-volume nightly status emails.
Email actions send via the configured notifications channel (Graph or SMTP). Tasker-task actions create a new task in TATER Ops under the supplied category and priority, with the run summary embedded in the task description and the originating schedule linked.
Recent runs & per-target output
The Recent Runs tab lists the last 50 runs across all schedules. Click any row to open the run detail modal, which shows:
- Drift diff panel (amber) - sample of what changed between consecutive runs
- Per-target table - host, status, exit code, duration, and clipped stdout (2KB per host)
- For Skipped runs (cloud scripts in Phase 1) - the reason the run was skipped
Cloud script execution
Cloud-targeted schedules execute. They fire the same per-org Azure Automation webhook
(Run-OpsScriptCloud) that the on-demand Run button uses — one shared
dispatch, so a scheduled run cannot behave differently from a manual one.
A cloud run stays Running until the runbook posts its result back, then finalises
Success, Failed, or Drift from that result. It is deliberately
NOT finalised the way device runs are: device runs are judged by counting agent commands, and a cloud
job has none — so the device logic would see “nothing pending”, conclude the run was
over before the runbook had started, and record Failed every time.
If the organisation has no Ops Script Cloud Webhook URL configured, the run records
Failed with that as the reason. It is not skipped and it is not green.
Skipped, advanced its next-run time, and executed nothing — which looked
identical to a working schedule on every surface. At one firm a weekly governance scan cited in a
published config document as a live control had run zero times. If you have cloud schedules that
predate that date, judge them by their run history, not their next-run time.
Is this schedule actually running?
A green dot and a future next-run time are not evidence that anything ran. Every schedule now
carries a health verdict, computed from its real run history rather than from its settings,
and shown on the schedule list, the schedule detail, and list_ops_schedules over MCP.
| Verdict | Means |
|---|---|
| ok | Runs, and recently enough for its cadence. |
| stuck clock | Enabled, but its next-run time is in the past — the scheduler could not advance it, so it is retrying silently. |
| stale | Nothing has actually executed for several intervals — or the schedule has never completed a real run. |
| inactive | Disabled, or a one-off that has fired. Nothing to say. |
| unknown | The cadence is not one this check understands, so whether it runs is unknown — stated rather than assumed to be fine. |
Two things it deliberately does not count as an execution: a Skipped run (a skip is not a run) and a Running one (it has not finished). A schedule whose history is nothing but skips therefore reports stale — which is the honest answer, and the one the old surfaces could not give.
If the verdict cannot be computed, the schedule shows health unknown rather than nothing. Absent evidence must not read as good news.
Permissions
- Auditor+ - view schedules, view runs.
- Admin+ - create, edit, delete schedules; trigger ad-hoc runs.
Related
- Ops customization - categories, priorities, statuses, teams.
- Agent deployment - getting the TATER Agent installed on target devices.
- MCP setup - using AI agents to query schedule history and trigger runs.