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.