54 lines
2.3 KiB
Go
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
|
|
}
|