Files
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

54 lines
2.3 KiB
Go

//go:build windows
package main
import (
"os/exec"
"golang.org/x/sys/windows"
)
// hideConsole stops a child process from opening a console window.
//
// THE SYMPTOM: two flashing blue screens, seen again and again. Two, and
// they flash, and they come back — that is the shape of this bug exactly.
// Three Windows paths shell out to powershell.exe:
//
// schedulers_windows.go:44 Get-ScheduledTask (the scheduled-job inventory)
// origin_windows.go:79 Win32_Service 'Schedule' (the scheduler's own pid)
// computer_tree_windows.go:97 UIAutomation (on demand, not on the beat)
//
// The first two sit on the heartbeat path and are each cached for
// schedCacheFor = 10 minutes (schedulers.go:435), so they expire together and
// respawn together: TWO windows, every ten minutes, for ever. The third only
// runs when the host asks to read the screen.
//
// WHY IT ONLY STARTED NOW, AND WHY IT IS 0.24.0's DOING. A console child
// INHERITS its parent's console when the parent has one — started from a
// terminal, these three were invisible for as long as they have existed. 0.24.0
// added relaunchAfterUpdate, which restarts the bastion with DETACHED_PROCESS
// (relaunch_windows.go:47) because Windows has no service to do it. A detached
// process has NO console, so there is none to inherit, and Windows allocates a
// fresh one for each child — which is a window, on screen, in front of whatever
// the user was doing. The same commit that made the bastion survive an update
// is what made its shell-outs visible.
//
// CREATE_NO_WINDOW is the correct flag for a child whose output we capture and
// whose input we never supply: it still gets a console object, so PowerShell
// runs normally, but no window is created for it. HideWindow alone would ask
// the child politely to start minimised and PowerShell does not always honour
// it. Both are set because they cost nothing together.
//
// Not applied to relaunchAfterUpdate: that one sets DETACHED_PROCESS already
// and must keep it — it is the flag that lets the replacement outlive us.
func hideConsole(cmd *exec.Cmd) {
if cmd == nil {
return
}
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &windows.SysProcAttr{}
}
cmd.SysProcAttr.HideWindow = true
cmd.SysProcAttr.CreationFlags |= windows.CREATE_NO_WINDOW
}