WindowsCapabilities
Per-machine opt-in switches for the Windows post-install capabilities that a plain NETSetup install does NOT turn on by default (remote management, the WSL2 runtime, extra local-group membership, and the OU a freshly joined computer object belongs in).
Persisted to JSON - every field is a primitive or a plain string collection on purpose, exactly like OuSubnets. NOTHING here may become an interface or abstract type: Machine.Hardware already proves what that costs (System.Text.Json throws "Deserialization of interface or abstract types is not supported" and the config becomes unloadable for EVERY guest).
Consumed by Stage3Windows during install and reachable by hand through the matching CLI verbs (enable-winrm, install-wsl, add-group-member, join-domain) so a node that is already up can be brought to the same state without a reinstall.
public class WindowsCapabilities
1. Properties
1.1. AutoLogonUser
Username of the machine-local account Windows logs on automatically at boot, so that per-user applications (Docker Desktop, see DockerAutoStart) have a session to run in. Must name a User declared on the machine in the config - the password is read from THAT entity and is never a literal. Null disables autologon.
SECURITY: enabling this writes the account’s password in clear text to HKLM\...\Winlogon\DefaultPassword, where any local administrator can read it, and it leaves a logged-on console session on the machine. NETSetup locks that desktop immediately after logon, but the trade-off is real: only declare it for lab or build nodes whose local administrator password is not reused anywhere that matters.
public string AutoLogonUser { get; set; }
1.2. DockerAutoStart
Makes Docker Desktop’s engine available after an unattended reboot.
Docker Desktop is a per-USER desktop application, not a service: every com.docker.backend process lives inside an interactive session. With nobody logged on there is no engine at all, so a build node that reboots comes back with Docker "not coming up" - measured on devBuilder05, where every Docker process sat in the RDP session and vanished with it.
Turning this on requires AutoLogonUser, because the only way to guarantee a session exists is to create one. See DockerAutoStartHelper for what gets configured and for the security trade-off that comes with it.
public bool DockerAutoStart { get; set; }
1.3. DomainOuPath
Optional distinguished name of the OU the computer object is created in at domain-join time, e.g. "OU=Devices,OU=OSISA,DC=corp,DC=osisa,DC=com". Null keeps the domain default (CN=Computers), which is what left devBuilder04 outside the GPO scope that grants local admin. Applied by the Add-Computer -OUPath join path.
public string DomainOuPath { get; set; }
1.4. EnableWinRm
Enables PowerShell Remoting / WinRM on the node (HTTP listener, service auto-start, firewall). Off by default - only turn it on for machines that are actually managed remotely, since it opens an inbound administrative path.
public bool EnableWinRm { get; set; }
1.5. InstallWsl
Installs the WSL2 runtime (Microsoft-Windows-Subsystem-Linux
VirtualMachinePlatform + the WSL kernel). Needed by Docker Desktop’s Linux engine, which is why a build node that runs containers cannot be provisioned without it. Requires a reboot.
public bool InstallWsl { get; set; }
1.6. LocalGroupMembers
Extra local-group membership to apply after the domain join. Key = local group name (e.g. "Administrators", "docker-users", "Remote Desktop Users"), value = principals to add, either DOMAIN\Principal or a plain local username. Group and principal names are resolved by SID where Windows offers it, so a localized node (German Administratoren) still works - see LocalGroupHelper.
public Dictionary<string, List<string>> LocalGroupMembers { get; set; }
1.7. LocalPackageFeedPath
Directory backing the LocalBuild NuGet source, e.g. "C:\_gh\packages". Created empty when missing. NuGet fails the ENTIRE restore when a declared local source does not exist (NU1301: Die lokale Quelle ... ist nicht vorhanden), whereas an empty one simply matches nothing and lets the remaining sources answer. Null skips the directory entirely.
public string LocalPackageFeedPath { get; set; }
1.8. PackageFeedCredentialUser
Username of a User declared on the machine whose credentials are published as the GITHUB_PACKAGES_USER / GITHUB_PACKAGES_TOKEN MACHINE environment variables, which is what the repository’s NuGet.Config reads to authenticate against the GitHub Packages feed. Null leaves them unset. The token is read from THAT entity and is never a literal.
Without it a node runs CI jobs but cannot BUILD: restore dies with 401 (Unauthorized) on the first osisa package. A workflow-level GITHUB_TOKEN does not substitute - the config reads the GITHUB_PACKAGES_* pair by name. Measured on devRunner02, run 34477949026.
SECURITY: the token ends up as a CLEAR-TEXT machine-scope environment variable, readable by every account on the node, and it travels there on a child process command line (Base64, visible through Win32_Process.CommandLine while the call runs). Same trade-off class as AutoLogonUser. Only declare it for lab or build nodes, and only with a token whose scope you are willing to expose - never a personal token with org-wide rights.
public string PackageFeedCredentialUser { get; set; }
1.9. WslInstallerUrl
Optional URL of the WSL2 installer MSI (github.com/microsoft/WSL releases, e.g. wsl.2.7.12.0.x64.msi). Needed because wsl --install deploys an MSIX, and Windows REFUSES MSIX deployment under LOCAL SYSTEM - which is exactly how NETSetup runs. Measured on devBuilder05: DISM reports both features Enabled and wsl.exe still answers "Windows-Subsystem fuer Linux ist nicht installiert", so Docker Desktop has a CLI and no Linux engine. Same class of failure as the winget-via-Chocolatey one. An MSI installs fine as SYSTEM, so pointing this at one is what actually gets a working runtime. Null falls back to wsl --install, which only works when something runs it interactively.
public string WslInstallerUrl { get; set; }