// Copyright 2026 certctl LLC. All rights reserved. // SPDX-License-Identifier: BUSL-1.1 package iis import ( "context" "fmt" "time" "github.com/masterzen/winrm" ) // WinRMConfig holds WinRM connection settings for remote IIS management. // Used when Mode is "winrm" — the proxy agent connects to a remote Windows // server over WinRM and executes PowerShell commands remotely. type WinRMConfig struct { Host string `json:"winrm_host"` // WinRM target hostname or IP (required) Port int `json:"winrm_port"` // WinRM port (default 5985 for HTTP, 5986 for HTTPS) Username string `json:"winrm_username"` // Windows user (e.g., "Administrator") Password string `json:"winrm_password"` // Windows password UseHTTPS bool `json:"winrm_https"` // Use HTTPS (port 5986) instead of HTTP (port 5985) Insecure bool `json:"winrm_insecure"` // Skip TLS certificate verification (for self-signed certs) Timeout int `json:"winrm_timeout"` // Operation timeout in seconds (default 60) } // winrmExecutor implements PowerShellExecutor by running PowerShell commands // on a remote Windows server via WinRM. This enables the proxy agent pattern: // a Linux agent in the same network zone manages Windows IIS servers remotely. type winrmExecutor struct { client *winrm.Client } // newWinRMExecutor creates a WinRM client and returns a PowerShellExecutor. func newWinRMExecutor(cfg *WinRMConfig) (*winrmExecutor, error) { if cfg.Host == "" { return nil, fmt.Errorf("winrm_host is required for WinRM mode") } if cfg.Username == "" { return nil, fmt.Errorf("winrm_username is required for WinRM mode") } if cfg.Password == "" { return nil, fmt.Errorf("winrm_password is required for WinRM mode") } port := cfg.Port if port == 0 { if cfg.UseHTTPS { port = 5986 } else { port = 5985 } } timeout := time.Duration(cfg.Timeout) * time.Second if cfg.Timeout == 0 { timeout = 60 * time.Second } endpoint := winrm.NewEndpoint( cfg.Host, port, cfg.UseHTTPS, cfg.Insecure, nil, // CA cert nil, // Client cert nil, // Client key timeout, ) client, err := winrm.NewClient(endpoint, cfg.Username, cfg.Password) if err != nil { return nil, fmt.Errorf("failed to create WinRM client: %w", err) } return &winrmExecutor{client: client}, nil } // Execute runs a PowerShell script on the remote Windows server via WinRM. // The script is wrapped in powershell.exe invocation on the remote side. func (e *winrmExecutor) Execute(ctx context.Context, script string) (string, error) { // RunPSWithContext returns (stdout, stderr, exitCode, error) stdout, stderr, exitCode, err := e.client.RunPSWithContext(ctx, script) if err != nil { return stdout + stderr, fmt.Errorf("WinRM command failed: %w", err) } if exitCode != 0 { return stdout + stderr, fmt.Errorf("PowerShell exited with code %d: %s", exitCode, stdout+stderr) } return stdout, nil }