You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何向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不生效,因为这个信号需要发送到进程组而非单个进程。正确的做法是:

  1. 启动PowerShell时,设置创建新的进程组,让PowerShell成为进程组组长
  2. 调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 09:13:25