The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Win32::Daemon::Simple - framework for Windows services

0.2.5

SYNOPSIS

        use FindBin qw($Bin $Script);
        use File::Spec;
        use Win32::Daemon::Simple
                Service => 'SERVICENAME',
                Name => 'SERVICE NAME',
                Version => 'x.x',
                Info => {
                        display =>  'SERVICEDISPLAYNAME',
                        description => 'SERVICEDESCRIPTION',
                        user    =>  '',
                        pwd     =>  '',
                        interactive => 0,
        #               parameters => "-- foo bar baz",
                },
                Params => { # the default parameters
                        Tick => 0,
                        Talkative => 0,
                        Interval => 10, # minutes
                        LogFile => "ServiceName.log",
                        # ...
                        Description => <<'*END*',
        Tick : (0/1) controls whether the service writes a "tick" message to
          the log once a minute if there's nothing to do
        Talkative : controls the amount of logging information
        Interval : how often does the service look for new or modified files
          (in minutes)
        LogFile : the path to the log file
        ...
        *END*
                },
                Param_modify => {
                        LogFile => sub {File::Spec->rel2abs($_[0])},
                        Interval => sub {
                                no warnings;
                                my $interval = 0+$_[0];
                                die "The interval must be a positive number!\n"
                                        unless $interval > 0;
                                return $interval
                        },
                        Tick => sub {return ($_[0] ? 1 : 0)},
                },
                Run_params => { # parameters for this run of the service
                        #...
                };

        # initialization

        ServiceLoop(\&doTheJob);

        # cleanup

        Log("Going down");
        exit;

        # definition of doTheJob()
        # You may want to call DoEvents() within the doTheJob() at places where it
        # would be safe to pause or stop the service if the processing takes a lot of time.
        # Eg. DoEvents( \&close_db, \&open_db, sub {close_db(); cleanup();1})

DESCRIPTION

This module will take care of the instalation/deinstalation, reading, storing and modifying parameters, service loop with status processing and logging. It's a simple to use framework for services that need to wake up from time to time and do its job and otherwise should just poll the service status and sleep as well as services that watch something and poll the Service Manager requests from time to time.

You may leave the looping to the module and only write a procedure that will be called in the specified intervals or loop yourself and allow the module to process the requests when it fits you.

This module should allow you to create your services in a simple and consistent way. You just provide the service name and other settings and the actuall processing, the service related stuff and commandline parameters are taken care off already.

use Win32::Daemon::Simple

All the service parameters are passed to the module via the use statement. This allows the module to fetch the service parameters before your script gets compiled, set the constants according to the parameters and to the way the script was started. Thanks to this Perl will be able to inline the constant values and optimize out statements that are not needed. Eg:

        print "This will print only if you start the script on cmd line.\n"
                if CMDLINE;

Service

The internal system name of the service (for example "w3svc"). The service parameters will be stored in the registry in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\$Service.

Name

The name of the service as it will be printed into the log file and to the screen when installing/uninstalling/modifying the service.

Version

The version number. This will be printed to the screen and log files and used by PDKcompile to set the version info of the EXE generated by PerlApp.

Info

This is a hash that is with minor changes passed to the Win32::Daemon::CreateService.

display

The display name of the service. This is the name that will be displayed in the Service Manager. Eg. "World Wide Web Publishing Service".

description

The description displayed alongside the display name in the Service Manager.

user

pwd

The username and password that the service will be running under. The accont must have the SeServiceLogonRight right. You can change user rights using Win32::Lanman::GrantPrivilegeToAccount() or the User Manager.

interactive

