如何检测Windows是正在关闭还是重新启动

20

我知道在Windows关闭时,它会向每个应用程序发送一个WM_QUERYENDSESSION消息。这使得检测Windows何时关闭变得容易。然而,在Windows关闭后,是否有可能知道计算机是要关机还是重启。

考虑到MSDN文档对WM_QUERYENDSESSION的描述:“……不可能确定正在发生哪个事件”,我并不特别有希望,但是stackoverflow网站上聚集了众多聪明的人,他们的智慧总能让人惊叹不已。

4个回答

10
在Windows 7(以及可能也适用于Vista/8/Server),您可以使用系统事件跟踪Windows是正在关闭(并关闭计算机)还是仅重新启动。每次启动/重启时(通过任何方式-单击“开始”菜单中的按钮或编程方式),Windows 7在系统日志中写入一个或两个事件,源USER32,事件ID为1074。如果您从“管理工具”打开事件查看器(将系统日志过滤为仅查看ID 1074),则可以查看这些事件的记录。这些事件的描述(消息)包含关闭类型。因此,您可以解析此类型最近事件的描述(在启动关闭后),查找必要的单词(关闭,重新启动/重启)。
我没有尝试查看使用电源按钮优雅地关闭Windows时写入事件的关闭类型(我通常禁用此功能),但有些网站建议它声明"关机"类型而不是"关闭" - 因此请检查一下,如果您需要确定。或者只需寻找"重新启动"类型-如果未找到,则假定为"关闭"类型。
在Windows XP中,根据我的经验,仅当以编程方式执行关闭/重新启动(例如,在程序安装期间或使用shutdown.exe实用程序时)时,才会记录事件1074。因此,它不会注册从shell(资源管理器)启动的关闭,但也许您可以将此方法与另一个答案中提出的从注册表中读取值相结合。此外,请记住,在WinXP中,事件1074的消息始终包含单词"重新启动",无论关闭类型是什么,因此您应查看"关闭类型:"字段,该字段将说明是"关闭"或"重新启动"。
与此相关的是,每当Windows由于某种原因无法关闭/重新启动时(例如,如果应用程序不允许响应WM_QUERYENDSESSION而进行关闭),都会记录事件ID 1073。在这种情况下,消息还将包含诸如"关闭","重新启动"或"关机"之类的单词-在WinXP中。对于我们的情况,在Win7中,这种类型的事件不太有用,因为它不会区分关闭和重新启动。但是对于WinXP-如果您只需要拦截关闭/重新启动,执行一些操作,然后继续相应的关闭或重新启动过程-则应按预期工作。

1
需要注意的是,EventLog 显示本地化消息,因此关键字如“关闭”或“重新启动”将根据操作系统本地化而有所不同。 - tpx86

6
这里可以读取DWORD值,以确定用户从关机对话框中选择了什么。 这是一个绕路的解决方案,但应该能解决问题。

13
当然,这是基于假设,即系统现在正在关闭是因为当前用户在资源管理器中发起了关机操作。如果这是一个程序性的关机,从另一个应用程序进行的关机,或者是由另一个用户进行的关机,您将得到先前关机的原因。 - MSalters
8
因为答案不完整并且只适用于Windows Explorer。此外,在Windows 7中似乎已经被删除。 - unixman83
1
它在WindowsXP下运行良好,但在Windows7下不起作用。 - conceptacid

5
通常有效的方法是捕获 WM_ENDSESSION 并记录它。然后跟踪时间。如果系统在合理的时间内重新启动(比如 5 分钟),那么这就是重启而不是关机。
想法:如果系统在 5 分钟内重新启动,那么用户是点击了“关机”还是“重启”真的很重要吗?
如果你确实需要检测关机(我认为唯一需要这样做的原因是如果你依赖于关机与重启之间的微小软件行为差异),你可以调查 ExitWindowsEx 和相关函数的 API hooking,但我不推荐这种方法。请重新考虑是否真的需要直接检测此问题。

