diff --git a/internal/runner/hidecmd_other.go b/internal/runner/hidecmd_other.go new file mode 100644 index 0000000..a41ac38 --- /dev/null +++ b/internal/runner/hidecmd_other.go @@ -0,0 +1,7 @@ +//go:build !windows + +package runner + +import "os/exec" + +func hideWindow(cmd *exec.Cmd) {} diff --git a/internal/runner/hidecmd_windows.go b/internal/runner/hidecmd_windows.go new file mode 100644 index 0000000..30e7fd8 --- /dev/null +++ b/internal/runner/hidecmd_windows.go @@ -0,0 +1,14 @@ +//go:build windows + +package runner + +import ( + "os/exec" + "syscall" +) + +// hideWindow prevents the child process from creating a visible console window. +// Without this, powershell.exe opens a full-size window on top of the GUI. +func hideWindow(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} +} diff --git a/internal/runner/runner.go b/internal/runner/runner.go index a85b55b..4bec1d8 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -223,6 +223,7 @@ func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error } cmd := exec.CommandContext(ctx, "powershell.exe", args...) + hideWindow(cmd) // prevent PS console window from appearing over the GUI stdout, err := cmd.StdoutPipe() if err != nil { @@ -237,6 +238,9 @@ func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error scanner := bufio.NewScanner(stdout) for scanner.Scan() { line := scanner.Text() + if skipPSNoiseLine(line) { + continue + } r.onLog(LogLine{ StepID: step.ID, Text: line, @@ -247,6 +251,33 @@ func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error return cmd.Wait() } +// skipPSNoiseLine returns true for PowerShell stderr noise that clutters the log: +// multi-line error blocks (At line:N, CategoryInfo, FullyQualifiedErrorId, etc.), +// blank lines, and VERBOSE: prefix lines already handled by Write-Log. +func skipPSNoiseLine(line string) bool { + trimmed := strings.TrimSpace(line) + if trimmed == "" { + return true + } + for _, prefix := range []string{ + "At line:", + "+ CategoryInfo", + "+ FullyQualifiedErrorId", + "+ PositionMessage", + "VERBOSE:", + "DEBUG:", + } { + if strings.HasPrefix(trimmed, prefix) { + return true + } + } + // PS error continuation lines start with spaces + "+" or "~" + if len(trimmed) > 0 && (trimmed[0] == '+' || strings.HasPrefix(trimmed, "~")) { + return true + } + return false +} + // parseLevel extracts the log level from lines formatted as "[HH:mm:ss] [LEVEL] message". func parseLevel(line string) string { if strings.Contains(line, "] [OK]") {