Win32 machines have the ability to run programs when the computer boots up. In UNIX machines, these programs are called daemons; in Win32 machines, they're called services. Services run automatically; they don't need a user logging on to the machine.
Services provide many of the Win32 capabilities with which you're likely familiar. For example, the Winlogon service provides the ability to log on to a Windows 2000 or Windows NT machine. The Server service provides remote access to a machine's disks, named pipes, and other resources. The Scheduler service executes programs and scripts at scheduled times.
Systems administrators typically manage services by telling them to start, stop, pause, and resume (from a paused state) from a service manager or Microsoft Management Console (MMC) GUI application. However, you can use Perl to write scripts that programmatically perform the same actions if you need to perform those actions on many machines.
The Service Control Manager
Unlike UNIX daemons, Win32 services aren't quite so easy to develop. Although UNIX daemons can be a simple Perl or shell script, Win32 services are specialized programs that interact with the Win32 Service Control Manager (SCM). The SCM tells the service what to do and when to do it. For example, the SCM tells a service when to start, shut down, pause, and resume from a paused state.
Generally, all communication between a systems administrator and a service goes through the SCM. If a systems administrator runs the Net Stop command to stop a service, the command sends a request to the SCM to tell the service to stop. The SCM then manages the stopping process. The SCM also listens to the system at large for certain events, such as a system shutdown. If an event occurs, the SCM requests that an appropriate service handle the request.
The SCM manages relationships between services. For example, starting Microsoft IIS (i.e., Microsoft's Web service) requires two services: W3SVC and IISAdmin. The SCM first instructs IISAdmin to start. After IISAdmin is running, the SCM instructs the W3SVC service to run.
To understand how to manage services, you need to understand that each service has two names: a display name and a service name. The display name is the name the computer uses when displaying the service to users. Simple TCP/IP Services and World Wide Web Publishing Service are examples of display names. The service name is less elegant. It is what the computer calls the service internally. Examples are SimpTcp and W3SVC. When Perl interacts with the SCM, Perl uses the service names.
Perl and the SCM
Win32 Perl has two extensions that provide a script with functions to manage Win32 services—Win32::Service and Win32::Lanman. The Win32::Service extension comes standard with ActivePerl's version of Win32 Perl (http://www.activestate.com). Win32::Lanman is a separate download not included with the standard ActivePerl distribution. You can obtain Win32::Lanman from http://jenda.krynicky.cz or ftp://ftp.roth.net/pub/ntperl/others/lanman.
Although you can use both extensions to manage services, I discuss only the Win32::Service extension in this column because it's solely for services. In addition, because it comes with the ActivePerl package, you already have it on your system if you've installed ActiveState's Win32 Perl. However, only Win2K and NT support Win32::Service; Windows 9x and Windows Millennium Edition (Windows Me) don't. So, all the scripts in this month's column work with a standard installation of ActivePerl on Win2K and NT 4.0.
You can use Perl to enumerate the services installed on a given machine. You simply call the Win32::Service::GetServices() function:
( $Machine, \%List );
The function accepts a machine name ($Machine) followed by a hash reference (\%List). The machine name can be undef (i.e., an undefined value) or an empty string to represent the local machine; a simple name (e.g., ServerA); or an official machine name preceded with double backslashes (e.g., \\ServerA). If the function call is successful, it returns a true value (i.e., a nonzero value).
If the function is successful, keys representing the service names populate the hash. The keys' values are the display names.
Listing 1 shows how you use the Win32::Service::GetServices() function to display all the services on a machine. The code at callout A in Listing 1 populates the %List array with key value pairs consisting of the service name and the display name.
Querying a Service's Status
A Perl script can learn what state a service is in by querying the SCM. The Win32::Service::GetStatus() function performs just such a query:
( $Machine, $ServiceName,
The $Machine parameter is the same as the one in the GetServices() function. The second parameter, $ServiceName, is the service name (not the display name). The third parameter, \%Status, is a hash reference, which the function will populate with the keys and values that specify the service's status.
GetServices() provides many keys, but typically, only the hash's CurrentState key is of value to systems administrators. (For a list of all the keys, see Web-Exclusive Table 1 on the Windows Scripting Solutions Web site at http://www.winscriptingsolutions.com.) The CurrentState key represents a service's current state. Table 1 lists this key's possible values.
If the GetServices() function is successful, it returns a nonzero value. Unfortunately, because the Win32::Service extension doesn't export the constants in Table 1, the function returns the CurrentState key's constant value, not the constant's name. However, you can use the script ServiceStatus.pl in Listing 2 to return the constant name. The code at callout A in Listing 2 creates an array with the various service states in order such that the service-state value aligns with the correct array index. The code at callout B in Listing 2 queries the SCM for information about the service. Two lines later, the script references the CurrentState key, which represents the service's current state.
To use ServiceStatus.pl, you pass in the name of the machine you want to query followed by the service name. If you don't specify a machine name, the local machine is queried. For example, the call
queries the state of the W3SVC service on machine \\server.
If you take Listing 1 and add the ability to query the state of a service, you get a more complex version of ServiceStatus.pl. This new script, ListServices.pl, displays all services and their states on a specified machine. You can download ListServices.pl from the Code Library on the Windows Scripting Solutions Web site. To run the script, you just need to pass in the name of the machine you want to query. You don't need to specify a service name. For example, if you type
at the command line, the script queries the state of all the services on machine \\server. If you don't specify a machine name, the local machine is queried.
Changing the State of a Service
Gathering all this information about a machine's services is helpful, but what if you need to change a service's state? For example, let's say that you need to process all the files in your Web site, so you write a script that will modify some HTML tags in each file. During this time, you don't want anyone to access the files. Although using MMC to stop the Web service on a few remote machines wouldn't be time-consuming, having to manually stop the Web service on a hundred servers would be tedious. To ease the burden, you can use the Win32::Service extension to either start then stop or pause then resume the Web service.
The functions to call are intuitive:
( $Machine, $ServiceName );
( $Machine, $ServiceName );
( $Machine, $ServiceName );
( $Machine, $ServiceName );
Each function performs only the action that is representative of the function name. Each function takes a computer name as the first parameter. This parameter can be undef or an empty string to specify the local machine.
If the function is successful, it returns a nonzero value. If the function fails, it generates an error. A success means only that the service successfully submitted the requested command. You can't use a success or failure to determine a service's status. For example, if a call to the StartService() function returns a value of 1 (indicating success), it means the SCM received the request to start the service. If the service was already running, the function returns a value of 0—the service can't start if it's already started; hence, the function fails. Therefore, you can't depend on this return value to determine whether the service is in the desired state. For that, you need to call GetStatus().
If the service is already busy performing another request, the service you must use GetStatus() to determine whether the service processed your request. For example, suppose your script calls StopService() then immediately calls StartService(). The SCM might accept both requests and prompt the function to return values of 1. However, the service might be transitioning between requests.
In Listing 3, page 3, the script ResetServices.pl stops then restarts a predefined group of services. The code at callout A in Listing 3 defines the available groups. In this case, you have only one group called web, which consists of IIS's Web and FTP services. You can easily add additional groups.
The code at callout B in Listing 3 creates a hash called $ReverseList. This hash has keys consisting of service names and display names as values, which is the reverse of the hash that GetStatus() generates. Reversing the hash makes it a bit easier to look up a service's display name by providing its service name.
The real meat in this script is in two blocks: the stop block and the start block. The stop block at callout C in Listing 3 stops each service in the specified group. Note that the call to Win32::Service::StopService() returns after the SCM acknowledges it received the command, which doesn't necessarily mean that the command successfully executed. In other words, the code at callout C sends a stop command but doesn't test whether the service has actually stopped. This setup allows the code to execute stop commands asynchronously, which is beneficial when stopping multiple services (or when targeting multiple machines).
To determine whether the service has stopped, the code at callout D in Listing 3 calls the WaitState() function to query the service's status. If WaitState() returns a true value, the service has successfully stopped, so the code starts the service with the code in the start block. Finally, to determine whether the service has started, the script calls WaitState() again, as the code at callout E in Listing 3 shows.
The code at callout F in Listing 3 contains the WaitState() function. WaitState() queries the status of the service every second until either the service has stopped, started, or exceeded the timeout value you specify.
To run ResetServices.pl, you need to pass in the name of the machine. For example, if you type
perl resetservices.pl \\server
at the command line, the script resets the predefined services on machine \\server. If you don't specify a machine name, the local machine is queried.
Services Are Under Control
As these examples illustrate, you can quite easily use a Perl script to programmatically enumerate and determine the status of Win32 services. Even programmatically changing the state of a service is fairly easy.