Hello Perl-Pros.
I'm trying to write a Windows Service using Win32::Daemon and yes, I've used Super Search before writing this.
However, I am quite confused by the behavior of this module, and I wanted to a) share my observations and b) ask you for any experience in the areas I am dealing with.
First of all: Installing a service, as well as starting and stopping - all that works fine. But, due to the nature of my project, I am very interested in fetching the Messages that SCM sends, in particular SERVICE_CONTROL_SESSIONCHANGE and SERVICE_CONTROL_SHUTDOWN.
All Tests have been made on WindowsVista. Perl Version is 5.8, Win32::Daemon is Version 20030617.
I was not able to install 20080324, because it is looking for some *.al files (those are linked to XS, right?), which were not part of the sources that I've downloaded. I am happy about any hint on that as well.
The Skeleton that I've used is from the 20080324 Version.
The first thing that I had to learn was that the defined Constants were not all matching the documented ones from MSDN.
That's not really an issue, I've re-declared them and I'll include them here in case you find them useful:
use constant SERVICE_CONTROL_STOP => 0x00000001; use constant SERVICE_CONTROL_PAUSE => 0x00000002; use constant SERVICE_CONTROL_CONTINUE => 0x00000003; use constant SERVICE_CONTROL_INTERROGATE=> 0x00000004; use constant SERVICE_CONTROL_SHUTDOWN => 0x00000005; use constant SERVICE_CONTROL_PARAMCHANGE=> 0x00000006; use constant SERVICE_CONTROL_NETBINDADD => 0x00000007; use constant SERVICE_CONTROL_NETBINDREMOVE => 0x00000008; use constant SERVICE_CONTROL_NETBINDENABLE => 0x00000009; use constant SERVICE_CONTROL_NETBINDDISABLE=> 0x0000000A; use constant SERVICE_CONTROL_DEVICEEVENT=> 0x0000000B; use constant SERVICE_CONTROL_HARDWAREPROFILECHANGE => 0x0000000C; use constant SERVICE_CONTROL_POWEREVENT => 0x0000000D; use constant SERVICE_CONTROL_SESSIONCHANGE=> 0x0000000E; use constant SERVICE_CONTROL_PRESHUTDOWN => 0x0000000F; # Next 2 are not supported in Win2k and XP use constant SERVICE_CONTROL_TIMECHANGE => 0x00000010; use constant SERVICE_CONTROL_TGIGGEREVENT => 0x00000020; use constant SERVICE_STOPPED => 0x00000001; use constant SERVICE_START_PENDING => 0x00000002; use constant SERVICE_STOP_PENDING => 0x00000003; use constant SERVICE_RUNNING => 0x00000004; use constant SERVICE_CONTINUE_PENDING => 0x00000005; use constant SERVICE_PAUSE_PENDING => 0x00000006; use constant SERVICE_PAUSED => 0x00000007;

This brings me to the next thing that I find somehow misleading documented: The Communication of SCM with Win32::Daemon.
I am not sure if I understand this completely, please correct me if I am wrong:

As you can see above, we do have 2 classes of constants, One set represents Messages (SERVICE_CONTROL_*),
the second group defines Service States.
Controls can be fetched using
Win32::Daemon::QueryLastMessage(),
States can be set and fetched using Win32::Daemon::State().
If you hand over a non-zero value to Win32::Daemon::QueryLastMessage(), it will re-set the last Message.
The watch out here is that the function does return the reset value in this case, and not - as the documentation indicates - the last set Message !
Another Watch out is, that the Callback Function will still get the last Message that has been set, no matter if you reset it or not, it seems to be buffered "elsewhere".
Knowing all that I assumed the following flow:
1.) Win32::Daemon hands in a Control (= Message).
2.) The Script is responsible to act on this control and
3.) Sets the correct Status if needed.

BUT, as you may have noticed, there's no code for "SERVICE_CONTROL_START" (not at all, it's not defined in SCM documentation).
Which means, the Startup is indicated to you with the Status "SERVICE_START_PENDING".


Ok, got that. This means that a "STOP" is indicated by a "SERVICE_STOP_PENDING" - right?
To answer the question myself: yes, right. If you do a "net stop myservice", the Script gets this status, but it also gets a "SERVICE_CONTROL_STOP".
It's your decision which one to pick up.
Now we are coming to the things I am most interested in:
The other Controls.
To cut this story short: I don't get any of them.
Certainly I have used AcceptedControls() to subscribe them:
Win32::Daemon::AcceptedControls( &SERVICE_CONTROL_STOP| &SERVICE_CONTROL_PAUSE| &SERVICE_CONTROL_CONTINUE| SERVICE_CONTROL_INTERROGATE| &SERVICE_CO +NTROL_SHUTDOWN| &SERVICE_CONTROL_PARAMCHANGE| &SERVICE_CONTROL_NETBINDADD|&SERVICE_CONTROL_NETBINDREMOVE| + &SERVICE_CONTROL_NETBINDENABLE| &SERVICE_CONTROL_NETBINDDISABLE| &SERVICE_CONTROL_DEVICEEVENT| &SERVICE_CONTROL_HARDWAREPROFILECHANGE | &SERVICE_CONTROL_POWEREVENT|&SERVICE_CONTROL_SESSIONCHANGE| &SERVICE_CONTROL_PRESHUTDOWN|&SERVICE_CONTROL_TIMECHANGE| &SERVICE_CONTROL_TGIGGEREVENT );
But, I am ignored :-)

