Converting Perl Scripts to Win32 Perl Services

Progressive Perl for Windows

Dave Roth

April 6, 2003

12 Min Read
ITPro Today logo in a gray background | ITPro Today


Most programs require that a user log on to the system to run them. However, long ago before Windows ever appeared, UNIX developers realized the importance of having some programs run from the time that a computer booted until the computer was shut down because the programs provided important services that had to run, even when nobody was logged on. Hence, the daemon was born. A daemon is software that's designed to run at boot time and keep running, without user intervention. The OS launches the program because a user might not be available to do so.

Most of the common services associated with the Internet are daemons, such as Web, FTP, email, firewall, streaming-media, and print services. These services start running when the computer starts and stop running when the computer shuts down.

Windows OSs based on the Windows NT kernel (e.g., Windows Server 2003, Windows XP, Windows 2000, NT) refer to daemons as services. You can find your machine's Win32 services and their states (e.g., running, stopped) in the Control Panel Services applet, which is in the Control Panel's Administrative Tools folder. When you double-click the Services applet, the Microsoft Management Console (MMC) Services snap-in appears. The Services snap-in lists each service's name and provides information about that service, such as its description and status.

Win32 services are useful for many reasons. For example, Win32 services provide the means to run a program when the OS starts. In addition, you can control a service from a remote machine.

You can make almost any application or tool a Win32 service. For example, consider a script that pings a server every minute to determine whether the server is online. This script is a perfect candidate for a Win32 service because it needs to run as soon as the OS is loaded and must continue running regardless of which users log on and log off.

Win32 services are truly great tools. However, when you use C or Visual Basic (VB) to develop them, the task is fairly daunting because you have to manage multiple threads interacting at different times. This task leaves you with little time to benefit from the services. However, you can use a Perl script to create Win32 services, which is far easier than using C or VB to create them. And modifying a service is as easy as modifying a Perl script.

How Win32 Perl Services Work
Windows uses a specialized software manager called the Service Control Manager (SCM) to handle services. The SCM manages the state of a service. When Windows boots up, the SCM starts any service configured to start at boot time. When a user wants to start, pause, or stop a service, the SCM performs the action on the user's behalf. The SCM then interacts with the service to inform it that its state must change.

When the SCM starts a service, the SCM executes the service's .exe file. For Perl scripts, the SCM launches perl.exe and passes to perl.exe the name of the Perl script and any parameters that the script needs. The Perl script then tells the SCM to treat it as a service. Thereafter, the script is officially considered to be running as a service. In addition to whatever tasks the script is programmed to do, the script has only one service-related task that it must perform: monitor the SCM for state changes.

The SCM receives requests to change the state of a service. Such requests can come from not only local or remote users but also local or remote applications and system components. Thus, the Perl script continuously monitors the SCM for state changes and reacts accordingly. For example, when the state changes to SERVICE_PAUSE_PENDING, the script reacts by performing whatever actions are required to make the script pause. The script must then inform the SCM that it has entered the SERVICE_PAUSED state. The script reacts similarly to the SERVICE_START_PENDING, SERVICE_CONTINUE_PENDING (essentially unpausing), and SERVICE_STOP_PENDING states and performs the necessary actions to enter the pending state.

One of the most important states is SERVICE_RUNNING. When the SCM reports this state, the script performs the actions for which it was designed. For example, if the script was designed to ping a server every minute, during the SERVICE_RUNNING state, the script issues a ping every minute.

