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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With