Capturing Windows Power Events in a Console Application

So you’re writing a console application and you think “Hey, it would be great if I could catch power events so I could:”

  • get all my data in a sane state before a shutdown
  • defer heavy processing until we are back on AC power
  • reinitialize network resources if we just returned from an unexpected sleep state

You fire off a Google search for “Windows Power Events” and you quickly come across this MSDN article that tells you that you need to call the RegisterPowerSettingNotification API function. Super! Then you quickly notice a few problems:

  1. The RegisterPowerSettingNotification function takes either a Window Handle or a SERVICE_STATUS_HANDLE.
    1. You’re a console application so you don’t have a Window handle.
    2. You aren’t running as a service so you can’t call RegisterServiceCtrlHandlerEx to get a SERVICE_STATUS_HANDLE
  2. The minimum supported client is Windows Vista and you would like to at least support Windows XP forward.

Ahh, crap. As far as my (prolonged) search results show, there is no way to receive power events without either having a window handle, running as a service or being a device driver. Period.

Enter the Hidden Window

The best solution to this problem that I’ve come across is to create a hidden window. It seems like such a hack, but it does work! There are a few things you need to be aware of when using this method. As per MSDN recommendations one should generally use a single thread to create all of their windows. The system directs messages to individual windows, so you need to process the message queue on the same thread that created the window*. In a Windows application this is generally all done in WinMain. However for a console application, we likely have other things going on in the main thread, especially if we want the power event notifications to be available early on in the application startup process. Therefore I create a separate thread which will create the hidden window, register for power events, and then continuously process the message loop.

* In fact, the message queue is really the thing that we need in all this so that we can receive the WM_POWERBROADCAST messages. AFAIK, the only ways to get a message queue are via creating a window or running as a service.

Power Events

After you have a thread and create a window you will automatically receive the WM_POWERBROADCAST messages in your message queue for the following power events:

Windows 2000 and Later
PBT_APMPOWERSTATUSCHANGE
PBT_APMRESUMEAUTOMATIC
PBT_APMRESUMESUSPEND
PBT_APMSUSPEND
PBT_POWERSETTINGCHANGE

Windows 2000, Windows XP, and Windows Server 2003 only
PBT_APMBATTERYLOW
PBT_APMOEMEVENT
PBT_APMQUERYSUSPEND
PBT_APMQUERYSUSPENDFAILED
PBT_APMRESUMECRITICAL

As you can see you may not even need to call RegisterPowerSettingNotification at all to get the events you need! In the case of Windows XP, these are all that you are going to get. On Vista and later however, you may still want to register for additional power events. There are several more event types that you can register for, which are described here. The ones that I cared about were:

GUID_ACDC_POWER_SOURCE
GUID_BATTERY_PERCENTAGE_REMAINING
GUID_MONITOR_POWER_ON

Show Me Teh Codez!

I wrote a sample console application in C that creates a hidden window on a separate thread, tries to register for additional power events if they are available, and then processes the message queue until the user enters input twice. It prints the message type of any window message it receives, but it provides additional information for power events. The application has both 32- and 64-bit builds and has been tested on Windows XP Home RTM 32-bit and Windows 7 Home Premium 64-bit.  It is written with Visual Studio 2010 but the code should work on previous versions of VS as well, you’ll just have to migrate the project and solution settings.

NOTE: In order to build for Windows XP RTM/SP1 I targeted the v90 (Visual Studio 2008) toolset. You must have Visual Studio 2008 installed to do this. See my post here on how and why I have to do this.

https://bitbucket.org/zachburlingame/windowspowerevents

Additional Resources

Comments

5 responses to “Capturing Windows Power Events in a Console Application”

  1. Ronil Avatar

    Really nice description. I’m having same type of queries.
    With above links i’m not able to find source code of that console app.
    Can you please provide link for source code so i can refer it?
    Thanks.

  2. BillW Avatar

    The repo doesn’t seem to be active. Can the source code repository be updated or someone point to it? Thanks!

    1. ZachB Avatar

      BillW,

      Try now – Atlassian changed the way they handled link redirection and it broke a lot of my older links. I’ve updated them all now, so they should work again.

      – Zach

  3. Phil Avatar
    Phil

    Many thanks for your solution, helped me a bit!

  4. JJ Berry Avatar
    JJ Berry

    Nice little article! I have a service that remains WinXP compatible. It registers for power events by wrapping RegisterPowerSettingNotification in a shim function that calls LoadLibrary on user32.dll and fails gracefully under WinXP.

    However, I couldn’t find a way of continuing to maintain WinXP compatibility and still get PBT_APMQUERYSUSPEND

    Your article has given me an interesting new avenue to explore. So thank you for that!

    I presume this will work with a service, since it is essentially a console app. Or will Session 0 Isolation rear its ugly head?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.