//go:build windows package runtime import ( "encoding/base64" "encoding/json" "fmt" "io" "os" "os/exec" "path/filepath" "sort" "strconv" "strings" "sync" "syscall" "time" "unicode/utf16" "unsafe" "golang.org/x/sys/windows" ) const managedProcessHelperFlag = "--run-managed-process-helper" func configureManagedProcessCommand(cmd *exec.Cmd) { // Run may itself be launched by a service or task scheduler job that // terminates its process tree on shutdown. The helper is the durable // owner of the game process, so ask Windows to keep it outside that job. // The helper itself does not need a console: its stdout/stderr are already // durable files. CREATE_NEW_CONSOLE creates a second hidden conhost in a // non-interactive Task Scheduler session and prevents the child pseudo // console from initializing on Windows Server. cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: windows.CREATE_NO_WINDOW | windows.CREATE_BREAKAWAY_FROM_JOB, HideWindow: true} } // Windows console output is collected by a child helper that owns the // pseudo-console. The helper inherits the durable stdout/stderr files, so it // remains attached to the supervised process when Run itself is updated or // restarted. The new Run instance resumes tailing those files by offset. type windowsFileManagedProcess struct { cmd *exec.Cmd helperExecutable string targetPID int } type managedProcessHelperSpec struct { Command ProcessCommand `json:"command"` StopEventName string `json:"stopEventName,omitempty"` StdoutPath string `json:"stdoutPath,omitempty"` StderrPath string `json:"stderrPath,omitempty"` PIDPath string `json:"pidPath,omitempty"` } func startManagedProcess(command ProcessCommand, files managedProcessFiles, stopEventName string) (managedProcess, error) { pidPath := files.stdout.Name() + ".pid" _ = os.Remove(pidPath) body, err := json.Marshal(managedProcessHelperSpec{Command: command, StopEventName: stopEventName, StdoutPath: files.stdout.Name(), StderrPath: files.stderr.Name(), PIDPath: pidPath}) if err != nil { return nil, fmt.Errorf("encode managed process helper spec: %w", err) } payload := base64.RawURLEncoding.EncodeToString(body) executable, err := os.Executable() if err != nil { return nil, fmt.Errorf("resolve Run executable for managed process helper: %w", err) } helperExecutable, err := prepareManagedProcessHelperExecutable(executable, files.stdout.Name()) if err != nil { return nil, fmt.Errorf("prepare managed process helper executable: %w", err) } cmd := exec.Command(helperExecutable, managedProcessHelperFlag, payload) cmd.Stdout = files.stdout cmd.Stderr = files.stderr configureManagedProcessCommand(cmd) if err := cmd.Start(); err != nil { _ = os.Remove(helperExecutable) return nil, err } targetPID := cmd.Process.Pid deadline := time.Now().Add(2 * time.Second) for time.Now().Before(deadline) { if body, readErr := os.ReadFile(pidPath); readErr == nil { if parsed, parseErr := strconv.Atoi(strings.TrimSpace(string(body))); parseErr == nil && parsed > 0 { targetPID = parsed break } } time.Sleep(10 * time.Millisecond) } return &windowsFileManagedProcess{cmd: cmd, helperExecutable: helperExecutable, targetPID: targetPID}, nil } func prepareManagedProcessHelperExecutable(executable, outputPath string) (string, error) { directory := filepath.Dir(outputPath) if err := os.MkdirAll(directory, 0o700); err != nil { return "", err } temporary, err := os.CreateTemp(directory, ".run-managed-helper-*.exe") if err != nil { return "", err } helper := temporary.Name() if err := temporary.Close(); err != nil { _ = os.Remove(helper) return "", err } if err := os.Remove(helper); err != nil { return "", err } if err := copyExecutable(executable, helper); err != nil { _ = os.Remove(helper) return "", err } return helper, nil } func (process *windowsFileManagedProcess) PID() int { return process.cmd.Process.Pid } func (process *windowsFileManagedProcess) TargetPID() int { if process.targetPID > 0 { return process.targetPID } return process.PID() } func (process *windowsFileManagedProcess) Wait() (int, error) { err := process.cmd.Wait() if process.helperExecutable != "" { _ = os.Remove(process.helperExecutable) } if process.cmd.ProcessState == nil { return -1, err } return process.cmd.ProcessState.ExitCode(), err } func (process *windowsFileManagedProcess) Kill() error { return process.cmd.Process.Kill() } // RunManagedProcessHelper is invoked by the same executable in a detached // child process. It is deliberately not a worker mode and does not register // with Platform. func RunManagedProcessHelper(args []string) (bool, int) { if len(args) < 3 || args[1] != managedProcessHelperFlag { return false, 0 } body, err := base64.RawURLEncoding.DecodeString(args[2]) if err != nil { fmt.Fprintf(os.Stderr, "invalid managed process helper payload: %v\n", err) return true, 2 } var spec managedProcessHelperSpec if err := json.Unmarshal(body, &spec); err != nil || len(spec.Command.Args) == 0 { if err == nil { err = fmt.Errorf("managed process command is empty") } fmt.Fprintf(os.Stderr, "invalid managed process helper spec: %v\n", err) return true, 2 } code, err := runManagedProcessHelper(spec) if err != nil { fmt.Fprintf(os.Stderr, "managed process helper failed: %v\n", err) if code == 0 { code = 1 } } return true, code } func runManagedProcessHelper(spec managedProcessHelperSpec) (int, error) { defer scheduleManagedProcessHelperCleanup() stdout, closeStdout, err := openManagedProcessOutput(spec.StdoutPath, os.Stdout) if err != nil { return 1, fmt.Errorf("open managed process stdout: %w", err) } defer closeStdout() stderr, closeStderr, err := openManagedProcessOutput(spec.StderrPath, os.Stderr) if err != nil { return 1, fmt.Errorf("open managed process stderr: %w", err) } defer closeStderr() command := spec.Command stopEventName := spec.StopEventName // The plugin-declared pipes mode uses ordinary inherited handles. The // durable output files are attached directly to the child process, so this // path remains valid when Run itself is updated or restarted. Console mode // is deliberately kept separate for applications that switch from // redirected handles to a Windows console after startup. if command.OutputMode != "console" { return runManagedProcessWithPipes(command, stopEventName, spec.PIDPath, stdout, stderr) } return runManagedProcessWithPseudoConsole(command, stopEventName, spec.PIDPath, stdout, stderr) } func scheduleManagedProcessHelperCleanup() { executable, err := os.Executable() if err != nil || strings.TrimSpace(executable) == "" { return } commandLine := "ping 127.0.0.1 -n 2 >nul & del /f /q " + quoteWindowsCommandArg(executable) cleanup := exec.Command("cmd.exe", "/d", "/c", commandLine) cleanup.Stdout = io.Discard cleanup.Stderr = io.Discard configureManagedProcessCommand(cleanup) _ = cleanup.Start() } func openManagedProcessOutput(path string, fallback *os.File) (io.Writer, func(), error) { if strings.TrimSpace(path) == "" { return fallback, func() {}, nil } file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) if err != nil { return nil, nil, err } return file, func() { _ = file.Close() }, nil } func runManagedProcessWithPipes(command ProcessCommand, stopEventName string, pidPath string, stdout io.Writer, stderr io.Writer) (int, error) { if len(command.Args) == 0 { return 1, fmt.Errorf("managed process command is empty") } application := command.Args[0] if strings.EqualFold(application, "cmd.exe") { if comspec := os.Getenv("ComSpec"); comspec != "" { application = comspec } } cmd := exec.Command(application, command.Args[1:]...) cmd.Dir = command.WorkDir cmd.Env = os.Environ() for key, value := range command.Env { cmd.Env = append(cmd.Env, key+"="+value) } cmd.Stdout = stdout cmd.Stderr = stderr configureManagedProcessCommand(cmd) if err := cmd.Start(); err != nil { return 1, fmt.Errorf("start managed process with pipes: %w", err) } if err := writeManagedProcessPID(pidPath, cmd.Process.Pid); err != nil { _ = cmd.Process.Kill() _ = cmd.Wait() return 1, fmt.Errorf("persist managed process pid: %w", err) } processHandle, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE|windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(cmd.Process.Pid)) if err != nil { _ = cmd.Process.Kill() _ = cmd.Wait() return 1, fmt.Errorf("open managed process handle: %w", err) } job, err := createManagedProcessJob(processHandle) _ = windows.CloseHandle(processHandle) if err != nil { _ = cmd.Process.Kill() _ = cmd.Wait() return 1, err } defer windows.CloseHandle(job) stopCleanup, err := watchManagedProcessStop(job, stopEventName) if err != nil { _ = windows.TerminateJobObject(job, 1) _ = cmd.Wait() return 1, err } defer stopCleanup() waitErr := cmd.Wait() if cmd.ProcessState == nil { if waitErr != nil { return 1, waitErr } return 1, fmt.Errorf("managed process has no exit state") } exitCode := cmd.ProcessState.ExitCode() if waitErr != nil { // exec.Cmd returns *exec.ExitError for a normal non-zero exit. Return // the actual code without treating it as an internal supervisor error; // the outer helper will propagate the code to the durable supervisor. if _, ok := waitErr.(*exec.ExitError); !ok { return 1, waitErr } } if exitCode > 255 { return 1, fmt.Errorf("managed process exited with code %d", exitCode) } return exitCode, nil } func writeManagedProcessPID(path string, pid int) error { if strings.TrimSpace(path) == "" || pid <= 0 { return nil } temporary := path + ".tmp" if err := os.WriteFile(temporary, []byte(strconv.Itoa(pid)+"\n"), 0o600); err != nil { return err } if err := os.Rename(temporary, path); err != nil { _ = os.Remove(temporary) return err } return nil } type synchronizedOutputWriter struct { mu sync.Mutex w io.Writer } func (writer *synchronizedOutputWriter) Write(body []byte) (int, error) { writer.mu.Lock() defer writer.mu.Unlock() return writer.w.Write(body) } func runManagedProcessWithPseudoConsole(command ProcessCommand, stopEventName string, pidPath string, stdout io.Writer, stderr io.Writer) (int, error) { // Direct console executables use a pseudo-console so programs that require // a console handle still have a bounded, hidden console surface. This is // also used for plugin-declared console capture around a Windows shell. inputRead, inputWrite, err := createPseudoConsolePipe() if err != nil { return 1, fmt.Errorf("create pseudo-console input pipe: %w", err) } outputRead, outputWrite, err := createPseudoConsolePipe() if err != nil { closePseudoConsoleHandles(inputRead, inputWrite) return 1, fmt.Errorf("create pseudo-console output pipe: %w", err) } var console windows.Handle if err := windows.CreatePseudoConsole(windows.Coord{X: 160, Y: 50}, inputRead, outputWrite, 0, &console); err != nil { closePseudoConsoleHandles(inputRead, inputWrite, outputRead, outputWrite) return 1, fmt.Errorf("create pseudo-console: %w", err) } _ = windows.CloseHandle(inputRead) _ = windows.CloseHandle(outputWrite) attributes, err := windows.NewProcThreadAttributeList(1) if err != nil { windows.ClosePseudoConsole(console) closePseudoConsoleHandles(inputWrite, outputRead) return 1, fmt.Errorf("create pseudo-console process attributes: %w", err) } defer attributes.Delete() // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE expects the HPCON handle value as // lpValue, not the address of the local variable that stores the handle. if err := attributes.Update(windows.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, unsafe.Pointer(uintptr(console)), unsafe.Sizeof(console)); err != nil { windows.ClosePseudoConsole(console) closePseudoConsoleHandles(inputWrite, outputRead) return 1, fmt.Errorf("configure pseudo-console process attributes: %w", err) } applicationPath := command.Args[0] commandIsCmd := strings.EqualFold(applicationPath, "cmd.exe") if commandIsCmd { applicationPath = os.Getenv("ComSpec") if applicationPath == "" { applicationPath = `C:\Windows\System32\cmd.exe` } } // CreateProcess receives both an application name and a command line. // Keep the command-line program name identical to the resolved application // name; cmd.exe can fail with STATUS_DLL_INIT_FAILED when the former is // left as the short name while the latter is an absolute path. commandArgs := append([]string(nil), command.Args...) commandArgs[0] = applicationPath commandLine, err := windows.UTF16FromString(windows.ComposeCommandLine(commandArgs)) if err != nil { windows.ClosePseudoConsole(console) closePseudoConsoleHandles(inputWrite, outputRead) return 1, fmt.Errorf("encode managed process command: %w", err) } var applicationName *uint16 if !commandIsCmd && strings.ContainsAny(applicationPath, `:\`) { applicationName, err = windows.UTF16PtrFromString(applicationPath) if err != nil { windows.ClosePseudoConsole(console) closePseudoConsoleHandles(inputWrite, outputRead) return 1, fmt.Errorf("encode managed process executable: %w", err) } } environment, err := managedProcessEnvironment(command.Env) if err != nil { windows.ClosePseudoConsole(console) closePseudoConsoleHandles(inputWrite, outputRead) return 1, fmt.Errorf("encode managed process environment: %w", err) } var workDir *uint16 if command.WorkDir != "" { workDir, err = windows.UTF16PtrFromString(command.WorkDir) if err != nil { windows.ClosePseudoConsole(console) closePseudoConsoleHandles(inputWrite, outputRead) return 1, fmt.Errorf("encode managed process working directory: %w", err) } } // A pseudo-console supplies the child's console surface itself; do not // combine it with STARTF_USESHOWWINDOW, which makes cmd.exe fail during // initialization on some non-interactive Windows Server sessions. startup := &windows.StartupInfoEx{StartupInfo: windows.StartupInfo{Cb: uint32(unsafe.Sizeof(windows.StartupInfoEx{}))}, ProcThreadAttributeList: attributes.List()} var processInfo windows.ProcessInformation // The helper has already detached from any parent job. A second // CREATE_BREAKAWAY_FROM_JOB on the pseudo-console client is rejected by // some Windows Server builds during console initialization and surfaces as // STATUS_DLL_INIT_FAILED from the otherwise valid child process. if err := windows.CreateProcess(applicationName, &commandLine[0], nil, nil, false, windows.CREATE_UNICODE_ENVIRONMENT|windows.EXTENDED_STARTUPINFO_PRESENT, environment, workDir, &startup.StartupInfo, &processInfo); err != nil { windows.ClosePseudoConsole(console) closePseudoConsoleHandles(inputWrite, outputRead) return 1, fmt.Errorf("start managed process in pseudo-console: %w", err) } _ = windows.CloseHandle(processInfo.Thread) _ = windows.CloseHandle(inputWrite) if err := writeManagedProcessPID(pidPath, int(processInfo.ProcessId)); err != nil { _ = windows.TerminateProcess(processInfo.Process, 1) _ = windows.CloseHandle(processInfo.Process) windows.ClosePseudoConsole(console) _ = windows.CloseHandle(outputRead) return 1, fmt.Errorf("persist managed process pid: %w", err) } job, err := createManagedProcessJob(processInfo.Process) if err != nil { _ = windows.TerminateProcess(processInfo.Process, 1) _ = windows.CloseHandle(processInfo.Process) windows.ClosePseudoConsole(console) _ = windows.CloseHandle(outputRead) return 1, err } defer windows.CloseHandle(job) stopCleanup, err := watchManagedProcessStop(job, stopEventName) if err != nil { _ = windows.TerminateJobObject(job, 1) windows.ClosePseudoConsole(console) _ = windows.CloseHandle(processInfo.Process) return 1, err } defer stopCleanup() outputFile := os.NewFile(uintptr(outputRead), "run-pseudo-console-output") if outputFile == nil { _ = windows.TerminateProcess(processInfo.Process, 1) _ = windows.CloseHandle(processInfo.Process) windows.ClosePseudoConsole(console) return 1, fmt.Errorf("open pseudo-console output") } outputDone := make(chan struct{}) outputWriter := &synchronizedOutputWriter{w: stdout} go func() { _, _ = io.Copy(outputWriter, outputFile) close(outputDone) }() _, waitErr := windows.WaitForSingleObject(processInfo.Process, windows.INFINITE) var exitCode uint32 if waitErr == nil { waitErr = windows.GetExitCodeProcess(processInfo.Process, &exitCode) } windows.ClosePseudoConsole(console) <-outputDone _ = outputFile.Close() _ = windows.CloseHandle(processInfo.Process) if waitErr != nil { return 1, waitErr } if exitCode > 255 { return 1, fmt.Errorf("managed process exited with code %d", exitCode) } return int(exitCode), nil } func runManagedShellWithPipes(command ProcessCommand, stopEventName string, stdout io.Writer, stderr io.Writer) (int, error) { inputRead, inputWrite, err := createPseudoConsolePipe() if err != nil { return 1, fmt.Errorf("create managed shell input pipe: %w", err) } stdoutRead, stdoutWrite, err := createPseudoConsolePipe() if err != nil { closePseudoConsoleHandles(inputRead, inputWrite) return 1, fmt.Errorf("create managed shell stdout pipe: %w", err) } stderrRead, stderrWrite, err := createPseudoConsolePipe() if err != nil { closePseudoConsoleHandles(inputRead, inputWrite, stdoutRead, stdoutWrite) return 1, fmt.Errorf("create managed shell stderr pipe: %w", err) } closeOnError := func() { closePseudoConsoleHandles(inputRead, inputWrite, stdoutRead, stdoutWrite, stderrRead, stderrWrite) } if err := windows.SetHandleInformation(stdoutRead, windows.HANDLE_FLAG_INHERIT, 0); err != nil { closeOnError() return 1, fmt.Errorf("make managed shell stdout pipe private: %w", err) } if err := windows.SetHandleInformation(stderrRead, windows.HANDLE_FLAG_INHERIT, 0); err != nil { closeOnError() return 1, fmt.Errorf("make managed shell stderr pipe private: %w", err) } applicationPath := os.Getenv("ComSpec") if applicationPath == "" { applicationPath = `C:\Windows\System32\cmd.exe` } commandArgs := append([]string(nil), command.Args...) commandArgs[0] = applicationPath commandLine, err := windows.UTF16FromString(windows.ComposeCommandLine(commandArgs)) if err != nil { closeOnError() return 1, fmt.Errorf("encode managed shell command: %w", err) } applicationName, err := windows.UTF16PtrFromString(applicationPath) if err != nil { closeOnError() return 1, fmt.Errorf("encode managed shell executable: %w", err) } environment, err := managedProcessEnvironment(command.Env) if err != nil { closeOnError() return 1, fmt.Errorf("encode managed shell environment: %w", err) } var workDir *uint16 if command.WorkDir != "" { workDir, err = windows.UTF16PtrFromString(command.WorkDir) if err != nil { closeOnError() return 1, fmt.Errorf("encode managed shell working directory: %w", err) } } startup := &windows.StartupInfo{Cb: uint32(unsafe.Sizeof(windows.StartupInfo{})), Flags: windows.STARTF_USESTDHANDLES | windows.STARTF_USESHOWWINDOW, ShowWindow: windows.SW_HIDE, StdInput: inputRead, StdOutput: stdoutWrite, StdErr: stderrWrite} var processInfo windows.ProcessInformation if err := windows.CreateProcess(applicationName, &commandLine[0], nil, nil, true, windows.CREATE_BREAKAWAY_FROM_JOB|windows.CREATE_NO_WINDOW|windows.CREATE_UNICODE_ENVIRONMENT, environment, workDir, startup, &processInfo); err != nil { closeOnError() return 1, fmt.Errorf("start managed shell: %w", err) } _ = windows.CloseHandle(processInfo.Thread) closePseudoConsoleHandles(inputRead, inputWrite, stdoutWrite, stderrWrite) job, err := createManagedProcessJob(processInfo.Process) if err != nil { _ = windows.TerminateProcess(processInfo.Process, 1) _ = windows.CloseHandle(processInfo.Process) closePseudoConsoleHandles(stdoutRead, stderrRead) return 1, err } defer windows.CloseHandle(job) stopCleanup, err := watchManagedProcessStop(job, stopEventName) if err != nil { _ = windows.TerminateJobObject(job, 1) _ = windows.CloseHandle(processInfo.Process) return 1, err } defer stopCleanup() stdoutFile := os.NewFile(uintptr(stdoutRead), "run-managed-shell-stdout") stderrFile := os.NewFile(uintptr(stderrRead), "run-managed-shell-stderr") if stdoutFile == nil || stderrFile == nil { if stdoutFile != nil { _ = stdoutFile.Close() } if stderrFile != nil { _ = stderrFile.Close() } _ = windows.TerminateProcess(processInfo.Process, 1) _ = windows.CloseHandle(processInfo.Process) return 1, fmt.Errorf("open managed shell output pipes") } outputDone := make(chan struct{}) outputWriter := &synchronizedOutputWriter{w: stdout} go func() { _, _ = io.Copy(outputWriter, stdoutFile) _ = stdoutFile.Close() close(outputDone) }() errorDone := make(chan struct{}) errorWriter := &synchronizedOutputWriter{w: stderr} go func() { _, _ = io.Copy(errorWriter, stderrFile) _ = stderrFile.Close() close(errorDone) }() _, waitErr := windows.WaitForSingleObject(processInfo.Process, windows.INFINITE) var exitCode uint32 if waitErr == nil { waitErr = windows.GetExitCodeProcess(processInfo.Process, &exitCode) } <-outputDone <-errorDone _ = windows.CloseHandle(processInfo.Process) if waitErr != nil { return 1, waitErr } if exitCode > 255 { return 1, fmt.Errorf("managed shell exited with code %d", exitCode) } return int(exitCode), nil } func createManagedProcessJob(process windows.Handle) (windows.Handle, error) { job, err := windows.CreateJobObject(nil, nil) if err != nil { return 0, fmt.Errorf("create managed process job: %w", err) } if err := windows.AssignProcessToJobObject(job, process); err != nil { _ = windows.CloseHandle(job) return 0, fmt.Errorf("assign managed process to job: %w", err) } return job, nil } func watchManagedProcessStop(job windows.Handle, name string) (func(), error) { if strings.TrimSpace(name) == "" { return func() {}, nil } eventName, err := windows.UTF16PtrFromString(name) if err != nil { return nil, fmt.Errorf("encode managed process stop event: %w", err) } event, eventErr := windows.CreateEvent(nil, 1, 0, eventName) if eventErr != nil && eventErr != windows.ERROR_ALREADY_EXISTS { if event != 0 { _ = windows.CloseHandle(event) } return nil, fmt.Errorf("create managed process stop event: %w", eventErr) } stop := make(chan struct{}) done := make(chan struct{}) go func() { defer close(done) for { select { case <-stop: return default: } result, waitErr := windows.WaitForSingleObject(event, 100) if waitErr != nil { return } if result == windows.WAIT_OBJECT_0 { _ = windows.TerminateJobObject(job, 1) return } } }() return func() { close(stop) <-done _ = windows.CloseHandle(event) }, nil } func requestManagedProcessStop(identity ProcessIdentity) error { if strings.TrimSpace(identity.StopEventName) != "" { name, err := windows.UTF16PtrFromString(identity.StopEventName) if err != nil { return err } deadline := time.Now().Add(2 * time.Second) for time.Now().Before(deadline) { event, openErr := windows.OpenEvent(windows.EVENT_MODIFY_STATE|windows.SYNCHRONIZE, false, name) if openErr == nil { setErr := windows.SetEvent(event) _ = windows.CloseHandle(event) return setErr } time.Sleep(25 * time.Millisecond) } } return fmt.Errorf("managed process stop event is unavailable") } func forceManagedProcessStop(identity ProcessIdentity) error { if err := requestManagedProcessStop(identity); err == nil { return nil } if identity.PID <= 0 { return fmt.Errorf("managed process pid is invalid") } // A recovered process can outlive its helper. In that case no stop-event // watcher remains. Target only the persisted process PID here: /T would // recursively terminate descendants and can take down a game process that // was deliberately kept alive while Run is being restarted or updated. command := exec.Command("taskkill.exe", "/PID", strconv.Itoa(identity.PID), "/F") command.Stdout = io.Discard command.Stderr = io.Discard return command.Run() } func createPseudoConsolePipe() (windows.Handle, windows.Handle, error) { var readHandle, writeHandle windows.Handle // ConPTY owns these handles through its internal duplication. Keeping the // pipe ends non-inheritable matches the Windows ConPTY contract and avoids // leaking the pseudo-console handles into the client process. if err := windows.CreatePipe(&readHandle, &writeHandle, nil, 0); err != nil { return 0, 0, err } return readHandle, writeHandle, nil } func closePseudoConsoleHandles(handles ...windows.Handle) { for _, handle := range handles { if handle != 0 && handle != windows.InvalidHandle { _ = windows.CloseHandle(handle) } } } func managedProcessEnvironment(values map[string]string) (*uint16, error) { if len(values) == 0 { // A nil environment tells CreateProcess to inherit the helper's // environment. This preserves Windows' special drive-current-directory // entries and avoids rebuilding a potentially incomplete environment // block for the common case. return nil, nil } environment := make(map[string]string, len(values)) for _, entry := range os.Environ() { keyEnd := strings.IndexByte(entry, '=') if strings.HasPrefix(entry, "=") { // Windows stores drive current directories as =C:=C:\\...; // the first equals sign is part of that variable's name. if next := strings.IndexByte(entry[1:], '='); next >= 0 { keyEnd = next + 1 } } if keyEnd > 0 { key := entry[:keyEnd] environment[strings.ToUpper(key)] = entry } } for key, value := range values { environment[strings.ToUpper(key)] = key + "=" + value } entries := make([]string, 0, len(environment)) for _, entry := range environment { entries = append(entries, entry) } sort.Slice(entries, func(i, j int) bool { return strings.ToUpper(entries[i]) < strings.ToUpper(entries[j]) }) encoded := utf16.Encode([]rune(strings.Join(entries, "\x00") + "\x00\x00")) return &encoded[0], nil }