DLH.io Documentation logoDLH.io Documentation

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.

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:

SettingValueWhy it matters
Run whether user is logged on or notyes (password logon)Runs on a locked or rebooted host
Run with highest privilegesyesNeeded to write to the install directory and %PROGRAMDATA%
If the task is already runningDo not start a new instanceTwo concurrent runs from one directory would contend for the DuckDB state file and CT pointers
Start the task if a missed start is skippedyesCatches up after a reboot
Stop the task if it runs longer than1 hourGuards against a hung run; lengthen for large initial loads
Working directoryinstall directoryThe 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 $trigger

Or 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:

  1. Copy the installer to the host and run it silently (it is a standard Inno Setup package, so /VERYSILENT /SUPPRESSMSGBOXES applies); the exact command for your build is provided with the download package, request it through contact us.
  2. Drop the prepared dlh_agent_config.yaml and dlh_sql_server_credentials.yaml into the install directory. Ship the SQL Server password in plaintext once; the first run encrypts it and deletes the plaintext (or set DLH_AGENT_SECRET_KEY machine-wide first so the encryption key is not stored on disk).
  3. Install the ODBC driver: dlh_agent_sql_server.exe --install-driver --auto.
  4. Validate: dlh_agent_sql_server.exe --config-check then dlh_agent_sql_server.exe --diagnose --offline (before firewall rules) and --diagnose (after).
  5. Trigger one interactive run: dlh_agent_sql_server.exe; confirm exit code 0 and the tier banner shows the expected tier.
  6. 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 1 or 2;
  • state_store.tables_last_failed above zero for more than a few runs;
  • state_store.last_successful_run_at older than a threshold (for example three intervals);
  • any system check 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

  1. Stop the scheduled task (Disable) and wait for any running instance to finish.
  2. Run the new installer over the existing directory. Configuration, credential, key and state files are preserved.
  3. Run --config-check and add any new recommended fields it lists; review the changelog for behavior changes.
  4. 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

SituationAction
Alert: run completed with N errorsOpen the shipped log or --diagnose; see Troubleshooting
Alert: FATAL ERRORCheck SQL Server reachability and storage credentials with --diagnose; the run left no partial pointers
Platform shows no heartbeatHost down, task disabled or outbound HTTPS blocked; run --diagnose on the host
Trial expiry warningContact DLH.io through contact us before the date; after expiry the agent drops to Community limits
Disk warningReduce agent_log_retention_days and audit_trail_retention_days or disable the audit trail; move output to a larger volume
Table stuck failing for daysFix the cause before CT retention expires, or force a one-time sync_mode: full for that table