如何用CreateProcessWithTokenW在Go中重定向输出到当前控制台
问题:使用CreateProcessWithTokenW启动CMD时无法重定向到当前控制台
我正在用Go语言尝试通过CreateProcessWithTokenW启动新的CMD shell,目标是将标准输出重定向到当前控制台窗口,让命令在同一窗口执行。但当前代码会打开独立的新CMD窗口,无法实现预期效果;而用exec.Command的代码可以正常将输出保留在同一控制台。
现有问题代码:
func launchCmd(token windows.Token) { var si windows.StartupInfo var pi windows.ProcessInformation // Duplicate the handles dupStdin, err := duplicateHandle(os.Stdin.Fd()) if err != nil { fmt.Println("Error duplicating stdin handle:", err) return } dupStdout, err := duplicateHandle(os.Stdout.Fd()) if err != nil { fmt.Println("Error duplicating stdout handle:", err) return } dupStderr, err := duplicateHandle(os.Stderr.Fd()) if err != nil { fmt.Println("Error duplicating stderr handle:", err) return } defer windows.CloseHandle(dupStdin) defer windows.CloseHandle(dupStdout) defer windows.CloseHandle(dupStderr) si = windows.StartupInfo{ Cb: uint32(unsafe.Sizeof(si)), Flags: windows.STARTF_USESTDHANDLES, StdInput: dupStdin, StdOutput: dupStdout, StdErr: dupStderr, } err = CreateProcessWithTokenW( token, 0, "C:\\Windows\\System32\\cmd.exe", &si, &pi, ) if err != nil { fmt.Println("Error CreateProcessWithTokenW:", err) return } defer windows.CloseHandle(pi.Process) defer windows.CloseHandle(pi.Thread) fmt.Printf("Launched cmd.exe with PID: %d\n", pi.ProcessId) windows.WaitForSingleObject(pi.Process, windows.INFINITE) fmt.Println("Process completed.") } func duplicateHandle(fd uintptr) (windows.Handle, error) { var dupHandle windows.Handle procHandle := windows.CurrentProcess() err := windows.DuplicateHandle(procHandle, windows.Handle(fd), procHandle, &dupHandle, 0, true, windows.DUPLICATE_SAME_ACCESS) if err != nil { return 0, fmt.Errorf("error duplicating handle: %w", err) } return dupHandle, nil } func CreateProcessWithTokenW(duplicatedToken windows.Token, creationFlags uint32, commandLine string, startupInfo *windows.StartupInfo, processInfo *windows.ProcessInformation) error { cmdPtr, err := syscall.UTF16PtrFromString(commandLine) if err != nil { return err } r1, _, e1 := syscall.SyscallN( procCreateProcessWithTokenW.Addr(), uintptr(duplicatedToken), 0, 0, uintptr(unsafe.Pointer(cmdPtr)), uintptr(creationFlags), 0, 0, uintptr(unsafe.Pointer(startupInfo)), uintptr(unsafe.Pointer(processInfo)), ) if r1 == 0 { return e1 } return nil }
对比可行的exec.Command代码:
cmd := exec.Command("cmd.exe") cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr
解决方案
要实现和exec.Command相同的控制台重定向效果,需要修改以下几点:
1. 正确获取标准输入输出的Windows句柄
不要使用os.Stdin.Fd(),而是调用Windows API的GetStdHandle获取原生控制台句柄,避免Go的文件描述符包装带来的问题。
2. 调整StartupInfo的标志和窗口显示设置
添加STARTF_USESHOWWINDOW标志,并设置wShowWindow为SW_SHOW,确保子进程复用当前控制台窗口而非创建新窗口。
3. 修正CreateProcessWithTokenW的参数和加载
确保正确加载CreateProcessWithTokenW函数,并设置合适的创建标志(如CREATE_UNICODE_ENVIRONMENT)。
修改后的完整代码:
package main import ( "fmt" "syscall" "unsafe" "golang.org/x/sys/windows" ) var procCreateProcessWithTokenW = windows.NewLazySystemDLL("advapi32.dll").NewProc("CreateProcessWithTokenW") func launchCmd(token windows.Token) { var si windows.StartupInfo var pi windows.ProcessInformation // 获取标准输入输出的原生Windows句柄 stdinHandle := windows.GetStdHandle(windows.STD_INPUT_HANDLE) stdoutHandle := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) stderrHandle := windows.GetStdHandle(windows.STD_ERROR_HANDLE) // 复制句柄 dupStdin, err := duplicateHandle(stdinHandle) if err != nil { fmt.Println("Error duplicating stdin handle:", err) return } dupStdout, err := duplicateHandle(stdoutHandle) if err != nil { fmt.Println("Error duplicating stdout handle:", err) return } dupStderr, err := duplicateHandle(stderrHandle) if err != nil { fmt.Println("Error duplicating stderr handle:", err) return } defer windows.CloseHandle(dupStdin) defer windows.CloseHandle(dupStdout) defer windows.CloseHandle(dupStderr) // 配置StartupInfo:同时使用标准句柄和窗口显示设置 si = windows.StartupInfo{ Cb: uint32(unsafe.Sizeof(si)), Flags: windows.STARTF_USESTDHANDLES | windows.STARTF_USESHOWWINDOW, StdInput: dupStdin, StdOutput: dupStdout, StdErr: dupStderr, ShowWindow: windows.SW_SHOW, // 复用当前控制台窗口 } // 设置创建标志:使用Unicode环境 creationFlags := uint32(windows.CREATE_UNICODE_ENVIRONMENT) err = CreateProcessWithTokenW( token, 0, "C:\\Windows\\System32\\cmd.exe", creationFlags, &si, &pi, ) if err != nil { fmt.Println("Error CreateProcessWithTokenW:", err) return } defer windows.CloseHandle(pi.Process) defer windows.CloseHandle(pi.Thread) fmt.Printf("Launched cmd.exe with PID: %d\n", pi.ProcessId) windows.WaitForSingleObject(pi.Process, windows.INFINITE) fmt.Println("Process completed.") } // 修改参数为windows.Handle,直接接收原生句柄 func duplicateHandle(srcHandle windows.Handle) (windows.Handle, error) { var dupHandle windows.Handle procHandle := windows.CurrentProcess() // 确保复制时允许子进程继承句柄 err := windows.DuplicateHandle(procHandle, srcHandle, procHandle, &dupHandle, 0, true, windows.DUPLICATE_SAME_ACCESS) if err != nil { return 0, fmt.Errorf("error duplicating handle: %w", err) } return dupHandle, nil } // 调整函数参数顺序,加入creationFlags的正确传递 func CreateProcessWithTokenW(duplicatedToken windows.Token, logonFlags uint32, commandLine string, creationFlags uint32, startupInfo *windows.StartupInfo, processInfo *windows.ProcessInformation) error { cmdPtr, err := syscall.UTF16PtrFromString(commandLine) if err != nil { return err } r1, _, e1 := syscall.SyscallN( procCreateProcessWithTokenW.Addr(), uintptr(duplicatedToken), // hToken uintptr(logonFlags), // dwLogonFlags uintptr(0), // lpApplicationName (设为nil,使用commandLine) uintptr(unsafe.Pointer(cmdPtr)),// lpCommandLine uintptr(creationFlags), // dwCreationFlags uintptr(0), // lpEnvironment (继承父进程环境) uintptr(0), // lpCurrentDirectory (继承父进程目录) uintptr(unsafe.Pointer(startupInfo)), // lpStartupInfo uintptr(unsafe.Pointer(processInfo)), // lpProcessInformation ) if r1 == 0 { return e1 } return nil }
内容的提问来源于stack exchange,提问作者OhGodWhy
相关产品推荐
相关产品推荐

