Operations
Run the SQL Server Agent unattended: Windows Task Scheduler and RMM deployment, five-minute schedules, health checks, upgrades and the operational runbook.
The agent is designed to run unattended for months on a Windows host that nobody logs into. This section covers the scheduling, deployment and day-two practices that make that safe.
Monitoring and logs
Reading the run log, the telemetry blocks, shipped logs and platform run reports.
State store
What the DuckDB state database holds, how to back it up, inspect it and recover it.
Troubleshooting
Symptoms, causes and fixes for the errors you are most likely to meet.
Scheduling with Windows Task Scheduler
The installer registers a task named DLH.io SQL Server Agent X that runs the executable daily under the dedicated svc_dlh_agent_runner account with these settings:
| Setting | Value | Why it matters |
|---|---|---|
| Run whether user is logged on or not | yes (password logon) | Runs on a locked or rebooted host |
| Run with highest privileges | yes | Needed to write to the install directory and %PROGRAMDATA% |
| If the task is already running | Do not start a new instance | Two concurrent runs from one directory would contend for the DuckDB state file and CT pointers |
| Start the task if a missed start is skipped | yes | Catches up after a reboot |
| Stop the task if it runs longer than | 1 hour | Guards against a hung run; lengthen for large initial loads |
| Working directory | install directory | The agent reads dlh_agent_config.yaml from its own folder |
Five-minute schedule
For Change Tracking or CDC sources most customers change the trigger to repeat every 5 to 15 minutes:
$task = Get-ScheduledTask -TaskName "DLH.io SQL Server Agent X"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).Date -RepetitionInterval (New-TimeSpan -Minutes 5)
Set-ScheduledTask -TaskName $task.TaskName -Trigger $triggerOr in the UI: Triggers tab, edit the trigger, Repeat task every: 5 minutes, duration Indefinitely. Keep Do not start a new instance on the Settings tab.
Runs longer than the interval
With "Do not start a new instance" a run that takes 12 minutes simply causes the next two triggers to be skipped; nothing overlaps and nothing is lost because the CT pointer only advances after a successful write. If runs regularly exceed the interval, reduce the work per run (incremental modes, direct_cloud_write, produce_audit_trail: false) or split tables across two agent installs with separate connections. See Performance and retention.
Initial load versus steady state
The first run of a large database can take hours. Run it interactively (or with the task's time limit raised) so it is not killed at the one-hour mark, then restore the limit. Every incremental table that completes has its pointer recorded, so after an interrupted initial load those tables continue incrementally on the next run while only the tables that had not finished are loaded in full.
RMM deployment
Deploying through an RMM (ConnectWise Automate, NinjaOne, Datto, Intune, or similar) follows the same steps as a manual install, scripted:
- Copy the installer to the host and run it silently (it is a standard Inno Setup package, so
/VERYSILENT /SUPPRESSMSGBOXESapplies); the exact command for your build is provided with the download package, request it through contact us. - Drop the prepared
dlh_agent_config.yamlanddlh_sql_server_credentials.yamlinto the install directory. Ship the SQL Server password in plaintext once; the first run encrypts it and deletes the plaintext (or setDLH_AGENT_SECRET_KEYmachine-wide first so the encryption key is not stored on disk). - Install the ODBC driver:
dlh_agent_sql_server.exe --install-driver --auto. - Validate:
dlh_agent_sql_server.exe --config-checkthendlh_agent_sql_server.exe --diagnose --offline(before firewall rules) and--diagnose(after). - Trigger one interactive run:
dlh_agent_sql_server.exe; confirm exit code0and the tier banner shows the expected tier. - Adjust the scheduled task trigger as above.
Reuse the same configuration across hosts by templating only dlh_ref.connection_guid, target_schema_prefix, server_name_or_ip and database_names; everything else is usually identical for a fleet.
Health check as an RMM monitor
Schedule dlh_agent_sql_server.exe --diagnose --json from the RMM and alert on:
- exit code
1or2; state_store.tables_last_failedabove zero for more than a few runs;state_store.last_successful_run_atolder than a threshold (for example three intervals);- any
systemcheck reporting low disk.
This complements the platform-side heartbeat: the platform notices an agent that stopped calling home, the RMM notices a host that cannot even run the diagnostic.
Upgrading
- Stop the scheduled task (Disable) and wait for any running instance to finish.
- Run the new installer over the existing directory. Configuration, credential, key and state files are preserved.
- Run
--config-checkand add any new recommended fields it lists; review the changelog for behavior changes. - Run
--diagnose, then one interactive run, then re-enable the task.
To roll back, reinstall the previous version over the same directory. Keep a copy of dlh_agent_state.duckdb from before the upgrade so a rollback can restore the state file that matches the older release.
Moving to a new host
Copy the whole install directory (including dlh_agent_state.duckdb, dlh_agent_secret.key and the .sec credential file) to the new host, install the ODBC driver, register the task, and run --diagnose. The bootstrap cache under %PROGRAMDATA% is not needed; it is rebuilt on the first run. Never run the old and new host at the same time against the same connection.
Runbook summary
| Situation | Action |
|---|---|
| Alert: run completed with N errors | Open the shipped log or --diagnose; see Troubleshooting |
| Alert: FATAL ERROR | Check SQL Server reachability and storage credentials with --diagnose; the run left no partial pointers |
| Platform shows no heartbeat | Host down, task disabled or outbound HTTPS blocked; run --diagnose on the host |
| Trial expiry warning | Contact DLH.io through contact us before the date; after expiry the agent drops to Community limits |
| Disk warning | Reduce agent_log_retention_days and audit_trail_retention_days or disable the audit trail; move output to a larger volume |
| Table stuck failing for days | Fix the cause before CT retention expires, or force a one-time sync_mode: full for that table |