1
API钩子建议加1分。时间检查建议纯属猜测。 - Joe Jordan
@Joe,随着现代ACPI电源管理的发展:睡眠、休眠、远程唤醒等功能已经成为常态。重启(或热启动)的定义变得越来越难以界定。大多数人所说的重启(或类似的术语)是指系统在非正常状态下停留了一个非常短暂的时间 - unixman83

2
可能的Windows7实验性解决方案如下。(我不确定这是否适用于其他本地化,因此我称其为解决方法)
using System.Diagnostics.Eventing.Reader;

namespace MyApp
{
public class RestartDetector : IDisposable
{
    public delegate void OnShutdownRequsted(bool restart);
    public OnShutdownRequsted onShutdownRequsted;

    private EventLogWatcher watcher = null;

    public RestartDetector()
    {
        try
        {
            EventLogQuery subscriptionQuery = new EventLogQuery(
                "System", PathType.LogName, "*[System[Provider[@Name='USER32'] and (EventID=1074)]]");

            watcher = new EventLogWatcher(subscriptionQuery);

            // Make the watcher listen to the EventRecordWritten
            // events.  When this event happens, the callback method
            // (EventLogEventRead) is called.
            watcher.EventRecordWritten +=
                new EventHandler<EventRecordWrittenEventArgs>(
                    EventLogEventRead);

            // Activate the subscription
            watcher.Enabled = true;
        }
        catch (EventLogReadingException e)
        {
        }
    }

    public void EventLogEventRead(object obj, EventRecordWrittenEventArgs arg)
    {
        bool restart = false;
        try
        {
            // Make sure there was no error reading the event.
            if (arg.EventRecord != null)
            {
                String[] xPathRefs = new String[1];
                xPathRefs[0] = "Event/EventData/Data";
                IEnumerable<String> xPathEnum = xPathRefs;

                EventLogPropertySelector logPropertyContext = new EventLogPropertySelector(xPathEnum);
                IList<object> logEventProps = ((EventLogRecord)arg.EventRecord).GetPropertyValues(logPropertyContext);

                string[] eventData = (string[])logEventProps[0];

                foreach (string attribute in eventData)
                {
                    if (attribute.Contains("restart")) { restart = true; break; }
                }
            }
        }
        catch (Exception e)
        {
        }
        finally
        {
            if (onShutdownRequsted != null) { onShutdownRequsted(restart); }
        }   
    }

    public void Dispose()
    {
        // Stop listening to events
        if (watcher != null)
        {
            watcher.Enabled = false;
            watcher.Dispose();
        }
    }
}
}

以下是电脑重新启动时写入事件日志的XML示例:
- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
  <Provider Name="USER32" /> 
  <EventID Qualifiers="32768">1074</EventID> 
  <Level>4</Level> 
  <Task>0</Task> 
  <Keywords>0x80000000000000</Keywords> 
  <TimeCreated SystemTime="2015-12-15T11:10:43.000000000Z" /> 
  <EventRecordID>90416</EventRecordID> 
  <Channel>System</Channel> 
  <Computer>WIN7PC</Computer> 
  <Security UserID="S-1-5-21-1257383181-1549154685-2724014583-1000" /> 
  </System>
- <EventData>
  <Data>C:\Windows\system32\winlogon.exe (WIN7PC)</Data> 
  <Data>WIN7PC</Data> 
  <Data>No title for this reason could be found</Data> 
  <Data>0x500ff</Data> 
  <Data>restart</Data> 
  <Data /> 
  <Data>WIN7PC\WIN7PCUser</Data> 
 <Binary>FF00050000000000000000000000000000000000000000000000000000000000</Binary> 
  </EventData>
  </Event>

XPath查询只为我返回了第一个DATA条目,它是一个字符串,甚至不是一个字符串数组。我简化了我的代码,像这样工作得很好: 如果(arg.EventRecord!= null) { foreach(EventProperty x in((EventLogRecord)arg.EventRecord).Properties) { 如果(x.Value.Equals("restart")){restart = true; break; } } } - Sven Sowa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接