Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Nuances: Windows

What could surprise you here, why it is so, and what to do. Each entry has a level, 1 (day one) to 5 (expert): the index lists them all by level.

The installer isn't signed yet: SmartScreen says "unknown publisher" (level 1)

  • What: before v1.0 the MSI and ductus.exe carry no code-signing (Authenticode) signature. Run from a download, Windows SmartScreen shows "Windows protected your PC" and "unknown publisher"; some endpoint protection blocks unsigned installers outright. A silent install (msiexec /i … /qn) from a deployment tool doesn't show the dialog, but the file is the same.
  • Why: a code-signing certificate is a v1.0 step; until then the builds are development builds.
  • What to do: check the file before running it: its SHA-256 (Get-FileHash ductus-0.6.0-x64.msi) against the release's published checksums, and where GitHub attests the build, gh attestation verify ductus-0.6.0-x64.msi -R thorst/ductus. Then "More info" → "Run anyway", or Unblock-File it first. Ask your endpoint team to allow it by hash rather than by publisher.

The service's account: the default, a gMSA, or a named account (level 2)

  • What: the installer runs the service as NT SERVICE\Ductus (a virtual account: no password, and on the network it is the computer, HOSPITAL\SERVER01$). msiexec /i ductus.msi SERVICEACCOUNT=HOSPITAL\svc-ductus$ installs it as a group managed service account (gMSA) instead: Windows keeps its password, so nothing secret is ever typed, and upgrades keep it. An account with a password (HOSPITAL\svc-ductus, or .\name on this computer) works too, but the installer can't take its password (installer properties end up in the install log): give it with ductus service account 'HOSPITAL\svc-ductus' (it asks; --password-stdin for scripts). ductus service account with no name says who the service runs as.
  • Why: the account is what SQL Server, file shares and anything else on the network see. The default is enough when Ductus logs in to nothing with Windows; a gMSA gives the engine its own identity without a password anyone has to keep.
  • What to do: lead with the default, or a gMSA when the engine needs its own identity (a DBA's Windows login, a share): one step, kept across upgrades. Use a password account only where a gMSA isn't possible, and plan for the step after each upgrade (below). Changing the account keeps the data folder's permissions working: the service's own SID (NT SERVICE\Ductus) is in its token whatever account runs it, and that is what the permissions name.

An upgrade can't keep an account's password: the service waits for it (level 2)

  • What: an upgrade removes the service and installs it again, so a password given earlier is gone. The installer keeps the account's name, leaves the service stopped, and writes to the Event Log (event 1005) what to run: ductus service account 'HOSPITAL\svc-ductus' --start. The default account and gMSAs have no password and start again as before.
  • Why: Windows doesn't let anyone read a service's password back, and a password can't be an installer property.
  • What to do: in a scripted upgrade, follow msiexec /i ductus.msi /qn with ductus service account NAME --password-stdin --start (the password from your vault on stdin). Or move to a gMSA.

"Log on as a service": the installer grants it, Group Policy can take it away (level 3)

  • What: an account needs the "Log on as a service" right to run a service (the computer's own accounts have it already). The installer and ductus service account grant it on this computer; ductus service account also checks the password with a service logon before changing anything, and refuses an account Group Policy denies ("Deny log on as a service"). In a domain where Group Policy sets "Log on as a service", the next policy refresh (within about 90 minutes, and at every restart) replaces the local list, and a service whose account isn't in the policy's list fails to start with error 1069, "the service did not start due to a logon failure".
  • Why: that right is security policy, and in many hospitals the domain owns it.
  • What to do: where Group Policy manages the right, ask whoever manages it to add the service's account (or a group it's in) to "Log on as a service" for the Ductus servers before the upgrade or account change. ductus service account shows whether the account has the right itself and whether it is denied.

An upgrade starts the service again, unless it was stopped on purpose (level 2)

  • What: an upgrade stops the service, replaces the program and starts it again when it's configured (ductus.env exists). A service someone stopped (services.msc, Stop-Service) stays stopped: a stop asked of the service leaves a stopped file in C:\ProgramData\Ductus\logs, the installer looks for it, and the next start removes it. Windows shutting down leaves none, so a server restarted before an upgrade still starts.
  • Why: an engine stopped for a reason (a receiver's outage, a migration) shouldn't come back because a new version was installed.
  • What to do: nothing; start it yourself when you're ready. To have an upgrade start a stopped service, start it first (or delete the stopped file). A service killed rather than stopped (Task Manager) leaves no file and an upgrade starts it.

What goes to the Event Log, and what only to the files (level 2)

  • What: the service writes to the Application log, source Ductus, with its own text in Event Viewer: 1000 started (version, project, the database without its login), 1001 stopped cleanly, 1002 stopped before finishing the messages in hand, 1003 could not start (the reason), 1004 the last run didn't stop cleanly, 1005 the account needs its password, 1006 stopped on an error, 1007 the account was changed (by whom), 1010 an interface stopped by the runaway guard, 1020 a critical alert fired, and 1100 any other ERROR line, each kind and target (interface, destination, alert) at most once a minute, the next one saying "and N more like it since 14:02". Warnings and information otherwise stay in the files (C:\ProgramData\Ductus\logs, 14 days).
  • Why: the Event Log is what Windows tools watch (SCOM, a Splunk forwarder, Event Viewer); a flapping receiver logging an error every second would bury everything else there.
  • What to do: point your monitoring at source Ductus, events 1002–1006, 1010, 1020 and 1100; read the files for the detail. Only the service writes events (ductus run in a terminal doesn't). Uninstalling removes the source, so older events then show "the description cannot be found".
  • Upgrades: the text lives in ductus.exe, so a program that has shown our events (Event Viewer left open, a script's Get-WinEvent, a monitoring agent that renders messages) keeps ductus.exe open, and an upgrade or uninstall waits for it: measured 152 s instead of 1 s on a machine where Group Policy turns Restart Manager off (DisableAutomaticApplicationShutdown). On that machine an upgrade over a running service waited as long too (~2.5 min; over a stopped one, none): Windows Installer checks for files in use before it stops services. The upgrade finishes either way. Close Event Viewer before upgrading; to save the wait, stop the service first (Stop-Service Ductus, then Start-Service after: a service you stopped stays stopped). Where Restart Manager is on (Windows's default), there's no wait for the service, but Windows closes any other program running ductus.exe (a ductus sink or gen left open in a terminal), together with the terminal it runs in.

The Windows service can only touch what you give it (level 2)

  • What: the service runs as the virtual account NT SERVICE\Ductus (or the account you gave it), which can read C:\ProgramData\Ductus (settings, project) and write its logs there, and nothing else. A file or SFTP interface that moves, deletes or writes files outside it (an inbox on D:\Interfaces, an archive folder, an output share) fails with "access denied" until you allow it. Settings (ductus.env) hold the database password, so only administrators, SYSTEM and the service can read that folder.
  • Why: a service that can write anywhere turns one bad interface into a problem for the whole server.
  • What to do: grant the service the folders interfaces use, as narrowly as you can: icacls "D:\Interfaces" /grant "NT SERVICE\Ductus:(OI)(CI)M" (that works whatever account runs the service: its own SID is in its token). For a network share, grant the computer account (DOMAIN\SERVER$), since a virtual account authenticates as the machine, or the gMSA when the service runs as one. ductus doctor in a terminal runs as you, not as the service, so it can pass where the service fails: the service's log (C:\ProgramData\Ductus\logs) shows the "access denied".

A refused connection takes about a second on Windows, and localhost may be ::1 first (level 3)

  • What: when nothing listens at an address, Linux says "refused" at once; Windows sends the connection request again, half a second apart, before it says so (~2 s by default). The engine's MLLP destinations, SQL Server connections and doctor's checks ask Windows for two retries instead: refused in ~1 s, and a request lost on the way is still sent twice more (given up after ~7 s). Other connections (HTTP destinations, fetch(), api(), SFTP, LDAP, mail) keep Windows's ~2 s. A name with several addresses is tried one address at a time, and localhost is ::1 before 127.0.0.1: a receiver listening on 127.0.0.1 only costs that second at every new MLLP connection (once per reconnect, since connections stay open).
  • Why: Windows treats a refusal like a lost packet (a busy server may refuse and accept a moment later). No retries at all would make one lost packet a failed connection; measured on Windows 11: refused 2.03 s by default, ~1 s with two retries.
  • What to do: name the receiver by the address it listens on (127.0.0.1, not localhost, for an IPv4-only receiver on the same server). Keep an MLLP destination's timeout_ms above ~2 s on Windows so a reconnect fits in it. Doctor tells a refused port ("refused: ask its owner whether it's up") from dropped traffic ("no answer: a firewall") as on Linux.

Certificate files: write the drive (level 1)

  • What: in resources.yaml, cert_file, key_file and ca_file with a drive or a share (C:\certs\mllp.pem, \\vault\certs\mllp.key) are read as written; anything else is relative to the project folder. A path written for Linux (/etc/ductus/mllp.key) has no drive, so Windows reads it from the project's drive (C:\etc\ductus\mllp.key): ductus validate warns and says where.
  • Why: that is what a path without a drive means on Windows.
  • What to do: write the whole path, or keep the files in the project and write them relative to it. In YAML, put Windows paths in single quotes ('C:\certs\mllp.pem') or none: in double quotes a backslash starts an escape. The service account must be able to read them (above).