You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

WinPE10中如何用C#捕获WM_QueryEndSession/WM_WTSSESSIONCHANGE消息?

解决WinPE中捕获关机/会话事件的问题

首先得明确:WinPE的系统环境和常规Windows差异很大——它是一个轻量级的预启动环境,很多桌面会话管理、窗口消息路由的组件都被简化或禁用了,这就是为什么你在常规Windows里正常工作的WndProc消息捕获在WinPE里失效的原因:

  • WM_QUERYENDSESSION/WM_ENDSESSION这类窗口消息,在WinPE中不会像常规系统那样广播到所有顶层窗口,尤其是当关机由Windows安装程序这类系统级进程触发时,消息传递路径被大幅简化。
  • WTSRegisterSessionNotifications依赖于终端服务(TermService),而WinPE默认不会启动这个服务,即便手动启动,会话事件的广播逻辑也和常规系统不同,所以收不到通知是正常的。

针对你的需求,这里有两个更适合WinPE环境的方案,按推荐优先级排序:

方案1:隐藏控制台程序捕获CTRL信号

WinPE对控制台信号的支持是完整的,CTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT能被正常触发,而且控制台程序不需要依赖桌面窗口消息循环,适配性更好。

你可以通过SetConsoleCtrlHandler这个Win32 API来注册回调,处理关机/注销事件,示例代码如下:

using System;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    // 声明Win32 API
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handlerRoutine, bool add);

    private delegate bool ConsoleCtrlDelegate(CtrlTypes ctrlType);

    private enum CtrlTypes
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT = 1,
        CTRL_CLOSE_EVENT = 2,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT = 6
    }

    static void Main(string[] args)
    {
        // 注册回调
        SetConsoleCtrlHandler(Handler, true);
        
        // 保持程序运行(如果是隐藏控制台,用等待事件替代控制台输入)
        new AutoResetEvent(false).WaitOne();
    }

    private static bool Handler(CtrlTypes ctrlType)
    {
        switch (ctrlType)
        {
            case CtrlTypes.CTRL_LOGOFF_EVENT:
            case CtrlTypes.CTRL_SHUTDOWN_EVENT:
                // 执行你的关机前操作
                WriteLogFile(4, 1, $"Received {ctrlType} event. Starting cleanup...");
                
                // 注意:这里的操作必须是同步完成的,系统会在发送信号后很快终止进程
                // 避免异步操作,确保所有清理逻辑在返回前完成
                return true; // 返回true表示已处理该事件
            default:
                return false;
        }
    }

    // 你的日志方法
    public static void WriteLogFile(int level, int code, string message)
    {
        // 原有实现
    }
}

如果需要隐藏控制台窗口,可以在项目属性中把输出类型设置为Windows应用程序,这样程序运行时不会显示控制台界面,但依然能接收控制台信号。

方案2:通过Windows服务实现

WinPE支持运行Windows服务,但部署和配置相对繁琐:

  1. 你需要把程序编写为服务(继承ServiceBase,重写OnShutdown方法)。
  2. 在WinPE启动后,通过sc create命令注册服务,再用sc start启动。
  3. 服务的OnShutdown方法会在系统关机时被调用,适合执行后台清理操作。

不过这种方式的缺点是:WinPE重启后服务注册会丢失,需要在部署脚本中重复注册;而且服务是完全后台运行,和你的部署入口程序的交互会更复杂。所以除非你的关机操作需要完全脱离用户界面运行,否则优先选方案1。

额外提示

  • 在WinPE中执行关机前操作时,一定要确保操作是快速且同步的,WinPE的关机流程比常规系统快很多,不会等待长时间运行的操作。
  • 避免依赖任何桌面相关的API(比如窗口消息、Shell组件),尽量使用底层Win32 API或.NET的核心功能。

内容的提问来源于stack exchange,提问作者R.McInnes-Piper

火山引擎 最新活动