在调用 InitiateSystemShutdownW 之后,如何检测将来是否安排了重启?

How can I detect if a reboot is scheduled in the future after calling InitiateSystemShutdownW?

我目前正在使用 windows api 调用 InitiateSystemShutdownW 触发重启。我正在传递一个 dwTimeout 值(10 分钟)。我想检测是否已安排此重启(或任何其他重启):

[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool InitiateSystemShutdownW([MarshalAs(UnmanagedType.LPWStr)]String lpMachineName, [MarshalAs(UnmanagedType.LPWStr)]String lpMessage, int dwTimeout, bool bForceAppsClosed, bool bRebootAfterShutdown);

我知道如果我再次调用它,我会收到一条错误消息,告诉我它已经安排好了(通过 GetLastWin32Error),但我需要这样做而不需要再次调用重启(因为我可能不想在此时实际触发重启)。

我尝试使用参数 SM_SHUTTINGDOWN 调用 GetSystemMetrics,但即使我知道这是预定的 returns false/0。我假设这是实际发生的关机事件,未按计划发生。

无论如何,我是否可以检测到此调用正在进行中,或者更一般地说,是否安排了重新启动?

我正在使用 C#/DllImport/interop 调用这些方法,因此我需要一个可从 C# 进程访问的 api。

This answer here 包含有关通过 Powershell 检查 Windows 注册表以查看是否计划重启的信息:

Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'

除此之外,ExitWindowsEx 超时时间长得离谱(最长为 315360000 - 10 年),然后检查 ERROR_SHUTDOWN_IS_SCHEDULED - 如果命令成功,那么你'我会想要(当然)AbortSystemShutdown...

您可以使用 Windows Event Log API 访问 "System" 频道上的日志条目。该日志记录了登录请求关闭系统和中止系统关闭的请求。

这段代码展示了如何使用P/Invoke获取查询前指定时间范围内(以毫秒为单位)系统关闭和关闭中止请求的次数。因此,如果您在过去一小时内有 2 个计划关机请求和 1 个中止请求,那么您有一个挂起的关机正在进行中。

如果您真的想要有关已记录的关闭请求的所有详细信息,那么您可以使用 EvtRender 函数提取 XML 中的数据并对其进行解析。此代码不执行此操作。

using System;
using System.Runtime.InteropServices;

namespace WindowsEventLogChecker
{
  // partial list from "winerror.h"
  public enum ERROR
  {
    ERROR_SUCCESS,
    ERROR_NO_MORE_ITEMS = 259,
    ERROR_EVT_CHANNEL_NOT_FOUND = 15007,
    ERROR_EVT_INVALID_QUERY = 15001
  }

  // this is our own enum
  public enum SystemEventType
  {
    Shutdown,
    Abort
  }

  // these are from "winevt.h"
  public enum EVT_QUERY_FLAGS
  {
    EvtQueryChannelPath = 0x1,
    EvtQueryFilePath = 0x2,
    EvtQueryForwardDirection = 0x100,
    EvtQueryReverseDirection = 0x200,
    EvtQueryTolerateQueryErrors = 0x1000
  }


  class Program
  {
    [DllImport("wevtapi.dll", EntryPoint = "EvtQuery", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
    public static extern IntPtr EvtQuery(IntPtr session, [MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] string query, int flags);

    [DllImport("wevtapi.dll", EntryPoint = "EvtNext", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
    public static extern bool EvtNext(IntPtr resultSet, int batchSize, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] eventBatch, int timeout, int flags, ref int nReturned);

    [DllImport("wevtapi.dll", EntryPoint = "EvtClose", CallingConvention = CallingConvention.StdCall)]
    public static extern bool EvtClose(IntPtr handle);

    [DllImport("kernel32.dll", EntryPoint = "GetLastError", CallingConvention = CallingConvention.StdCall)]
    public static extern int GetLastError();

    static void Main(string[] args)
    {
      // get the number of scheduled shutdowns in the last hour
      int nShutdowns = GetEventCount(SystemEventType.Shutdown, 3600000);

      // get the number of aborted shutdowns in the last hour
      int nAborts = GetEventCount(SystemEventType.Abort, 3600000);
    }

    private static int GetEventCount(SystemEventType evtType, int timeSpanMs)
    {
      ERROR status = ERROR.ERROR_SUCCESS;
      IntPtr hResult = IntPtr.Zero;
      IntPtr[] eventBatch = new IntPtr[10];

      // these 2 event id's, along with 'USER32' event source, denote requested
      // shutdown and abort, respectively
      string shutDownId = "1074";
      string abortId = "1075";

      // XPath query to get the event id, event source, and timespan in ms from now 
      // back to when the event was posted to the event log.
      string format = "*[System[(EventID = {0}) and Provider[@Name=\"USER32\"] and TimeCreated[timediff(@SystemTime) <= {1}]]]";

      // The "System" event channel
      string channelPath = "System";

      int nEvents = 0;
      int count = 0;
      string evtQuery;

      switch (evtType)
      {
        case SystemEventType.Shutdown:
        evtQuery = string.Format(format, shutDownId, timeSpanMs);
        break;
        case SystemEventType.Abort:
        evtQuery = string.Format(format, abortId, timeSpanMs);
        break;
        default:
        throw new InvalidOperationException();
      }
      hResult = EvtQuery(IntPtr.Zero, channelPath, evtQuery, (int)(EVT_QUERY_FLAGS.EvtQueryChannelPath | EVT_QUERY_FLAGS.EvtQueryReverseDirection));
      if (IntPtr.Zero == hResult)
      {
        status = (ERROR)GetLastError();

        if (status == ERROR.ERROR_EVT_CHANNEL_NOT_FOUND)
        {
          // log error
          return 0;
        }
        else if (status == ERROR.ERROR_EVT_INVALID_QUERY)
        {
          // log error
          return 0;
        }
        else
        {
          // log error
          return 0;
        }
      }
      while (EvtNext(hResult, 10, eventBatch, 1000, 0, ref nEvents))
      {
        for (int i = 0; i < nEvents; i++)
        {
          count++;
          if (eventBatch[i] != IntPtr.Zero)
          {
            EvtClose(eventBatch[i]);
          }
        }
      }
      status = (ERROR)GetLastError();
      if (status != ERROR.ERROR_NO_MORE_ITEMS)
      {
        // log error here and cleanup any remaining events
        for (int i = 0; i < nEvents; i++)
        {
          if (eventBatch[i] != IntPtr.Zero)
          {
            EvtClose(eventBatch[i]);
          }
        }
      }
      if (hResult != null)
      {
        EvtClose(hResult);
      }
      return count;
    }
  }
}