我知道当 Windows 关闭时,它会向每个应用程序发送一条WM_QUERYENDSESSION消息。这样可以轻松检测 Windows 何时关闭。但是,是否有可能知道计算机是否会关闭电源,或者在 Windows 关闭后是否会重新启动。
我不是特别有希望,考虑到 MSDN 上的文档有这样的说法WM_QUERYENDSESSION
:“......不可能确定正在发生的事件”,但 stackoverflow 的累积聪明才智从未停止让我惊讶。
在 Windows 7(也可能在 Vista / 8 / Server 中)中,您可以使用系统事件来跟踪 Windows 是正在关闭(并关闭计算机电源)还是只是重新启动。每次启动关机/重启时(通过任何方式 - 单击开始菜单中的按钮,或以编程方式),Windows 7 都会在系统日志中写入一个或两个事件,源 USER32,事件 ID 1074。您可以看到记录的这些事件,如果您从管理工具中打开事件查看器(过滤系统日志以仅查看 ID 1074)。这些事件的描述(消息)包含关闭类型。因此,您可以解析这种类型的最新事件的描述(在启动关闭之后),寻找必要的词(关闭、重新启动/重新启动)。
使用电源按钮正常关闭 Windows 时,我没有尝试查看事件中写入的关闭类型(我通常禁用此功能),但某些网站建议它声明“关机”类型而不是“关机” -所以检查一下,如果你需要确定的话。或者只是寻找“重新启动”类型 - 如果找不到,则假定为“关闭”类型。
在 Windows XP 中,根据我的经验,只有在以编程方式完成关机/重新启动时(例如,在程序安装期间或使用 shutdown.exe 实用程序),才会记录事件 1074。因此它不会注册从 shell(资源管理器)启动的关闭,但也许您可以将此方法与从注册表中读取值结合起来,如另一个答案中所建议的那样。另外,请记住,在 WinXP 中,事件 1074 的消息包含单词“restart”,无论真正的关机类型是什么,因此您应该查看“Shutdown Type:”字段,该字段将显示“shutdown”或“重启”。
与此相关的是,每当 Windows 由于某种原因(例如,如果应用程序不允许作为对 WM_QUERYENDSESSION 的响应而关闭)时无法关闭/重新启动,就会记录一个事件 ID 1073。在这种情况下,该消息还将包含诸如“关机”、“重新启动”或“关机”之类的词 - 在 WinXP 中。对于 Win7,这种类型的事件在我们的例子中不太有用,因为它不会在关机和重启之间产生任何区别。但是对于 WinXP - 如果您只需要拦截关机/重启,执行一些操作,然后继续相应的关机或重启过程 - 它应该可以按预期工作。
从这里:
您可以从“HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shutdown Setting”中读取 DWORD 值,以确定用户上次从“关机”对话框中选择的内容。
有点迂回的解决方案,但它应该可以解决问题。
通常有效的一个技巧是捕获WM_ENDSESSION
并记录它。现在跟踪时间。如果系统在合理的周期内(比如 5 分钟)恢复。然后那是重新启动,而不是关机。
想法:如果系统在 5 分钟内恢复正常,用户单击“关机”或“重启”真的很重要吗?
如果您确实需要检测关机(我认为您需要这样做的唯一原因是,如果您依赖于关机与重新启动之间的模糊行为软件差异),您可以调查API hooking
相关ExitWindowsEx
功能,但我不推荐这种方法。如果您真的需要直接检测到这一点,请重新考虑。
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();
}
}
}
}
以下是 PC 重新启动时写入事件日志的 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>