Operators are still losing the relaunch, so I went and read the code
from before 0430aab — the commit that removed the helper — instead of
theorising again. It was:
Wait-Process -Id <pid>; Start-Sleep -Milliseconds 400;
Start-Process -FilePath <exe> -ArgumentList '--post-update'
Two things in there that the direct launch never had:
1. Start-Process made the new OpsLog a child of PowerShell, which then
exited — so it was detached. The direct launch makes it a child of
the instance that is dying, in the same process group and console.
DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP puts it back on its own,
so nothing aimed at the old process can reach the new one.
2. It slept 400 ms AFTER the old process was gone, before starting
anything. A process's handles are released by the kernel as it dies,
so the mutex is free the moment the wait returns — but the things
around it are not on that clock: the WebView2 user-data lock, the log
file, an antivirus that woke up when the exe was replaced. This is
not a theory about which of those it was; it is the pause being put
back where it was.
Not restored: the PowerShell itself. Defender removed 0.27.14 from a
station as Trojan:Script/Wacatac.H!ml, and "Script/" was that helper —
an unsigned binary replacing itself and spawning a windowless script to
start another executable is, byte for byte, a dropper. Bringing it back
trades this fault for one that deletes the program.
Also: the relaunch now logs the child's pid, which the new instance's
startup.log already records on the other side. Without the pair there is
no telling "the new instance never started" from "it started and gave up
waiting", and those have different causes.
51 lines
2.1 KiB
Go
51 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A relaunch of OpsLog itself must not suppress the new process's window.
|
|
//
|
|
// SysProcAttr is where that damage was done: hideConsole sets HideWindow, which
|
|
// on Windows becomes SW_HIDE in the STARTUPINFO, and Windows applies it to the
|
|
// first top-level window the new process shows. The updated OpsLog started,
|
|
// took the single-instance mutex and connected the rig — invisibly. Two
|
|
// operators on 0.27.23 reported it as "it goes to reload and just fails to
|
|
// load": a process in the task manager, no window, and killing it then starting
|
|
// it by hand working every time.
|
|
//
|
|
// The window flags themselves are asserted in relaunch_windows_test.go, where
|
|
// SysProcAttr has the fields to look at.
|
|
func TestRelaunchCmdPassesItsArgumentsAndFolder(t *testing.T) {
|
|
cmd := relaunchCmd(filepath.Join("C:", "OpsLog", "OpsLog.exe"), "--post-update", "--wait-pid", "1234")
|
|
if len(cmd.Args) != 4 || cmd.Args[1] != "--post-update" || cmd.Args[3] != "1234" {
|
|
t.Errorf("args = %v, want the exe plus the three passed through", cmd.Args)
|
|
}
|
|
// The working directory matters: the new instance keeps its data folder
|
|
// beside the executable, and inheriting the old process's cwd would look for
|
|
// it somewhere else entirely.
|
|
if cmd.Dir != filepath.Join("C:", "OpsLog") {
|
|
t.Errorf("Dir = %q, want the executable's folder", cmd.Dir)
|
|
}
|
|
}
|
|
|
|
// hideConsole is right for the console tools and wrong for OpsLog, and the two
|
|
// live a few lines apart. This is the guard that stops the update path
|
|
// borrowing it again — which is how it broke the first time, when the
|
|
// PowerShell helper was replaced by a direct exec.Command beside them.
|
|
func TestUpdateRelaunchDoesNotHideTheWindow(t *testing.T) {
|
|
src, err := os.ReadFile("update.go")
|
|
if err != nil {
|
|
t.Fatalf("read update.go: %v", err)
|
|
}
|
|
if strings.Contains(string(src), "hideConsole(") {
|
|
t.Error("update.go calls hideConsole — a relaunch of OpsLog must not hide its window (see relaunch.go)")
|
|
}
|
|
if !strings.Contains(string(src), "relaunchCmd(exe,") {
|
|
t.Error("update.go no longer relaunches through relaunchCmd, where that rule is written down")
|
|
}
|
|
}
|