10

Trying to find any currently active timers that are still pending that may cause the computer to wake-up. When the timer is created, a name is specified. A list of all named timers would be ideal, not just the name specified.

Here is the code to create the named timer:

[DllImport("kernel32.dll")]
private static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, String lpTimerName);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWaitableTimer(SafeWaitHandle hTimer, [In] ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume);

private static int SetWaitForWakeUpTime(DateTime wakeTime) {
    long waketime = wakeTime.ToFileTime();

    String timerName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name.ToString() + "WakeUpTimer";
    using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, timerName)) {
        if (SetWaitableTimer(handle, ref waketime, 0, IntPtr.Zero, IntPtr.Zero, true)) {
            using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset)) {
                wh.SafeWaitHandle = handle;
                wh.WaitOne();
            }
        }
        else {
            return Marshal.GetLastWin32Error();
        }
    }
    return 0;
}

I have tried using the NtQuerySystemInformation method without success. I'm not even sure that function is the correct function to use.

[DllImport("ntdll.dll", SetLastError=true)]
private static extern NtStatus NtQuerySystemInformation(uint infoClass, IntPtr info, uint size, out uint length);

I have also tried using the command line powercfg /waketimers, however that seems to only list Windows Task Scheduler tasks that have the Wake the computer to run this task option checked.

Loathing
  • 5,109
  • 3
  • 24
  • 35
  • 1
    `powercfg /waketimers` should work. For a quick test, just run this tool https://www.codeproject.com/Articles/49798/Wake-the-PC-from-standby-or-hibernation and hit 'set timer'. `powercfg /waketimers` will list the timer it sets. – Simon Mourier Jun 28 '17 at 06:36
  • Did you read the last sentence in the original post? – Loathing Jun 28 '17 at 07:04
  • Yes and it's wrong hence my comment. Have tou tested what I suggest? – Simon Mourier Jun 28 '17 at 07:06
  • Yes, I have tested creating a wake timer and then running `powercfg`, and the wake timer doesn't show up. – Loathing Jun 28 '17 at 07:09
  • Have you used the sample tool I mention? It works fine for me (Win 10 64b) – Simon Mourier Jun 28 '17 at 07:31
  • 2
    Okay, I tested again. I do see it listed now with a `[PROCESS]` prefix. I think my understanding of wake timers was incorrect. I had thought the wake event was registered with the operating system, regardless if the application is still running. However, it appears the wake timer is removed if the process that created it is closed, and also that each process can create at most one wake timer. The most recently created wake timer in a process replaces any previous wake timer created in that process. Is that correct? Thank you for your comments. – Loathing Jun 28 '17 at 18:55
  • 1
    Yes because they use handles which are in-memory things only. If you don't want a running process, then you need a task. I don't think you have a 1 timer limit if you don't specify a name. So do you still have a question open? – Simon Mourier Jun 29 '17 at 05:56
  • 1
    As the `lpTimerName` is optional, I thought it wouldn't have functional impact, but you are correct, the same name overwrites the previous timer. It would be nice to know how to output the timer names, as the name is not listed by the `powercfg /waketimers` command. It would also be helpful to know which API `powercfg` is using, rather than relying on it directly. – Loathing Jun 30 '17 at 19:15
  • 1
    Unfortunately, `powercfg /waketimers` is using undocumented APIs, it correspond to the WakeTimerList value is https://msdn.microsoft.com/en-us/library/windows/hardware/mt807499.aspx but there's nothing more to it. And I doubt this API will list timer names (if powercfg doesn't). – Simon Mourier Jul 01 '17 at 07:40
  • I tried using the `const int WakeTimerList = 50;` to the `CallNtPowerInformation` function, but without success. The error code was always `C000000D`, which means `An invalid parameter was passed to a service or function.` Please post an answer with one of the comments so I can accept it. If you're able to get `WakeTimerList` to work, that would be interesting too. Thanks for your help. – Loathing Jul 06 '17 at 05:48
  • API tracing `powercfg /waketimers` shouldn't be too hard. Oddly nobody else seems to have had a need for this... – Fizz Mar 10 '21 at 22:31
  • @Loathing to get around the error, you probably need to use the undocumented `PowerInformationWithPrivileges` function, see https://stackoverflow.com/a/53787160 – that still leaves the issue that the structure of the returned buffer is undocumented, although that shouldn't be too hard to work out, with a bit of guesswork – Simon Kissane Mar 16 '23 at 04:32

1 Answers1