The Win32::Daemon Perl Extension
A few tools are available to create a Win32 Perl service, such as the srvany.exe freeware utility and ActiveState's PerlSvc utility. However, srvany.exe has limitations, and PerlSvc requires the purchase of ActiveState's Perl Dev Kit. An alternative is the Win32::Daemon extension (http://www.roth.net/perl/daemon) and the Win32::Daemon::Simple extension (http://jenda.krynicky.cz), which depends on Win32::Daemon. These extensions provide full Win32 service functionality from a simple Perl script.

You can install Win32::Daemon by running the Perl Package Manager (PPM) from a command line:

PPM install http://www.roth.net/perl/  packages/win32-daemon.ppd

Creating a Process-Monitoring Service
In the Code Library on the Windows Scripting Solutions Web site (http://www.winscriptingsolutions.com), you'll find ProcMon.pl, a script that creates a process-monitoring service. You can use this script to prevent specific processes from running. For example, I configured the script to prevent users from running the registry editors (regedit.exe and regedt32.exe) and the FTP client. You can also use the script to terminate a process after the process has run for a specific amount of time. For example, I configured the script to terminate any Telnet session that runs longer than 2 minutes. This termination prevents users from staying logged on to a Telnet client for long periods of time.

The script's %PROC_LIST hash lists the applications to kill and when to kill them, as the code in Listing 1 shows. Each hash key lists the application's filename (e.g., telnet.exe), which must be in all lowercase letters. The value associated with each hash key specifies how many seconds the application is allowed to run before being terminated. Notice that all but one of the applications have a value of 0 seconds. This value causes the script to terminate those applications just after the script notices they're running.

Next, the script sets the service configurations. Listing 2 shows this block of code. The code first sets the default configuration settings, then calls the Configure() subroutine to check for user-specified parameters, some of which might override the default settings. Depending on the configuration options you specify when you launch ProcMon.pl, the code either installs the script as a service, stops the script from running as a service, or displays the script's syntax and exits. Figure 1 shows the configuration options that you can use when launching ProcMon.pl.

After setting the service configurations, the script creates the log file. The default log's filename is the name of the script with the .log extension. So, if you leave the script's name as ProcMon.pl, the resulting default log file will be ProcMon.log. The log file will reside in the same directory as the script. The code at callout A in Listing 2 configures this default location. However, you can override the default by using the -l parameter when you launch the script.

Starting the Service and Processing Service States
Now that the script has made the necessary preparations to run as a service, the script starts the service by calling the Win32::Daemon::StartService() function, as Listing 3 shows. If the call is successful, the SCM starts interacting with the Perl script as if the script were a built-in Win32 service. If the call fails, the script logs an error message and exits.

After the service starts, the script continuously checks the SCM for messages. The script uses a long loop that continuously calls the Win32::Daemon::State() method to check for any state change. In this loop, which Listing 4, page 12, shows, the script has a block of code for each possible state that can occur. The code executes an appropriate action for that state. For example, the block of code for the SERVICE_PAUSE_PENDING state doesn't do anything other than log an indicator that specifies that the script is entering the paused state. Each block of code calls the Win32::Daemon::State() method, which passes in a new state. The loop continues until the state changes to SERVICE_STOPPED, at which time the script cleans up any loose ends (e.g., closes open files, deletes temporary files), then ends.

Of the different states that the main loop monitors, the most important states are SERVICE_START_PENDING, SERVICE_STOP_PENDING, SERVICE_RUNNING, and SERVICE_STOPPED. The script enters the SERVICE_START_PENDING state only once—at the beginning of the script. As the code at callout A in Listing 4 shows, the code initiates a Windows Management Instrumentation (WMI) connection that the script uses throughout the life of the service. The code then instructs the SCM that the service has officially entered the SERVICE_RUNNING state.

The SCM returns a SERVICE_STOP_PENDING state when a user, application, or system component requests the service to stop. In response, the script cleans up any loose ends and prepares for the service's termination. Afterward, the script calls the Win32::Daemon::State(SERVICE_STOPPED) function to indicate that the script has stopped processing service requests. When the SCM returns the SERVICE_STOPPED state, the main loop ends and the script shuts down the service by calling the Win32::Daemon::StopService() functions, then exits.

The script uses the SERVICE_RUNNING state to do the bulk of its work. When the loop detects this state, it runs the code at callout B in Listing 4. This block of code first checks the clock to see whether enough time has passed since the last check of running processes. This amount of time is configurable through the -proctime command-line parameter. If the specified amount of time has passed, the code uses WMI to walk through each running process on the machine. The code compares each process name with the names in the %PROC_LIST hash. When a match occurs, the code compares how long the process has been running with the permitted time in the %PROC_LIST hash. If the process has been running longer than the permitted time, the code terminates the process.

The final line of the code at callout B is important. It tells the script to fall asleep for 25 milliseconds (ms). This sleep cycle occurs for each process to prevent WMI from spiking the CPU load.

The last state that the main loop checks for is an unknown state. Some services have special states that specific applications trigger. To the SCM, these states are unknown. Because the SCM can indicate an unknown state, the script must be able to handle this state. Every time the script tells the SCM that it successfully changed states (e.g., moved from SERVICE_PAUSE_PENDING to SERVICE_PAUSED), the script remembers the new state (e.g., SERVICE_PAUSED) by storing that information in the $PrevState variable. Then, when an unknown state occurs, the script ignores the unknown state and tells the SCM that the service is still in the state that the $PrevState variable specifies, as the code at callout C in Listing 4 shows.

Finally, the main loop falls asleep for a short amount of time, as the code at callout D in Listing 4 shows. This sleep cycle prevents any CPU spiking that might occur if the loop runs amok. Typically, the main loop doesn't need to run constantly anyway. By default, the script falls asleep for 100ms before starting over.

Customizing ProcMon.pl
Now that you understand how a Win32 Perl Service works, you can customize ProcMon.pl to monitor the processes that you want to limit. Simply modify the %PROC_LIST hash in Listing 1 to include those processes. Then, to install the script as a service, use the following command to launch ProcMon.pl:

Perl ProcMon.pl -install

To start the service, run the command

net start ProcMon

To test the service, run a program that's in your %PROC_LIST hash. You can then check the log file for details about what the service has done.

Debugging the Service
If all goes well, your Win32 Perl service will run smoothly. However, problems can arise, and you might need to debug the service.

Win32 services have limitations that can be difficult to work around. The biggest limitation is that a service runs headless, which means it doesn't have a UI. Thus, Win32 services are difficult to debug because they don't have any windows that display information, so you can't directly interact with the services. This limitation can be especially frustrating for Perl scriptwriters because they typically use an interactive debugger to debug their scripts.

The most practical way to debug a Win32 Perl service is to have it write debug messages to a log file. You can then examine the log file to see the data that your script printed out. However, this type of debugging is slow and cumbersome because you have to walk through a potentially long log file just to see the results of debug messages. A better alternative is to run SysLogD.pl, which you can find in the Code Library. SysLogD.pl is quite beneficial when debugging a Win32 service because the service writes debugging messages to a named pipe rather than a log file. A named pipe acts as a medium between two processes: the server process that creates the named pipe and the client process that connects to the named pipe. From the viewpoint of both applications, the named pipe looks like a file that they can write to or read from.

SysLogD.pl uses the Win32::Pipe extension (which comes with ActiveState's ActivePerl) to create the named pipe. After creating the named pipe, the script waits for an application to connect to it. A perfect example of a client application connecting to the named pipe is a Win32 service that uses the named pipe as a log file.

After an application connects to the named pipe, the script reads from the named pipe while the client is still connected. The call to the named pipe's Read() function is a blocking call. Therefore, the Read() function won't return anything until the client application has either written data to the named pipe or disconnected from the named pipe. If the application wrote data to the named pipe, the Read() function returns the data. SysLogD.pl displays anything that a client application might send to it. If the application disconnected from the named pipe, the Read() function returns nothing at all.

To test SysLogD.pl, run the script in a cmd.exe window. Then, in another cmd.exe window, run the following Dir command, which redirects the output to the named pipe:

Dir C:*.* > \YourMachineName  pipesyslog

Be sure to replace YourMachineName with your computer's name. The result is a directory listing in the Perl script's cmd.exe window.

Because named pipes work over a network, you can have your scripts log to a remote machine. If you have all your services on all your servers log to one remote machine, you would need to monitor only one computer to monitor an entire network of services.

Win32 Services for Everyone
Win32 services are incredibly powerful tools that you shouldn't be without. When you use Perl scripts, creating Win32 services is easy. So, have some fun and create some Win32 services. Drop me an email message sharing the kinds of Perl-based Win32 services that you've created.

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like