These are the ones I get: SERVICE_CONTROL_STOP, SERVICE_CONTROL_INTERROGATE, SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE
(BTW: STOP, PAUSE and CONTINUE do all come along with the "PENDING" states set.)

These are the ones I have not tested:
SERVICE_CONTROL_PARAMCHANGE, SERVICE_CONTROL_NETBIND*, SERVICE_CONTROL_HARDWAREPROFILECHANGE, SERVICE_CONTROL_POWEREVENT, SERVICE_CONTROL_TGIGGEREVENT

And I did not get anything for:
SERVICE_CONTROL_SHUTDOWN, SERVICE_CONTROL_SESSIONCHANGE, SERVICE_CONTROL_TIMECHANGE, SERVICE_CONTROL_DEVICEEVENT

I am mainly interested in any experience you might have in capturing SERVICE_CONTROL_SHUTDOWN, SERVICE_CONTROL_SESSIONCHANGE - Is it supposed to work ???
From what I see so far, I think it has never been tested.
And yes, I know the module is old - and maybe Perl is not the perfect language to write a Service.
My Product normally uses a C++ core as Service, but Perl is so much faster for Prototyping :-)

Here's the complete code for your reference:
use Win32::Daemon; use constant SERVICE_CONTROL_STOP => 0x00000001; use constant SERVICE_CONTROL_PAUSE => 0x00000002; use constant SERVICE_CONTROL_CONTINUE => 0x00000003; use constant SERVICE_CONTROL_INTERROGATE => 0x00000004; use constant SERVICE_CONTROL_SHUTDOWN => 0x00000005; use constant SERVICE_CONTROL_PARAMCHANGE => 0x00000006; use constant SERVICE_CONTROL_NETBINDADD => 0x00000007; use constant SERVICE_CONTROL_NETBINDREMOVE => 0x00000008; use constant SERVICE_CONTROL_NETBINDENABLE => 0x00000009; use constant SERVICE_CONTROL_NETBINDDISABLE => 0x0000000A; use constant SERVICE_CONTROL_DEVICEEVENT => 0x0000000B; use constant SERVICE_CONTROL_HARDWAREPROFILECHANGE => 0x0000000C; use constant SERVICE_CONTROL_POWEREVENT => 0x0000000D; use constant SERVICE_CONTROL_SESSIONCHANGE => 0x0000000E; use constant SERVICE_CONTROL_PRESHUTDOWN => 0x0000000F; use constant SERVICE_CONTROL_TIMECHANGE => 0x00000010; # XP + & Vista: Not Supported use constant SERVICE_CONTROL_TGIGGEREVENT => 0x00000020; # XP + & Vista: Not Supported use constant SERVICE_STOPPED => 0x00000001; use constant SERVICE_START_PENDING => 0x00000002; use constant SERVICE_STOP_PENDING => 0x00000003; use constant SERVICE_RUNNING => 0x00000004; use constant SERVICE_CONTINUE_PENDING => 0x00000005; use constant SERVICE_PAUSE_PENDING => 0x00000006; use constant SERVICE_PAUSED => 0x00000007; my %List; my $START_TIME = time(); my $DEFAULT_CALLBACK_TIMER = 5000; my ( $SCRIPT_DIR, $SCRIPT_FILE_NAME ) = ( Win32::GetFullPathName( $0 ) + =~ /^(.*)\\([^\\]*)$/ ); $| = 1; my $LOG_FILE = 'C:\RothExample.log'; if( open( LOG, ">$LOG_FILE" ) ){ my $StartTime = localtime( $START_TIME ); my $BackupHandle = select( LOG ); $| = 1; select( $BackupHandle ); } Win32::Daemon::RegisterCallbacks( \&CallbackRoutine ); Log( "Starting service" ); my %Context = ( last_state => SERVICE_STOPPED, count => 0, start_time => time(), ); Win32::Daemon::AcceptedControls(&SERVICE_CONTROL_STOP | &SERVICE_CONTROL_PAUSE | &SERVICE_CONTROL_CONTINUE | &SERVICE_CONTROL_INTERROGATE| &SERVICE_CONTROL_SHUTDOWN | &SERVICE_CONTROL_PARAMCHANGE| &SERVICE_CONTROL_NETBINDADD | &SERVICE_CONTROL_NETBINDREMOVE | &SERVICE_CONTROL_NETBINDENABLE | &SERVICE_CONTROL_NETBINDDISABLE| &SERVICE_CONTROL_DEVICEEVENT | &SERVICE_CONTROL_HARDWAREPROFILECHANGE + | &SERVICE_CONTROL_POWEREVENT | &SERVICE_CONTROL_SESSIONCHANGE | &SERVICE_CONTROL_PRESHUTDOWN | &SERVICE_CONTROL_TIMECHANGE | &SERVICE_CONTROL_TGIGGEREVENT ); Win32::Daemon::StartService( \%Context, $DEFAULT_CALLBACK_TIMER ); Log( "."x60 ); Log( "Shutting down the service." ); Log( "Start time: " . localtime( $Context{start_time} ) ); Log( "End time: " . localtime() ); Log( "Total running callback count: $Context{count}\n" ); # # Define the callback routine # sub CallbackRoutine { my( $Event, $Context ) = @_; my $Event_debug = Win32::Daemon::QueryLastMessage(); Win32::Daemon::QueryLastMessage(1); # Reset the Event my $State = Win32::Daemon::State(); Log( "---------- Entering Loop with Status/Event: $State/$Event ($ +Event_debug)" ); # Evaluate STATE if( SERVICE_RUNNING == $State ) { $Context->{count}++; Log( "Running!!! Count=$Context->{count}. Timer:".Win32::Daemo +n::CallbackTimer() ); } elsif( SERVICE_START_PENDING == $State ) { # Initialization code $Context->{last_state} = SERVICE_RUNNING; Win32::Daemon::State( SERVICE_RUNNING ); Log( "Service initialized. Setting state to Running." ); } elsif( SERVICE_PAUSE_PENDING == $State ) { $Context->{last_state} = SERVICE_PAUSED; Win32::Daemon::State( SERVICE_PAUSED ); Win32::Daemon::CallbackTimer( 0 ); Log( "Pausing." ); } elsif( SERVICE_CONTINUE_PENDING == $State ) { $Context->{last_state} = SERVICE_RUNNING; Win32::Daemon::State( SERVICE_RUNNING ); Win32::Daemon::CallbackTimer( $DEFAULT_CALLBACK_TIMER ); Log( "Resuming from paused state." ); } else { Log( "Service got an unknown STATE: $State" ); } # Evaluate CONTROLS / Events if( SERVICE_CONTROL_STOP == $Event ) { $Context->{last_state} = SERVICE_STOPPED; # eigentlich STOP_PE +NDING ??? Win32::Daemon::State( [ state => SERVICE_STOPPED, error => 123 +4 ] ); Log( "Stopping service." ); # We need to notify the Daemon that we want to stop callbacks +and the service. Win32::Daemon::StopService(); } elsif( SERVICE_CONTROL_SHUTDOWN == $Event ) { Log( "Event: SHUTTING DOWN! *** Stopping this service ***" ); # We need to notify the Daemon that we want to stop callbacks +and the service. Win32::Daemon::StopService(); } elsif( SERVICE_CONTROL_PRESHUTDOWN == $Event ) { Log( "Event: Preshutdown!" ); } elsif( SERVICE_CONTROL_INTERROGATE == $Event ) { Log( "Event: Interrogation!" ); } elsif( SERVICE_CONTROL_NETBINDADD == $Event ) { Log( "Event: Adding a network binding!" ); } elsif( SERVICE_CONTROL_NETBINDREMOVE == $Event ) { Log( "Event: Removing a network binding!" ); } elsif( SERVICE_CONTROL_NETBINDENABLE == $Event ) { Log( "Event: Network binding has been enabled!" ); } elsif( SERVICE_CONTROL_NETBINDDISABLE == $Event ) { Log( "Event: Network binding has been disabled!" ); } elsif( SERVICE_CONTROL_DEVICEEVENT == $Event ) { Log( "Event: A device has issued some event of some sort!" ); } elsif( SERVICE_CONTROL_HARDWAREPROFILECHANGE == $Event ) { Log( "Event: Hardware profile has changed!" ); } elsif( SERVICE_CONTROL_POWEREVENT == $Event ) { Log( "Event: Some power event has occured!" ); } elsif( SERVICE_CONTROL_SESSIONCHANGE == $Event ) { Log( "Event: User session has changed!" ); } else { # Take care of unhandled states by setting the State() # to whatever the last state was we set... Win32::Daemon::State( $Context->{last_state} ); Log( "Got an unknown EVENT: $Event" ); } return(); } sub Log{ my( $Message ) = @_; if( fileno( LOG ) ){ print LOG "[" . localtime() . "] $Message\n"; } }

In reply to Win32::Daemon Experience offered/requested by promd

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.