1

I can explain how to do this. I know you were asking for an answer in C#; I will explain how to do it in C or C++ (albeit just giving high-level steps rather than actual code); my knowledge of C# is rather limited, so I will leave translating these instructions to C# as an exercise for the reader.

You need to call the undocumented PowerInformationWithPrivileges function exported by powrprof.dll. Although undocumented, its prototype is identical to CallNtPowerInformation:

NTSTATUS PowerInformationWithPrivileges(
  [in]  POWER_INFORMATION_LEVEL InformationLevel,
  [in]  PVOID                   InputBuffer,
  [in]  ULONG                   InputBufferLength,
  [out] PVOID                   OutputBuffer,
  [in]  ULONG                   OutputBufferLength
);

It isn't in the import library, so in C or C++ you need to load it dynamically using GetProcAddress. (I suppose that's not an issue in C#, the DllImport attribute will handle that for you.) You'll invoke it like this:

NTSTATUS status = PowerInformationWithPrivileges(WakeTimerList,NULL,0,buf,bufLength);

bufLength needs to be at least 240 bytes, but the actual required size depends on how many wake timers you have. Usual story: check if returned NTSTATUS is STATUS_BUFFER_TOO_SMALL (0xC0000023), if so allocate a bigger buffer (e.g. double its size) and try again. Keep on growing the buffer in a loop until the call either succeeds, or fails with a different error.

Although the structure of the returned buffer is undocumented, it is actually given in the Windows Driver Kit kernel mode headers, look in the file km/ntoapi.h (see for example copy someone put on GitHub– that's a version of the header from a few years ago, although I don't believe these definitions have changed recently.) Note trying to import the kernel mode headers into a user mode C/C++ application doesn't work, because the SDK and WDK/DDK headers are not compatible with each other and cause irresolvable conflicts. Instead, you can just copy paste the definition out of ntpoapi.h:

typedef struct _WAKE_TIMER_INFO {
    SIZE_T OffsetToNext;
    ULARGE_INTEGER DueTime;
    ULONG Period;
    DIAGNOSTIC_BUFFER ReasonContext;
} WAKE_TIMER_INFO, * PWAKE_TIMER_INFO;

Note you also need to copy the definitions of DIAGNOSTIC_BUFFER, REASON_BUFFER, and REQUESTER_TYPE.

The structure is essentially a linked-list. If OffsetToNext is zero, there is no next entry; if it is non-zero, that's the number of bytes to skip after this entry to find the next one. The OffsetToNext is relative to the start of that entry. In my experience it is normally 240 bytes, but don't rely on that.

I believe the offsets in DIAGNOSTIC_BUFFER and REASON_BUFFER are also relative to the start of the respective structure.

Note also for UserSharedServiceRequester you will need to decode the ServiceTag in DIAGNOSTIC_BUFFER using the undocumented I_QueryTagInformation function exported by advapi32.dll. It is easy to find sample code for doing that, see this C# code or this C code

In terms of what the Flags in REASON_BUFFER are, see this #define earlier in the ntpoapi.h header file:

#define DIAGNOSTIC_REASON_SIMPLE_STRING             0x00000001
#define DIAGNOSTIC_REASON_DETAILED_STRING           0x00000002

You might notice that that REASON_BUFFER, and those flags, are very similar to the publicly documented REASON_CONTEXT structure – the difference being the lack of an initial Version field, and also using buffer offsets instead of pointers.

Finding names of timers

WakeTimerList doesn't give you the names of timers. It does tell you which process/server/driver created the timer, which may be enough to work out which timer it is.

If you search the NT Object Manager namespace using undocumented NtQueryDirectoryObject/etc APIs, you can find named timers in there. Or you can just use WinObj (or similar tools). Given its name, you can open it, and then there is an undocumented NtQueryTimer API which can tell you some basic info about a timer (has it fired yet, how much time is remaining until it does). Unfortunately, there doesn't seem to be any user mode API for finding out from a timer handle whether it is a wake timer or not.

The other option you have is to call SetWaitableTimerEx instead of SetWaitableTimer, and pass a non-NULL WakeContext argument. That points to a REASON_CONTEXT which can contain a reason string explaining why the timer is waking up the computer. That reason string will be displayed in powercfg -waketimers output, which is getting it from the REASON_BUFFER returned by PowerInformationWithPrivileges(WakeTimerList,...). You could put the timer name in that string.

Simon Kissane
  • 4,373
  • 3
  • 34
  • 59