Whether or not is the service supposed to run interactive (visible to whoever is logged on the server's console).

path

The path to the script/program to run. This should either be full path to Perl, space and full path to your raw script OR a full path to the EXE created by PerlApp or Perl2Exe. This option will be set properly by the module and you should never specify it yourself. You should really know what you are doing and what before you do.

parameters

The "command line" parameters that are to be passed to the service. Please see below for the explanation of commandline parameter processing !!!

Params

This hash specifies the parameters that the service uses and their DEFAULT values. When the service is installed these values will be stored in the registry (under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\$Service\Parameters) and whenever the service starts the current values will be read and the module will define a constant for each of the subkeys.

Some of the parameters are used by the Win32::Daemon::Simple itself, so they should be always present.

Tick

Controls whether the module prints

        "tick: " . strftime( "%Y/%m/%d %H:%M:%S", localtime()) . "\n"

into the log file once a minute. (So that you could see that the service did not hang, but just doesn't have anything to do.) The ticking will be done only if you let the module do the looping (see ServiceLoop below).

If you do not specify this parameter here it will always be OFF.

Interval

Specifies how often should the module call your callback subroutine (see ServiceLoop below). In minutes, though it doesn't have to be a whole number, you can specify interval=0.5. The module will not call your callback more often than once a second though!

Not necessary if you do the looping yourself.

LogFile

The path to the log file. You should include this parameter so that the user will be able to change the path where the logging information is written. Currently it's not possible to turn the loggin off except by overwriting the Logging_code.

If you do not specify this parameter or use undef then the log file will be created in the same directory as the script and named ScriptName.log.

Description

This value of this parameter is included in the help printed when the script is executed with -help parameter. It should describe the various parameters that you can set for the service.

The values of these options are available as TICK, INTERVAL, LOGFILE and DESCRIPTION constants.

Param_modify

Here you may specify what functions to call when the user tries to update a service parameter. The function may modify or reject the new value. If you want to reject a value die("with the message\n"), otherwise return the value you want to be stored in the registry and used by the service.

                Param_modify => {
                        LogFile => sub {File::Spec->rel2abs($_[0])},
                        Interval => sub {
                                no warnings;
                                my $interval = 0+$_[0];
                                die "The interval must be a positive number!\n"
                                        unless $interval > 0;
                                return $interval;
                        },
                        Tick => sub {return ($_[0] ? 1 : 0)},
                        SMTP => sub {
                                my $smtp = shift;
                                return $smtp if Mail::Sender::TestServer($smtp);
                                # assuming you have Mail::Sender 0.8.07 or newer
                        },
                },

Logging_code

(ADVANCED) This option allows you to overwrite the functions that will be used for logging. You can log into the EvenLog or whereever you like.

                Logging_code => <<'*END*',
        sub LogStart {};        # called once when the service starts
        sub Log {};             # called many times. Appends a timestamp.
        sub LogNT {};           # called many times. Doesn't append a timestamp.
        sub OpenLog {}; # called once, just before printing the params
        sub CloseLog {};        # called once, just after printing the params
        sub CatchMessages {}; # not caled by Win32::Daemon::Simple
        sub GetMessages {}; # not caled by Win32::Daemon::Simple
        *END*

See below for more information about the functions.

Run_params

(ADVANCED) Here you can overwrite the service parameters. The values specified here take precedence over the values stored in the registry or specified in Params=> hash.

        Run_params => {
                LogFile => (condition ? "$Bin\\Foo.log" : "$Bin\\Bar.log"),
        }

Exported functions

ServiceLoop

        ServiceLoop( \&processing)

Starts the event processing loop. The subroutine you pass will be called in the specified intervals.

In the loop the module tests the service status and processes requests from Service Manager, ticks (writes "Tick at $TimeStamp" messages once a minute if the Tick parameter is set) and calls your callback if the interval is out. Then it will sleep(1).

DoEvents

        DoEvents()
        DoEvents( $PauseProc, $UnPauseProc, $StopProc)

You may call this procedure at any time to process the requests from the Service Manager. The first parameter specifies what is to be done if the service is to be paused, the second when it has to continue and the third when it's asked to stop.

If $PauseProc is:

        undef : the service is automaticaly paused,
                DoEvents() returns after the Service Manager asks it to continue
        not a code ref and true : the service is automaticaly paused,
                DoEvents() returns after the Service Manager asks it to continue
        not a code ref and false : the service is not paused,
                DoEvents() returns SERVICE_PAUSE_PENDING immediately.
        a code reference : the procedure is executed. If it returns true
                the service is paused and DoEvents() returns after the service
                manager asks the service to continue, if it returns false DoEvents()
                returns SERVICE_PAUSE_PENDING.

If $UnpauseProc is:

        a code reference : the procedure will be executed when the service returns from
                the paused state.
        anything else : nothing will be done

If $StopProc is:

        undef : the service is automaticaly stopped and
                the process exits
        not a code ref and true : the service is automaticaly stopped and
                the process exits
        not a code ref and false : the service is not stopped,
                DoEvents() returns SERVICE_STOP_PENDING immediately.
        a code reference : the procedure is executed. If it returns true
                the service is stopped and the process exits, if it returns false DoEvents()
                returns SERVICE_PAUSE_PENDING.

Pause

        Pause()
        Pause($UnPauseProc, $StopProc)

If the DoEvents() returned SERVICE_PAUSE_PENDING you should do whatever you need to get the service to a pausable state (close open database connections etc.) and call this procedure. The meanings of the parameters is the same as for DoEvents().

Log

Writes the parameters to the log file (and in commandline mode also to the console). Appends " at $TimeStamp\n" to the message.

LogNT

Writes the parameters to the log file (and in command line mode also to the console). Only appends the newline.

ReadParam

        $value = ReadParam( $paramname, $default);

Reads the value of a parameter stored in HKLM\SYSTEM\CurrentControlSet\Services\SERVICENAME\Parameters If there is no value with that name returns the $default.

SaveParam

        SaveParam( $paramname, $value);

Stores the new value of the parameter in HKLM\SYSTEM\CurrentControlSet\Services\SERVICENAME\Parameters.

CatchMessages

        CatchMessages( $boolean);

Turns on or off capturing of messages passed to Log() or LogNT(). Clears the buffer.

GetMessages

        $messages = GetMessages();

Returns the messages captured since CatchMessages(1) or last GetMessages(). Clears the buffer.

These two functions are handy if you want to mail the result of a task. You just CatchMessages(1) when you start the task and GetMessages() and CatchMessages(0) when you are done.

CMDLINE

Constant. If set to 1 the service is running in the command line mode, otherwise set to 0.

PARAMETERNAME

For each parameter specified in the params={...}> option the module reads the actual value from the registry (using the value from the params={...}> option as a default) and defines a constant named uc($parametername).

Service parameters

The parameters passed to a script using this module will be processed by the module! If you want to pass some paramters to the script itself use -- as a parameter. If you do then the parameters before the -- will be processed by the module and the ones behind will be passed to the script. If you do not use the -- but do call the program with some parameters then the parameters will be processed by Win32::Daemon::Simple and your program will end! You may use either -param or /param. This makes no difference.

The service created using this module will accept the following commandline parameters:

-install

Installs the service and stores the default values of the parameters to the registry into HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ServiceName\Parameters

If you get an error like

        Failed to install: The specified service has been marked for deletion.

or

        Failed to install: The specified service already exists.

close the Services window and/or the regedit and try again!

-uninstall

Uninstalls the service.

-start

Starts the service.

-stop

Stops the service.

-params

Prints the actual values of all the parameters of the service.

-help

Prints the name and version of the service and the list of options. If the parameters=>{} option contained a Description, then the Description is printed as well.

-default

Sets all parameters to their default values.

-PARAM

Sets the value of PARAM to 1. The parameter names are case insensitive.

-noPARAM

Sets the value of PARAM to 0. The parameter names are case insensitive.

-PARAM=value

Sets the value of PARAM to value. The parameter names are case insensitive.

You may validate and/or modify the value with a handler specified in the Param_modify=>{} option. If the handler die()s the value will NOT be changed and the error message will be printed to the screen.

-defaultPARAM

Deletes the parameter from registry, therefore the default value of that parameter will be used each time the service starts.

-service=name

Let's you overwrite the service ID specified in the

        use Win32::Daemon::Simple
                Service => 'TestSimpleService',

If you use this BEFORE -install, the service will be installed into HKLM\SYSTEM\CurrentControlSet\Services\[$name]

This allows you to install several instances of a service, each under a different name. Each instance will remember its name which you can access as SERVICEID.

If you want to change the parameters of one of the instances use

        service.pl -service=name -tick -logfile=name.log

without the -service parameter you are chaning the default service.

-service:name=name

Let's you overwrite the service display name and the name written to the log file. That is both

        use Win32::Daemon::Simple
                ...
                Name => 'Long Service Name',
                ...
                Info => {
                        display =>  'Display Service Name',

You may get the name as SERVICENAME.

-service:user=.\localusername

-service:pwd=password

You can specify what user account to use for the service. These parameters are ONLY effective if followed by -install !

-service:interactive=0/1

Let's you specify whether the service is allowed to interact with the desktop. This parameter is ONLY effective if followed by -install and if you do not specify the user and pwd!

--

Stop processing parameters, run the script and leave the rest of @ARGV intact. The -install, -uninstall, -stop, -start, -help and -params parameters cannot be used before the --.

If the service parameters contain -- then all the -param, -noparam, -param=value, -defaultparam and -default only affect the current run and are not written into the registry.

Examples

        script.pl -install

Installs the script.pl as a service with the default parameters.

        script.pl -uninstall

Uninstalls the service.

        script.pl -tick -interval=10

Changes the options in the registry. When the service starts next time it will tick and the callbacl will be called each 10 minutes.

        script.pl -notick -interval=5 --

Start the service without ticking and with the interval of 5 minutes. Do not make any changes to the registry.

        script.pl -interval=60 -start

Set the interval to 60 minutes in the registry, start the service (via the service manager) and exit.

        script.pl -- foo fae fou

Start the service and set @ARGV = qw(foo fae fou).

Comments

The scripts using this module are sensitive to the way they were started.

If you start them with a parameter they process that parameter as explained above. Then if you started them from the Run dialog or by doubleclicking they print (press ENTER to continue) and wait for the user to press enter, if you started them from the command prompt they exit immediately

If they are started without parameters or with -- by the Service Manager they register with the Manager and start your code passing it whatever parameters you specified after the --, if they are started without parameters from command prompt they start working in a command line mode (all info is printed to the screen as well as to the log file) and if they are started by doubleclicking on the script they show the -help screen.

To do

-install=name A way to override the service name set by the script. I will have to append -s_v_c_n_a_m_e=name to the service parameters! Needed for ability to run several instances of a service.

AUTHOR

 Jenda@Krynicky.cz
 http://Jenda.Krynicky.cz

 With comments and suggestions by extern.Lars.Oeschey@audi.de

SEE ALSO

Win32::Daemon.