Everything you need to deploy DrainCtl, configure it for your environment, and start getting real-time drain mode visibility across your RDSH farm.
DrainCtl ships as a single MSI. Deploy it however you deploy software — your RMM, SCCM, Intune, GPO, or just double-click it.
Double-click the MSI to launch the interactive installer. It walks you through choosing an install mode (dashboard, registration, or standalone) and setting notification URLs, dashboard address, grace period, and other options.
msiexec /i LISSTech.DrainCtl.msi /qn
Pass these properties on the command line (or in your RMM/SCCM transform) to pre-configure the installation:
| Property | Values / Example | Description |
|---|---|---|
INSTALL_MODE |
dashboard | registration | standalone |
Sets the operational mode. dashboard enables the web UI, registration registers with an existing dashboard, standalone runs independently. |
WEBHOOK_URL |
https://hooks.example.com/drain |
Adds a webhook notification target |
NTFY_URL |
https://ntfy.sh/drainctl |
Adds an ntfy notification target |
DASHBOARD_URL |
https://dash:49470 |
Dashboard URL to register with (used with registration mode) |
DASHBOARD_PORT |
49470 |
Port for the dashboard listener (used with dashboard mode) |
DASHBOARD_GROUP |
Domain Admins |
AD group authorized for dashboard access |
GRACE_PERIOD |
120 |
Grace period in minutes before drain mode becomes an alert |
Example — register with a dashboard, no UI:
msiexec /i LISSTech.DrainCtl.msi /qn INSTALL_MODE=registration DASHBOARD_URL=https://dash:49470
drainctl.exe and drainctl.dll to
C:\Program Files\LISS Technologies\LISSTech DrainCtl\bin\
drainctl is available
immediately in any terminal
C:\Program Files\WindowsPowerShell\Modules\
C:\ProgramData\LISS Technologies\LISSTech DrainCtl\%ProgramData%\LISS Technologies\LISSTech DrainCtl\config.json
drainctl check will work right
away.
If you just want the PowerShell cmdlets without the Windows Service, install directly from PSGallery:
Install-Module -Name LISSTech.DrainCtl -Scope AllUsers
This gives you Get-RDSHDrainMode, Test-RDSHDrainMode, and friends — but without the service, queries go directly to the registry instead of the named pipe (slightly slower, no persistent audit store).
msiexec /x LISSTech.DrainCtl.msi /qn
This stops the service, removes all files, cleans up PATH, and deregisters the Event Log source. Your audit data in ProgramData is preserved (delete it manually if you want a clean slate).
After installation, verify everything is working:
# Is the service running?
Get-Service DrainCtl
# Quick status check (instant — talks to the service via named pipe)
drainctl check
# PowerShell way
Get-RDSHDrainMode | Format-List
You should see status=Healthy and connections_allowed=true. If drain mode is
active on this server, you'll see the current state and how long it's been active.
drainctl check responds in under 100ms, it's talking to the service over the named pipe.
If it takes a second or two, the service isn't running and it's falling back to a direct registry read.
Check Get-Service DrainCtl.
All settings live in a single JSON file and hot-reload automatically when you save it. No service restart needed.
%ProgramData%\LISS Technologies\LISSTech DrainCtl\config.json
{
"grace_period": 60,
"retention_days": 90,
"poll_interval": 300,
"audit_path": "C:\\ProgramData\\LISS Technologies\\LISSTech DrainCtl\\audit.jsonl",
"notifications": [
{
"type": "webhook",
"url": "https://hooks.example.com/drainctl",
"triggers": ["drain_on", "drain_off", "alert", "healthy"],
"repeat_minutes": 30
}
],
"dashboard": {
"enabled": false,
"port": 49470,
"group": "Domain Admins"
},
"session_warning_threshold": 80
}
| Key | Default | What it does |
|---|---|---|
grace_period |
60 | Minutes drain mode must be active before it becomes an alert. During the grace period, the status is "Grace" and exit code is 0. |
retention_days |
90 | Days to keep audit records. Maximum 365. Older records are pruned daily. |
poll_interval |
300 | Seconds between safety-net polls. The service also uses real-time registry notifications, so this is a fallback. |
audit_path |
ProgramData path | Full path to the JSONL audit trail file. |
notifications |
[] | Array of notification targets (see Notifications). |
dashboard |
see above | Dashboard settings (see Dashboard). |
session_warning_threshold |
80 |
Session utilization percentage that triggers a session_warning notification.
|
Edit the file with any text editor:
%ProgramData%\LISS Technologies\LISSTech DrainCtl\config.json
The service picks up changes within seconds (it watches config.json with a file system watcher). No restart needed.
HKLM\SYSTEM\CurrentControlSet\Services\DrainCtl\Parameters) into config.json.
Your old registry values are left in place but are no longer read. No manual migration needed.
DrainCtl can tell you who changed drain mode — but it needs Windows to record that information first. Run this once (as admin):
drainctl audit-setup
# or
Install-RDSHDrainAudit
This does two things:
auditpolTSServerDrainMode is modified
auditpol settings. For persistence, configure the equivalent GPO:Computer Configuration > Policies > Windows Settings > Security Settings > Advanced
Audit Policy Configuration > Object Access > Audit Registry > Success
Without audit setup, DrainCtl still detects changes instantly — it just can't tell you who made
them. The changed_by field will be empty.
# Plain text (default — great for terminal and RMM scripts)
drainctl check
# JSON (great for parsing)
drainctl check --format json
# Table
drainctl check --format table
# Quiet mode (only final status line)
drainctl --quiet check
# Last 50 records (table format)
drainctl history
# Only state transitions
drainctl history --changes-only
# Last 10 records as JSON
drainctl history --limit 10 --format json
# Time-bounded queries (RFC 3339 timestamps)
drainctl history --since 2025-01-01T00:00:00Z
drainctl history --since 2025-06-01T00:00:00Z --until 2025-06-30T23:59:59Z
| Code | Meaning | When |
|---|---|---|
| 0 | Healthy / Grace | Connections allowed, or drain mode active but within grace period |
| 1 | Alert | Drain mode active beyond grace period — new connections are blocked |
| 2 | Error | Registry unreadable or other failure |
--format plain (default for check), table (default for
history), csv, json.
The JSON output from drainctl check --format json includes session data — active
sessions, total capacity, and utilization percentage — useful for building custom dashboards or
feeding into monitoring systems.
# View notification status
drainctl notify status
# Set a webhook target
drainctl notify set-webhook https://hooks.example.com/drainctl
# Set an ntfy target
drainctl notify set-ntfy https://ntfy.sh/my-rdsh-alerts
config.json directly or use the dashboard UI. The CLI
set-webhook and set-ntfy commands are convenience shortcuts for single-target
setups.
Run drainctl configure without flags to step through every setting interactively.
The current value is shown in brackets — press Enter to keep it.
drainctl configure
When called with flags (e.g., by the MSI installer during silent install), it applies the values
directly and writes config.json without prompting:
drainctl configure --grace-period 120 --mode dashboard --dashboard-port 49470
The module is auto-registered by the MSI. It works on both PowerShell 5.1 and 7+.
# Rich status object
Get-RDSHDrainMode
# Boolean check — perfect for scripts and alerts
if (Test-RDSHDrainMode) { "All good" } else { "ALERT: drain mode active!" }
# Audit history
Get-RDSHDrainHistory -Limit 20
# Only transitions (who changed what, when)
Get-RDSHDrainHistory -ChangesOnly | Format-Table Timestamp, DrainMode, ChangedBy
# Pipeline magic
Get-RDSHDrainHistory -ChangesOnly |
Where-Object { $_.DrainMode -ne "ALLOW_ALL_CONNECTIONS" } |
Select-Object Timestamp, ChangedBy, DrainMode
| Cmdlet | Returns | Description |
|---|---|---|
Get-RDSHDrainMode |
PSObject | Full drain mode state with audit data |
Test-RDSHDrainMode |
bool | $true if connections allowed |
Get-RDSHDrainHistory |
PSObject[] | Audit trail records |
Install-RDSHDrainAudit |
— | One-time audit configuration |
Get-RDSHDrainNotification |
PSObject | Current notification config |
Set-RDSHDrainNotification |
— | Update notification settings |
Test-RDSHDrainNotification |
— | Send test notification |
Get-RDSHDrainNotificationTarget |
PSObject[] | Lists all notification targets with type, URL, triggers, repeat interval |
Add-RDSHDrainNotificationTarget |
— |
Adds a notification target (-Type, -URL, -Triggers,
-RepeatMinutes)
|
Remove-RDSHDrainNotificationTarget |
— | Removes a notification target by -URL |
Enable-RDSHDrainDashboard |
— | Enable the multi-server dashboard on this server |
Disable-RDSHDrainDashboard |
— | Disable the dashboard on this server |
Install-RDSHDrainCertificate |
— | Install a custom TLS certificate for the dashboard |
Get pushed when something happens. DrainCtl supports multiple notification targets — any combination of webhooks (Slack, Teams, PagerDuty, custom HTTP endpoints) and ntfy.sh topics. Each target has its own triggers and repeat interval.
# Set a webhook target (Slack, Teams, PagerDuty, custom endpoint, etc.)
drainctl notify set-webhook https://hooks.slack.com/services/T.../B.../xxx
# Set an ntfy target (push notifications on your phone!)
drainctl notify set-ntfy https://ntfy.sh/my-rdsh-alerts
# View notification status
drainctl notify status
# Send a test to make sure it works
drainctl notify test
To manage multiple targets with per-target triggers and repeat intervals, edit
config.json directly or use the dashboard UI's notification
settings drawer. See the Configuration section for the full
config.json structure.
Set-RDSHDrainNotification -WebhookURL "https://hooks.example.com/drainctl"
Set-RDSHDrainNotification -NtfyURL "https://ntfy.sh/my-alerts"
Test-RDSHDrainNotification
# See current config
Get-RDSHDrainNotification
# List all targets
Get-RDSHDrainNotificationTarget
# Add a webhook for alerts only
Add-RDSHDrainNotificationTarget -Type webhook -URL 'https://hooks.example.com/drain' -Triggers alert -RepeatMinutes 15
# Add ntfy for all events
Add-RDSHDrainNotificationTarget -Type ntfy -URL 'https://ntfy.sh/drainctl'
# Remove a target
Remove-RDSHDrainNotificationTarget -URL 'https://hooks.example.com/drain'
Each notification target can subscribe to specific triggers. Assign them with
--triggers when adding a target, or edit config.json directly.
| Trigger | Description |
|---|---|
drain_on |
Drain mode activated |
drain_off |
Drain mode deactivated |
grace_entered |
Entered grace period |
alert |
Grace period exceeded |
healthy |
Returned to healthy |
session_warning |
Session utilization threshold exceeded (default 80%) |
Each target also has a repeat_minutes setting — set it to re-send alerts periodically
while the condition persists (0 = notify once).
{
"event": "transition",
"host": "MDS-LDC1-RDS5",
"drain_mode": "ALLOW_RECONNECTIONS_PREVENT_NEW_LOGONS",
"previous_mode": "ALLOW_ALL_CONNECTIONS",
"status": "Grace",
"message": "Drain mode active, within grace period (45m remaining).",
"changed_by": "MDS\\lissadmin",
"state_duration_seconds": 900,
"timestamp": "2026-04-03T14:30:00-04:00"
}
ntfy messages arrive with priority high for alerts (red notification on your phone) and default for transitions.
DrainCtl monitors active RDS sessions via WTSEnumerateSessionsW and exposes utilization
data alongside drain mode state.
Session counts and utilization appear automatically in drainctl check --format json output,
including active sessions, total capacity, and utilization percentage. This is useful for feeding into
monitoring systems or custom dashboards.
When session utilization exceeds the configured threshold, DrainCtl fires a
session_warning notification. The default threshold is 80% — change
it in config.json:
{
"session_warning_threshold": 80
}
Add session_warning to a notification target's triggers to receive these alerts (see
Notifications).
The multi-server dashboard displays per-server session gauges showing current utilization at a glance.
Managing multiple RDSH servers? The dashboard gives you a single, live view of drain mode state across your entire farm — with Windows Authentication so only the right people see it.
config.json, the
DrainCtl service serves an HTTP dashboard
# Enable the dashboard server (writes to config.json)
drainctl dashboard enable --port 49470 --group "Domain Admins"
# Or with a custom AD group
drainctl dashboard enable --port 49470 --group "RDS Admins"
You can also edit config.json directly:
{
"dashboard": {
"enabled": true,
"port": 49470,
"group": "Domain Admins"
}
}
Changes are hot-reloaded — no service restart needed.
On each RDSH server you want to monitor, run this once:
# Register with explicit URL
drainctl register https://dashboard-server:49470
# Or auto-discover via DNS SRV record
drainctl register --auto
This does two things:
dashboard.url to the local config.json (so the service starts
reporting automatically on startup)
Instead of configuring dashboard.url on every agent, create a DNS SRV record and agents
will find the dashboard automatically. The service checks for
_drainctl._tcp.<domain> on startup when no URL is configured.
# PowerShell — create the SRV record in AD-integrated DNS
Add-DnsServerResourceRecord -ZoneName "contoso.com" `
-Name "_drainctl._tcp" -Srv `
-DomainName "dashboard.contoso.com" `
-Port 49470 -Priority 0 -Weight 0
# Verify
Resolve-DnsName -Name "_drainctl._tcp.contoso.com" -Type SRV
Once the SRV record exists, agents discover the dashboard without any per-machine config. Just install the MSI and the service registers itself.
# Open in your browser
drainctl dashboard
# Or navigate directly
https://dashboard-server:49470
The dashboard shows a live grid with each server's hostname, drain mode, status, state duration, who last changed it, and when it was last seen. Color-coded: green for healthy, amber for grace, red for alert, gray for offline. Each server card includes a session gauge showing current utilization, and the dashboard features uPlot charts for historical state and session trends.
The notification settings drawer supports multi-target editing — add and remove individual notification targets, each with its own triggers and repeat interval, directly from the dashboard UI.
# List all registered servers
drainctl dashboard list-servers
# Remove a decommissioned server
drainctl dashboard remove-server RDSH-OLD
The dashboard uses Windows Authentication (Kerberos/SPNEGO) via SSPI. No passwords, no tokens, no configuration:
The dashboard always runs over HTTPS. On first start, the service auto-generates a self-signed TLS certificate (ECDSA P-256, valid 1 year). The certificate and private key are stored in the DrainCtl data directory:
%ProgramData%\LISS Technologies\LISSTech DrainCtl\dashboard-tls.crt # world-readable
%ProgramData%\LISS Technologies\LISSTech DrainCtl\dashboard-tls.key # SYSTEM + Admins only
The private key is ACL-restricted to SYSTEM and Administrators. The certificate auto-renews 24 hours before expiry.
To view the dashboard's current TLS certificate fingerprint:
drainctl dashboard fingerprint
Certificate pinning lets agents verify they're talking to the real dashboard, not an impersonator. Pinning is opt-in — without it, agents use trust-on-first-use (TOFU) for TLS, which is fine for most environments.
Auto-pin on registration: Pass --pin when registering to automatically
capture and save the dashboard's certificate fingerprint:
# Register and auto-pin the dashboard's TLS certificate
drainctl register --auto --pin
drainctl register https://dashboard:49470 --pin
The fingerprint is saved to dashboard.tls_fingerprint in the agent's
config.json. All subsequent reports will verify the dashboard's certificate matches.
Auto-pin via config (for service auto-registration): Set
"auto_pin": true in config.json and the service will capture the fingerprint
on its next registration:
{
"dashboard": {
"url": "https://dashboard:49470",
"auto_pin": true
}
}
MSI deployment: Pass AUTO_PIN=1 as an MSI property:
msiexec /i LISSTech.DrainCtl.msi /qn INSTALL_MODE=registration DASHBOARD_URL=https://dash:49470 AUTO_PIN=1
Manual pinning: If you prefer to set the fingerprint yourself (e.g., distributed via
GPO), run drainctl dashboard fingerprint on the dashboard server and set
dashboard.tls_fingerprint in each agent's config. A manually-set fingerprint is never
overwritten by auto-pin.
drainctl register --auto --pin on each agent, or
set "auto_pin": true in config and restart the service. Without pinning enabled, cert
renewal is seamless.
To use a CA-issued or internal PKI certificate instead of the auto-generated self-signed one:
# CLI
drainctl dashboard install-cert C:\certs\dashboard.pem C:\certs\dashboard-key.pem
# PowerShell
Install-RDSHDrainCertificate -CertPath C:\certs\dashboard.pem -KeyPath C:\certs\dashboard-key.pem
This copies the PEM files into the DrainCtl data directory and updates config.json with the
paths. Restart the service to use the new certificate.
Both files must be PEM-encoded. The service loads them on startup and skips auto-generation when both are set. If the custom cert is invalid or expired, the service falls back to HTTP with a warning in the event log.
You can also set the paths directly in config.json:
{
"dashboard": {
"tls_cert": "C:\\certs\\dashboard.pem",
"tls_key": "C:\\certs\\dashboard-key.pem"
}
}
| Key | Default | Description |
|---|---|---|
dashboard.enabled |
false | Enable the HTTPS dashboard (set to true) |
dashboard.port |
49470 | Port the dashboard listens on |
dashboard.group |
Domain Admins | AD group authorized to view the dashboard |
dashboard.url |
empty | Agent-side: URL of the dashboard to report to (set by drainctl register) |
dashboard.tls_cert |
empty | Path to PEM certificate file. If empty, a self-signed cert is auto-generated. |
dashboard.tls_key |
empty | Path to PEM private key file. Required when tls_cert is set. |
dashboard.tls_fingerprint |
empty |
SHA-256 certificate fingerprint for agent-side pinning. Set manually or via
--pin.
|
dashboard.auto_pin |
false | Auto-capture dashboard cert fingerprint on service registration. |
When an agent has a dashboard.url configured, it automatically pulls notification targets,
grace period, and session warning threshold from the dashboard server. This means you only need to
configure notifications once — on the dashboard — and all agents inherit the settings.
How it works:
GET /api/v1/notify-config from the dashboard
No dashboard URL? Agents without dashboard.url use their local
config.json notification targets as before — nothing changes for standalone
deployments.
notifications empty in each agent's config.json. The dashboard
becomes the single source of truth.
DrainCtl is designed to integrate with any RMM platform that can run a script and check an exit code.
drainctl check --quiet
exit $LASTEXITCODE
That's it. --quiet suppresses intermediate log lines — only the final status line is emitted. Works with any RMM that supports PowerShell or CMD scripts with exit code thresholds.
# Parse JSON output for custom dashboards or APIs
$result = drainctl check --format json | ConvertFrom-Json
if ($result.exit_code -ne 0) {
# Send to your ticketing system, dashboard, etc.
}
The MSI handles service installation. These commands are for manual management or troubleshooting.
# Check service status (CLI — shows Running / Stopped)
drainctl service status
# Check service status (PowerShell — richer output)
Get-Service DrainCtl
# Stop / Start / Restart
Stop-Service DrainCtl
Start-Service DrainCtl
Restart-Service DrainCtl
# Manual install (if not using MSI)
drainctl service install
drainctl service start
# Manual uninstall
drainctl service stop
drainctl service uninstall
TSServerDrainMode via
RegNotifyChangeKeyValue — instant detection
EvtSubscribe — knows
who made the change
\\.\pipe\drainctl) for instant
CLI/PS responses
WTSEnumerateSessionsW for utilization
alerts
config.json changesCheck Application log, source DrainCtl:
| ID | Level | What happened |
|---|---|---|
| 1000 | Info | Service started |
| 1001 | Info | Service stopped |
| 1002 | Info | Check: healthy |
| 1004 | Info | State transition detected |
| 2000 | Warning | Drain mode in grace period |
| 3000 | Error | Drain mode alert (grace exceeded) |
Run drainctl audit-setup as admin. On domain-joined machines, also configure the GPO (see
Audit Setup).
Check the Application Event Log for DrainCtl errors (Event ID 3002). Common causes:
The service isn't running. drainctl check falls back to direct registry read + file-based
audit. Start the service: Start-Service DrainCtl.
drainctl notify status — verify URLs are setdrainctl notify test — sends a test messageThe server's DrainCtl service isn't reporting. Check:
Get-Service DrainCtldashboard.url set in
%ProgramData%\LISS Technologies\LISSTech DrainCtl\config.json?
Test-NetConnection dashboard-server -Port 49470
Windows Authentication (Kerberos) issue:
http://server.domain.com:49470) — Kerberos needs the
full hostname
The reporting server isn't registered. Run drainctl register https://dashboard:49470 on
that server.
Open an issue on GitHub or contact LISS Technologies support.