feat: Add winexec helper for running PowerShell from the agent

This commit is contained in:
2026-08-13 10:26:31 +00:00
parent 38a731f269
commit 057c193e26
3 changed files with 75 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
// Package winexec runs PowerShell on Windows hosts.
//
// It exists because three subsystems — updates, workload collection and
// workload logs — all need the same invocation, and because getting a
// multi-line script past Go quoting, cmd.exe quoting and PowerShell's own
// parser is a problem worth solving once.
package winexec
import (
"encoding/base64"
"unicode/utf16"
)
// EncodeCommand renders a script for powershell.exe -EncodedCommand: UTF-16LE,
// no byte-order mark, base64. This is deliberately free of build tags so it is
// tested on a Linux development machine like every other pure function here.
func EncodeCommand(script string) string {
units := utf16.Encode([]rune(script))
b := make([]byte, 0, len(units)*2)
for _, u := range units {
b = append(b, byte(u), byte(u>>8))
}
return base64.StdEncoding.EncodeToString(b)
}
+20
View File
@@ -0,0 +1,20 @@
package winexec
import "testing"
func TestEncodeCommand(t *testing.T) {
// "hi" as UTF-16LE is 68 00 69 00, which base64-encodes to aABpAA==.
if got := EncodeCommand("hi"); got != "aABpAA==" {
t.Fatalf("EncodeCommand(hi) = %q, want aABpAA==", got)
}
}
func TestEncodeCommandMultiline(t *testing.T) {
// Only that it round-trips through the same encoding PowerShell expects:
// every ASCII byte followed by a zero byte, no BOM.
got := EncodeCommand("a\nb")
want := "YQAKAGIA"
if got != want {
t.Fatalf("EncodeCommand = %q, want %q", got, want)
}
}
+31
View File
@@ -0,0 +1,31 @@
package winexec
import (
"context"
"fmt"
"os/exec"
"strings"
)
// Run executes a PowerShell script and returns its stdout.
//
// powershell.exe rather than pwsh: everything this agent runs through here
// touches Windows Update COM or CIM, both of which are most reliable under
// Windows PowerShell 5.1, and 5.1 is present on every supported Windows while
// pwsh is an optional install.
func Run(ctx context.Context, script string) (string, error) {
cmd := exec.CommandContext(ctx, "powershell.exe",
"-NoProfile", "-NonInteractive", "-EncodedCommand", EncodeCommand(script))
out, err := cmd.Output()
if err != nil {
if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
return "", fmt.Errorf("powershell: %s", strings.TrimSpace(string(ee.Stderr)))
}
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("powershell: timed out")
}
return "", fmt.Errorf("powershell: %w", err)
}
return string(out), nil
}