Windows (WinRM) module
Deploy any file to any Windows path, then run PowerShell post-deploy scripts. The Windows sibling of the SSH module — same PathSet + ActionSet operator UX, different transport. Distinct from the IIS module, which is dedicated to IIS binding lifecycle.
Type: winrm Version: 1.0.0 Non-reversible
When to pick this module
Pick winrm when you want to:
- Drop a PEM / PFX / DER / arbitrary file at a known Windows path (
C:\inetpub\certs\site.pfx,\\fileserver\share\...). - Run PowerShell after — restart a service, import into a Java keystore via
keytool, run a custom hook script. - Cover Windows targets that aren't IIS: Tomcat, JBoss, custom .NET services, ad-hoc certificate stores.
If your only goal is to update an IIS HTTPS binding, the dedicated IIS module handles binding discovery, app-pool recycling, and old-cert cleanup automatically.
Server-side prerequisites
Run on each Windows host (PowerShell as Administrator):
# Enable WinRM
Enable-PSRemoting -Force
# HTTPS listener (production: replace the self-signed cert with a real one)
winrm quickconfig -transport:https
# Firewall — open both ports if you intend to use both
New-NetFirewallRule -Name 'WinRM-HTTP' -DisplayName 'WinRM HTTP' `
-Enabled True -Direction Inbound -Protocol TCP -LocalPort 5985 -Action Allow
New-NetFirewallRule -Name 'WinRM-HTTPS' -DisplayName 'WinRM HTTPS' `
-Enabled True -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow
# Optional: raise envelope size for large file transfers
Set-Item WSMan:\localhost\MaxEnvelopeSizekb 8192
Authentication methods
NTLM (default)
Works with local accounts and domain accounts. Provide username as just svc-certdeploy (local) or DOMAIN\svc-certdeploy (domain), or set the Domain field on the target separately.
Basic
Username/password sent over the wire. Use only with TLS enabled — the target form warns when you mix Basic with plain HTTP.
Kerberos (first-class)
Set auth_type=kerberos + Domain field (the Kerberos realm, e.g. CORP.LOCAL). The CertAutoPilot host needs a valid krb5.conf for KDC discovery (typical Linux installer ships /etc/krb5.conf; symlink or set the KRB5_CONFIG environment variable for non-default paths). The library auto-derives the SPN as HTTP/<hostname>.
CredSSP (multi-hop delegation) is not supported in this version. Most domain-joined scenarios are covered by Kerberos.
PathSet — where files go
The PathSet (and ActionSet) you bind to a WinRM target must be created with target_kind: "windows". The form's PathSet/ActionSet dropdowns filter to Windows-scoped resources only; the backend rejects mismatched bindings at create/update time. Reusing a Linux PathSet on a WinRM target (or vice versa) is not allowed — POSIX paths and owner/mode have no Windows equivalent.
Identical shape to the SSH module's PathSet. Each entry pairs a content source with an absolute Windows path. Sources:
cert— leaf certificate PEMchain— chain (intermediates) PEMfullchain— leaf + chain PEMprivate_key— private key PEMcombined— leaf + chain + key in one PEM file
Format defaults to pem; der is supported for cert and private_key sources.
Path validation rejects: relative paths, .. traversal segments, null bytes, Windows reserved device names (CON, NUL, PRN, AUX, COM1–9, LPT1–9), forbidden filename characters (< > : " | ? *), and components with trailing dots or spaces (Windows strips them silently). Drive-letter and UNC paths are both accepted.
ActionSet — what runs after
By default the ActionSet runs only when a deployed file actually changes (hash-based skip), so restart/reload actions stay idempotent on no-op redistributes. Set run_always on the ActionSet to run it on every distribution even when nothing changed — for verification/diagnostic actions that must run each time (e.g. printing the deployed certificate). When it runs despite no change the log shows no file changed, running anyway (run_always enabled).
Two modes:
command mode
List of PowerShell commands. Run one-by-one. Optional allowed_commands regex allowlist (defence in depth — operator mistypes a destructive command and the regex blocks it).
script_inline mode
Multi-line script body. Uploaded to a remote temp path ($env:TEMP\certautopilot-XXXX.ps1) via the chunked WriteFile transport, ACL-restricted to the executing user, run with -NoProfile -NonInteractive -ExecutionPolicy Bypass -File, then removed (best-effort cleanup).
Operator picks the shell: powershell.exe (Windows PowerShell 5.1, default — present on every modern Windows Server) or pwsh.exe (PowerShell 7+, must be installed on the target).
Variable substitution
Project variables expand via the ${{ NAME }} placeholder syntax. Values are PowerShell-escape-aware: a hostile project variable value $(Get-Process) is substituted as the literal string '$(Get-Process)' rather than an executable sub-expression. Same threat-model fix as the SSH module's shell-escape work; consult project variables for syntax details and escape semantics.
Worked example: cert + Tomcat reload
PathSet:
C:\Tomcat\conf\ssl\fullchain.pem→ sourcefullchainC:\Tomcat\conf\ssl\privkey.pem→ sourceprivate_key
ActionSet (mode script_inline, shell powershell.exe):
$ErrorActionPreference = 'Stop'
# Convert PEM → JKS for Tomcat
keytool -importkeystore `
-srckeystore "C:\Tomcat\conf\ssl\fullchain.pem" `
-srcstoretype PEM `
-destkeystore "C:\Tomcat\conf\ssl\keystore.jks" `
-deststoretype JKS `
-alias tomcat `
-storepass "${{ KEYSTORE_PASS }}"
Restart-Service -Name Tomcat9
Write-Output "tomcat reloaded with new cert"
Rollback
Not supported by the framework. Same posture as the IIS, NetScaler, F5 BIG-IP, and Webhook modules. The module is generic by design — it cannot know what your ActionSet did (restart a service? reconfigure a binding? import into a Java keystore?), so it cannot reliably undo it. If you need a backout, embed it in your ActionSet: take a .bak with Copy-Item before overwrite, keep a Restore-Item path on hand, or roll service state forward through your existing ops tooling.
Limits + performance
| Knob | Default | Note |
|---|---|---|
| File transfer rate | ~60–300 KB/sec | WAN-RTT bound; chunked base64 + certutil decode. |
| Per-file size cap | 100 MB | Configurable on the target via max_file_size_bytes. Raise only if you genuinely need to push > 100 MB. |
| Per-script size cap | ~256 KB UTF-8 source | UTF-16LE doubles bytes; we cap before WSMan envelope quota. |
| Per-target concurrency | 10 | Module-level; clamped on the upper end. |
| Per-target timeout | 5 min | target_timeout_seconds on the module config. |
| Per-command timeout | 60 sec | command_timeout_seconds on the target; ActionSet timeout_seconds overrides per-command. |
| Output truncation | 16 KB | Per-command stdout+stderr; PowerShell verbosity bounded. |
Error codes
The module emits structured WINRM_* error codes for retry classification:
WINRM_CONNECT— dial / DNS / TLS handshake failure (network class)WINRM_AUTH— 401, NTLM/Kerberos rejection, "Access denied" (auth class)WINRM_TIMEOUT— context deadline exceeded (transient — retried)WINRM_EXEC— non-zero PowerShell exit (transient — retried)WINRM_PS_SYNTAX— ParserError detected in stderr (validation — not retried)WINRM_FILE— file transfer failure (transient — retried)WINRM_VALIDATION— operator config error (path, run_as)WINRM_QUOTA— MaxEnvelopeSize exceeded (permanent — operator must raise the quota)
vs. the IIS module
| Aspect | winrm | iis |
|---|---|---|
| Scope | Generic file deploy + script | IIS binding lifecycle |
| PathSet / ActionSet | Yes | No (uses fixed IIS scripts) |
| Concurrency | Parallel (10 default) | Sequential |
| Auth | NTLM, Basic, Kerberos | NTLM, Basic |
| Rollback | No (operator scripts it in the ActionSet) | No |
| Picks IIS bindings automatically | No (operator scripts it) | Yes |
Multi-certificate hosts
Two mechanisms let one WinRM target serve many certificates:
- Per-distribution PathSet/ActionSet — each certificate's distribution can select its own PathSet (where files land) and ActionSet (what runs afterwards) from the certificate's Distributions → Overrides drawer (
winrm { path_set_id, action_set_id }, with a*default row). A selected override must resolve — a deleted or wrong-kind resource fails the run instead of silently using the target's own libraries. - Certificate-context variables in paths — PathSet paths may contain
${{ CAP_PRIMARY_DOMAIN }},${{ CAP_DOMAIN_SLUG }},${{ CAP_CERT_TOKEN }}or${{ CAP_DEPLOY_NAME }}, rendered per certificate at distribution time:C:\\certs\\${{ CAP_DOMAIN_SLUG }}\\bundle.pfx. UseCAP_DOMAIN_SLUG, notCAP_PRIMARY_DOMAIN, in Windows paths:*is invalid in Windows file names, so a wildcard certificate's primary domain (*.example.com) always fails, while the slug renders asexample-com. Project variables also render; secret variables are rejected in paths; a missing variable fails that target. Note that substitution always applies: a literal${{ ... }}in a path or command must be escaped with a backslash (\${{ ... }}). (WinRM is non-reversible; handle backout in the ActionSet.)
When several certificates share a host, put ${{ CAP_CERT_TOKEN }} or ${{ CAP_DEPLOY_NAME }} in the path (or use distinct PathSets) so files never collide.