0

I have created a service exe that will autourun at boot.

I am using this example to create the service: http://www.cromis.net/blog/2011/04/how-to-make-a-very-small-windows-service-executable/

Works pretty well. The service needs to 'monitor' the state of the PC, for example to check if the PC is connected to a powersupply or not. In case of a change, for example from connected power to battery or low battery status, it will send an emergency email about the critical state of the device.

This works pretty well when running as a normal exe but not as a service. The goal is to be able to do this at any state of the pc (logged in or not), so it is essential to be run as a service.

I have created a window handle to receive the WM_POWERBROADCAST message, for example:

procedure TEventAlerter.wndProc(var Msg : TMessage);
var
  handled: Boolean;

begin
  log( 'wndProc processed - '+intToStr( Msg.Msg ));
  // Assume we handle message
  handled := TRUE;
  case( Msg.Msg ) of
    WM_POWERBROADCAST : begin
                         case( Msg.WParam ) of
                           PBT_APMPOWERSTATUSCHANGE : powerChangeEvent(Msg.WParam);
                           PBT_APMBATTERYLOW        : powerLowEvent(Msg.WParam);
                          else powerEvent(Msg.WParam);
                         end;
                        end;
    else handled:= FALSE;
  end;

  if( handled ) then
   begin
    // We handled message - record in message result
    Msg.Result := 0
   end
  else
   begin
    // We didn't handle message
    // pass to DefWindowProc and record result
    Msg.Result := DefWindowProc(fHWnd, Msg.Msg, Msg.WParam, Msg.LParam);
   end;
end; 

To initialize I am using this:

FHwnd:=AllocateHWnd(wndProc);

Because I am aware of 0-isolation state when running as a service, I changed some example code of RegisterService() function with this:

ServiceStatus.dwServiceType := SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS;
ServiceStatus.dwCurrentState := SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP or
                                    SERVICE_ACCEPT_PAUSE_CONTINUE or
                                    SERVICE_ACCEPT_POWEREVENT;

But without any success. I have also use a thread to poll messages with windows API function getMessage() from the window but the results are the same.

What can I do to catch powerstate events? It's kind of weird that services cannot react on powerstate changes or not?

Craig
  • 1,874
  • 13
  • 41
Codebeat
  • 6,501
  • 6
  • 57
  • 99

1 Answers1

6

When a service uses SERVICE_ACCEPT_POWEREVENT, the service must use a HandlerEx() callback via RegisterServiceCtrlHandlerEx() to receive SERVICE_CONTROL_POWEREVENT notifications from the SCM. However, the example code you linked to (as well as Delphi's own TService framework) uses the outdated Handler() callback via RegisterServiceCtrlHandler() instead, which does not receive such notifications. So you need to update the code to use a HandlerEx() callback instead.

Alternatively, to receive power notifications without using an SCM callback, have a look at RegisterPowerSettingNotification() or PowerSettingRegisterNotification(), which can send power notifications to an HWND or callback function, respectively.

TLama
  • 75,147
  • 17
  • 214
  • 392
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • The link in the question does not use TService, RegisterServiceCtrlHandlerEx is a valid solution. – Sertac Akyuz Mar 08 '15 at 16:49
  • The linked code suffers from the same problem that `TService` suffers from, though. I have updated my answer to reflect that. – Remy Lebeau Mar 08 '15 at 16:54
  • Unluckily, my QC #124304 entry of a year ago is still in "Reported" state only, and don't know if it has been moved in the new bug tracker. Also i see Lebeau submitted a similar one (#287 !) in 2002, although for C++ Builder, and it's "open" since then... maybe we have chances in Delphi X80 Emb will update TService to an API introduced with Windows 2000... – LDS Mar 09 '15 at 13:09
  • QC #124304 is not in the new QP system. You should submit a new QP ticket and mention the original QC ticket in it. In any case, QC and QP are linked to the same internal bug tracking system (though it does not look like QC #124304 was ever pushed into it). EMBT devs can still see QC reports. As for `TService` getting updated in XE8, there is very little chance of that happening since [XE8 is already in beta](http://community.embarcadero.com/blogs/entry/if-you-are-doing-ios-development-join-the-beta-to-get-access-to-the-64-bit-toolchain). – Remy Lebeau Mar 09 '15 at 19:03
  • @Lebeau: guess I should submit them my TServiceEx implementation that does exactly what is needed to support the "new" features. Unluckily I can't share it because I couldn't simply inherit from TService and modify its behaviour, too many private fields and function, even an attempt to inject some code using class helpers didn't work, I had to modify TService code (here you can find some of the changes I made: https://www.sandon.it/?q=node/97) Anyway I wrote X8*0*, it wasn't a typing mistake... it wasn't fixed in the past thirteen years, it will take many more years to be fixed.... – LDS Mar 11 '15 at 13:13
  • Excuses for my late reply. Thanks for the info, i will take a look at this. – Codebeat Mar 26 '15 at 21:55
  • SERVICE_ACCEPT_POWEREVENT <- literally been looking for this all morning, wow. thanks! – Jon Marnock Sep 26 '16 at 01:55
  • I have a similar issue, only inside of a `TThread` of a standard VCL forms application. Outside of a thread, works perfectly. Inside a thread, in the very same project, `WM_POWERBROADCAST` is never received. – Jerry Dodge Mar 10 '17 at 17:33
  • @JerryDodge `WM_POWERBROADCAST` doesn't care about threads. What will matter is how you created the window to which the message is to be sent, and perhaps how you implemented your message queue in the thread. – David Heffernan Nov 06 '19 at 17:21
  • Services will not receive suspend/resume notifiations via SERVICE_ACCEPT_POWEREVENT on systems with "modern" standby. Hibernate works as expected. Alternative methods of RegisterSuspendResumeNotification() and PowerRegisterSuspendResumeNotification() have the same limitation. This is due to the Desktop Activity Moderator which suspends desktop processes and throttles service (session 0) processes. It seems there is no way for services to be notified of suspend/resume on such "modern" systems. See: https://learn.microsoft.com/en-us/windows/win32/w8cookbook/desktop-activity-moderator – jimc Apr 21 '22 at 16:06
  • @jimc per that document: "*When DAM suspension is engaged or disengaged, **DAM triggers delivery of a WM_POWERBROADCAST message to those processes subject to suspension** that have opted-in to message delivery... There are no notifications when DAM throttling is engaged or disengaged. **Processes should not need modification; processes continue to function**, albeit at a slower rate.*" – Remy Lebeau Apr 21 '22 at 16:24
  • @Remy Lebeau - That was my point. When "modern" standby occurs, desktop applications are suspended and services are (heavily) throttled. The traditional notification mechanism for services (SERVICE_ACCEPT_POWEREVENT) and the new mechanisms RegisterSuspendResumeNotification() and PowerRegisterSuspendResumeNotification() don't report "modern" standby to services because strictly speaking they are not suspended. This makes it very difficult for services, which must modify their behavior or do clean-up actions on suspend/resume to work as before. In other words, services live in a limbo world. – jimc Apr 22 '22 at 09:34