如何向PowerShell进程发送Ctrl+C信号以终止执行中的命令
问题描述
我需要启动PowerShell执行命令,并且能随时终止它。目前用Go的exec.Command()启动带-nologo、-noexit参数的PowerShell,通过-command $pid获取它的PID。尝试用另一个PowerShell执行taskkill /F /PID命令终止进程,虽然提示已终止,但进程实际还在运行;手动按Ctrl+C却能立即退出。
测试时用ping -n 50 google.com这个耗时命令,现在想知道怎么通过代码给PowerShell进程发送Ctrl+C信号,中途终止正在执行的命令,避免等待卡住的命令。
问题代码示例
package main import ( "fmt" "io" "log" "os" "strconv" "syscall" "os/exec" ) func main() { cmd := exec.Command("powershell", "-nologo", "-noexit", "-command", "$pid") stdin, err := cmd.StdinPipe() if err != nil { panic(err) } stdout, err := cmd.StdoutPipe() if err != nil { panic(err) } cmd.Start() buf := make([]byte, 1024) bytes, _ := stdout.Read(buf) pid := string(buf[:bytes]) log.Println("[+] Shell PID:", cmd.Process.Pid) log.Println("[+] Powershell PID:", pid) fmt.Fprintln(stdin, "ping -n 50 google.com") stdin.Close() count := 0 for { n, err := stdout.Read(buf) if err == io.EOF { break } if err != nil { panic(err) } fmt.Print(string(buf[:n])) count += 1 if count >= 15 && count <= 20 { log.Println("[-] Powershell killed") // 尝试发送Ctrl+C信号,但报错:not supported by windows pid_int, err := strconv.Atoi(pid) if err != nil { panic(err) } proc, err := os.FindProcess(pid_int) if err != nil { panic(err) } err = proc.Signal(syscall.Signal(syscall.CTRL_C_EVENT)) if err != nil { panic(err) } log.Println("[-] Shell killed") cmd.Process.Kill() } } }
解决方案
Windows下直接用os.Process.Signal()发送CTRL_C_EVENT不生效,因为这个信号需要发送到进程组而非单个进程。正确的做法是:
- 启动PowerShell时,设置创建新的进程组,让PowerShell成为进程组组长
- 调用Windows原生API的
GenerateConsoleCtrlEvent发送CTRL_C_EVENT到整个进程组,触发PowerShell的Ctrl+C处理逻辑
修改后的代码如下:
package main import ( "fmt" "io" "log" "os" "os/exec" "syscall" ) func main() { cmd := exec.Command("powershell", "-nologo", "-noexit", "-command", "$pid") // 设置创建新进程组,让PowerShell成为进程组组长 cmd.SysProcAttr = &syscall.SysProcAttr{ CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, } stdin, err := cmd.StdinPipe() if err != nil { panic(err) } stdout, err := cmd.StdoutPipe() if err != nil { panic(err) } err = cmd.Start() if err != nil { panic(err) } buf := make([]byte, 1024) bytes, _ := stdout.Read(buf) pid := string(buf[:bytes]) log.Println("[+] Shell PID:", cmd.Process.Pid) log.Println("[+] Powershell PID:", pid) fmt.Fprintln(stdin, "ping -n 50 google.com") stdin.Close() count := 0 for { n, err := stdout.Read(buf) if err == io.EOF { break } if err != nil { log.Println("读取输出错误:", err) break } fmt.Print(string(buf[:n])) count += 1 if count >= 15 && count <= 20 { log.Println("[-] 发送Ctrl+C信号到PowerShell进程组") // 发送CTRL_C_EVENT到进程组,进程组ID就是PowerShell的PID err := syscall.GenerateConsoleCtrlEvent(syscall.CTRL_C_EVENT, uint32(cmd.Process.Pid)) if err != nil { log.Println("发送信号失败:", err) } // 等待进程优雅退出 err = cmd.Wait() if err != nil { log.Println("进程退出错误:", err) } log.Println("[-] PowerShell已终止") break } } }
关键说明
CREATE_NEW_PROCESS_GROUP:让PowerShell进程成为新进程组的组长,后续发送的Ctrl+C信号会覆盖整个进程组(包括PowerShell启动的子进程,比如ping)syscall.GenerateConsoleCtrlEvent:Windows原生API,专门用于发送控制台控制信号到指定进程组,能正确触发PowerShell的Ctrl+C处理逻辑- 不需要额外获取
$pid,cmd.Process.Pid就是启动的PowerShell进程的PID,也就是进程组ID
这样修改后,就能像手动按Ctrl+C一样,让PowerShell终止正在执行的命令并退出,不会出现进程残留的问题。
内容的提问来源于stack exchange,提问作者Niu
相关产品推荐
相关产品推荐

