Main Contents »

Copyright © 2020 Ashok P. Nadkarni. All rights reserved.

Windows services are applications that run in the background and are managed through the Windows service control manager. This chapter describes configuration, control and implementation of services using Tcl.

1. Introduction

The highest of distinctions is service to others.
— King George VI

Most applications that users are familiar with run in the context of the user’s logged in desktop. However, some programs need to run in the background, possibly starting when the system is booted and independent of whether a user is logged in. Examples include server applications like web and database servers as well as critical Windows system components like Net Logon and Remote Procedure Call. These applications run as Windows services and conform to certain interfaces and restrictions.

Programming for Windows services has several aspects:

  • implementation of a program that can run as a service

  • installation and configuration of a service

  • executing control commands, such as stopping or starting services

  • monitoring service status

All these are more or less independent and can be implemented in isolation. We will describe them in the above order so that we can use the service we implement to illustrate the other operations.

All service related functionality requires the twapi_service package to have been loaded.

% package require twapi_service
→ 4.4.1
% namespace path twapi
Important Most of the code in this chapter requires the Tcl interpreter running with administrative privileges enabled.

2. Service concepts

Most of the details around implementation of a Windows service are hidden by the TWAPI package. We only describe what is necessary for Tcl programs to run as Windows services.

2.1. The Service Control Manager

On Windows, services run under the control of the Windows Service Control Manager (SCM) system component. They are managed through well-defined control interfaces that allow them to be configured, initiated and terminated by the SCM.

The SCM also maintains a service configuration database that includes definitions of each service, conditions under which it is to be run, security parameters and other service-specific information. It is also responsible for sending control commands and monitoring status information for all services.

2.2. Service states

The SCM defines certain values that represent the state of a service. In response to control signals from the SCM, a service may transition to a new state which it must report back to the SCM.

Table 1. Service states
State Description

continue_pending

The service is in the process of resuming after a pause.

pause_pending

The service is in the process of pausing.

paused

The service has temporarily paused operation while maintaining its internal state. The service either resumes or completely ceases operation on receipt of a continue or stop signal respectively.

running

The service is operational. In this state, the service is expected to be fully functional. It transitions into this state upon receipt of a start signal while in a stopped state, or a continue signal while in a paused state.

start_pending

The service is in the process of starting up.

stop_pending

The service is in the process of stopping.

stopped

The service is not operational. This is the initial state of a service and also the state services transition into upon receipt of a stop control signal.

When retrieving service information, any of the states may be returned. However, when implementing a service in Tcl, the programmer only has to deal with the states stopped, running, and paused. The other states are managed internally which simplifies the state machine the application has to implement.

2.3. Control signals

The primary requirement when running as a service under the control of the SCM is that the program must receive and respond to control signals and update its status. Thanks to the Tcl event loop this is fairly straighforward to do.

Service control signals lists the signals that may be received by a service. A service can specify which signals it supports as part of its start-up configuration.

Table 2. Service control signals
Control signal Description

continue

Sent to a paused service to resume operation.

interrogate

Sent to request a status update. Applications will not see this control signal as it is dealth with internally by the package.

pause

Sent to temporarily pause operation while maintaining state.

shutdown

Sent to indicate that the system is shutting down.

start

Sent to start the service.

stop

Sent to stop the service.

When the SCM sends a control signal, the service must respond by reporting its state to the SCM through update_service_status. This new state may or may not be the same as the current state.

2.4. Control notifications

In addition to control signals, the SCM may also send notifications to a service. These are listed in Service control notifications. Unlike for signals, a service does not need to explicitly respond or report its status to the SCM in response to a notification.

Table 3. Service control notifications
Notification Description

all_stopped

Sent when all services within a single process have been stopped.

hardwareprofilechange

Sent whenever there is a change in the system hardware profile, for example a new device being attached.

paramchange

Sent when one of the service configuration parameters has been modified. Indicates to the service that it should read its configuration information again.

netbindadd, netbinddisable, netbindenable, netbindremove

Sent for changes related to network bindings.

powerevent

Sent when there is a change in the power status of the system.

sessionchange

Sent for session changes like user logons.

user_defined

Service-specific events that can be defined by the service itself.

We will not be discussing notifications in detail as they are received in a similar manner to service control signals. Refer to TWAPI documentation or Windows Service Reference for details.

2.5. Service Control Programs

A service control program presents an interface that allows users to start and send control signals to a service and retrieve status information for display. Windows includes command line service control programs like sc.exe as well as GUI versions like the control panel Services applet.

We do not show examples of these here, preferring to demonstrate doing the same tasks directly from Tcl instead.

3. Writing a Windows service

In addition to the core functionality that it provides, a service must implement the following:

  • a dispatcher to receive the control signals and notifications from the SCM

  • a state machine, usually implemented as handlers for each of the possible control signals

  • registration of the implemented services with the SCM

  • running the Tcl event loop so that control signals are dispatched

We illustrate all these steps by implementing a sample service.

Our demo service is a simple time server that returns the current system time, similar to the sample program in Programming Server-Side Applications for Windows 2000.

The functionality of the time server is extremely simple. When a client connects, the server returns the current system time and closes the connection. The simplicity means the service does not have to deal with network protocols, security issues such as authentication, or safeguards against malicious input (it does not read any). That allows us to focus on the aspects related to Windows services without getting distracted by topics that are orthogonal to Windows services.

We start off loading the twapi_service package

package require twapi_service

We will store the configuration and current state of the service in the array state. We practice good programming hygiene by placing our code and definitions inside the timeserver namespace.

namespace eval timeserver {
    variable state
    array set state {
        name        MyTimeServer
        server_port 9999
        run_state   stopped
        utc         0
    }
}

3.1. Implementing the control dispatcher

The core of a Windows service is the dispatcher for the control signals so we start off writing that. The dispatcher is a callback that is invoked when a control event or notification is received from the SCM. It is called with at least the following three arguments:

  • the first parameter is the control signal or notification.

  • the second parameter is the name of the service for which the control is being sent. Processes that are running multiple services would make use of this to know which service is being targeted. In our case, we ignore the parameter since we have only one service implemented in our process. We do however need to pass it on to update_service_status.

  • the third parameter is the sequence number of the notification. We do not do anything with it other than passing it on to update_service_status.

There may be additional arguments supplied in the case of notifications. These depend on the notification type.

proc timeserver::control_dispatcher {control name seq args} {
    variable state
    switch -exact -- $control {
        start - stop - shutdown - pause - continue {
            $control
            twapi::update_service_status $name $seq $state(run_state)
        }
        userdefined {
            switch [lindex $args 0] {
                128 { set state(utc) 1}
                129 { set state(utc) 0}
            }
        }
        all_stopped {
            set [namespace current]::done 1
        }
    }
}

The handler responds to control signals by calling a procedure with the same name as the signal. We will define these later.

Also notice that after calling the appropriate procedure for a signal, we notify the SCM of the new status by calling update_service_status. This is mandatory for control signals. We pass it the name of the service, the sequence number that was passed in to us, and the new service state. The first two of these allow the SCM to match up status updates with the control signals it sends to services.

In addition to the control signals, control_dispatcher deals with two notifications:

  • The user_defined notification can be sent from a service control program such as sc.exe or from Tcl itself as we will see later. It can be passed additional numeric arguments which we use the switch the time responses from the server between local time and UTC.

  • The all_stopped notification is internally generated by the twapi_service package when all services in the process have been stopped. It is treated as a signal that the process can terminate. We do this by setting the done variable which, as we see later, will cause the Tcl event loop to terminate.

Note that no service status update needs to be called for these notifications.

Important All other signals and notifications that are not of interest should be gracefully ignored as they are not blocked in the current TWAPI implementation.

3.2. Implementing the control handlers

Implementation of the control handlers is straightforward. Note we have named the handlers based on the corresponding control signal names for convenience but there is no such requirement to always do so.

We begin with the handler for the start signal. This opens a new listening socket and sets the service state to running. The accept callback bound to the server socket simply writes the current time to the incoming connection and closes it.

proc timeserver::start {} {
    variable state
    if {[catch {
        set state(server_socket) [socket -server [namespace current]::accept \
            $state(server_port)]
    } msg]} {
        ::twapi::eventlog_log "Error binding to port $state(server_port): $msg. \
            $state(name) service exiting."
        exit 1
    } else {
        ::twapi::eventlog_log "$state(name) service started."
        set state(run_state) "running"
    }
}
proc timeserver::accept {sock addr port} {
    variable state
    if {$state(run_state) eq "running"} {
        puts $sock [clock format [clock seconds] -gmt $state(utc)]
    }
    close $sock
}

Note the accept checks if the service is in the running state before doing so. This check is required because the service might have transitioned to a paused or even stopped state between the time the accept handler was queued and the time is was run. It is important to keep the asynchronous nature of the event loop in mind when implementing connection and control signal callbacks in services.

We move on to the handle for the stop signal. This simply shuts down the listening socket and transition the service to a stopped state. The handler does not mark the process for exit. This is important in case we add other services to the same process. In general, a service process should exit only when it receives an all_stopped notification.

proc timeserver::stop {} {
    variable state
    if {$state(run_state) ne "stopped"} {
        close $state(server_socket)
        set state(run_state) "stopped"
        ::twapi::eventlog_log "$state(name) service stopped."
    }
}

We handle shutdown in identical fashion to the stop control signal.

interp alias {} timeserver::shutdown {} timeserver::stop

The pause and continue control signals are complementary. Pausing the service for our simple application just marks the state as paused so the accept call will not respond to incoming connections. The continue handler simply reverses this effect.

proc timeserver::pause {} {
    variable state
    if {$state(run_state) eq "running"} {
        set state(run_state) "paused"
        ::twapi::eventlog_log "$state(name) service paused."
    }
}
proc timeserver::continue {} {
    variable state
    if {$state(run_state) eq "paused"} {
        set state(run_state) "running"
        ::twapi::eventlog_log "$state(name) service continuing after pause."
    }
}

3.3. Registering with the SCM

Once its internal initialization is completed, the process has to register itself with the SCM in order to become a Windows service through the run_as_service command.

if {[catch {
    twapi::run_as_service {
        {mytimeserver timeserver::control_dispatcher}
    } -controls {stop shutdown pause_continue}
} msg]} {
    twapi::eventlog_log "Service error: $msg\n$::errorInfo"
    exit 1
}

The first argument to the command is a list of service definitions for the services (there can be more than one) implemented by the process. Each service definition consists of the service’s name and the name of the callback procedure to be invoked when the service is sent a control signal or notification. Our example only has one service definition.

Tip The service name is passed in to the callback procedure so, if desired, the same callback can be used for multiple services in a process.

The -controls option indicates which control signals are implemented by the process. The SCM will not send signals that are not included here, except for the start signal which is always sent. If unspecified, accepted signals default to stop and shutdown.

In case the registration is unsuccessful, we log an event and exit.

Note Because a service generally does not have a desktop attached and runs in the background, there is no way to display an error dialog to the user. By convention, errors are sent to the Windows error log, though some services may write to a private log file in addition to, or in lieu of, the event log.

Once registered, the program must wait for control signals from the SCM. A service should not actually begin its function, like serving out time in our example, until it receives a start signal from the SCM.

3.4. Running the event loop

In order to receive control signals from the SCM, the program must be running the Tcl event loop. In our sample program, we use the vwait command to do this. On receiving an all_stopped signal, our handler control_dispatcher sets the done variable which causes the vwait command to return, terminating the event loop. The script then ``falls off the end'' causing the process to exit in normal fashion.

vwait ::timeserver::done

3.5. Single Pager - Simple Time Server

Here is the complete program listing:

Program Listing - Simple Time Server
# timeserver.tcl
package require twapi_service

namespace eval timeserver {
    variable state
    array set state {
        name        MyTimeServer
        server_port 9999
        run_state   stopped
        utc         0
    }
}

proc timeserver::control_dispatcher {control name seq args} {
    variable state
    switch -exact -- $control {
        start - stop - shutdown - pause - continue {
            $control
            twapi::update_service_status $name $seq $state(run_state)
        }
        userdefined {
            switch [lindex $args 0] {
                128 { set state(utc) 1}
                129 { set state(utc) 0}
            }
        }
        all_stopped {
            set [namespace current]::done 1
        }
    }
}

proc timeserver::start {} {
    variable state
    if {[catch {
        set state(server_socket) [socket -server [namespace current]::accept \
            $state(server_port)]
    } msg]} {
        ::twapi::eventlog_log "Error binding to port $state(server_port): $msg. \
            $state(name) service exiting."
        exit 1
    } else {
        ::twapi::eventlog_log "$state(name) service started."
        set state(run_state) "running"
    }
}
proc timeserver::accept {sock addr port} {
    variable state
    if {$state(run_state) eq "running"} {
        puts $sock [clock format [clock seconds] -gmt $state(utc)]
    }
    close $sock
}

proc timeserver::stop {} {
    variable state
    if {$state(run_state) ne "stopped"} {
        close $state(server_socket)
        set state(run_state) "stopped"
        ::twapi::eventlog_log "$state(name) service stopped."
    }
}

interp alias {} timeserver::shutdown {} timeserver::stop

proc timeserver::pause {} {
    variable state
    if {$state(run_state) eq "running"} {
        set state(run_state) "paused"
        ::twapi::eventlog_log "$state(name) service paused."
    }
}
proc timeserver::continue {} {
    variable state
    if {$state(run_state) eq "paused"} {
        set state(run_state) "running"
        ::twapi::eventlog_log "$state(name) service continuing after pause."
    }
}

if {[catch {
    twapi::run_as_service {
        {mytimeserver timeserver::control_dispatcher}
    } -controls {stop shutdown pause_continue}
} msg]} {
    twapi::eventlog_log "Service error: $msg\n$::errorInfo"
    exit 1
}

vwait ::timeserver::done

We have not actually run the service though. We need to go through a couple of additional steps to run a service:

  • the service has to be installed and configured on the system

  • the SCM has to be told to start the installed service

We do these next.

4. Service installation and configuration

Installing a service is straightforward. Although the sc.exe Windows program can be used for the purpose, we will do it programmatically with the create_service call.

4.1. Configuration settings

Before installing our service, we discuss the configuration settings that may be associated with a service and the corresponding option to use with create_service when installing the service.

Note these can be configured at the time a service is installed and also modified later with the set_service_configuration command.

4.1.1. Internal name

The service internal name is the unique name that identifies the service. Most twapi_service commands take this as the first argument to identify which service is the target of the command.

4.1.2. Display name

The service display name is the user visible name for the service. This is displayed to the user by the Windows service-related programs like net start or the services control panel applet. The corresponding option to twapi_service commands is -displayname.

4.1.3. Service type

The SCM deals with four types of Windows services indicated through the -servicetype option of create_service. The possible values are shown in Service types.

Table 4. Service types
-servicetype value Service type

kernel_driver

Kernel driver

file_system_driver

File system driver

win32_own_process

User mode service that runs in its own process

win32_shareprocess

User mode service that runs in a shared process

All these types of services may be installed and configured through Tcl. However, Tcl (or any language other than C/C++) can only implement the user mode services.

4.1.4. Start type

The conditions under which a service is started is configured with the -starttype option to create_service. The possible values are shown in Service start types.

Table 5. Service start types
-starttype value Service start conditions

system_start

Started automatically when the system is initialized on power-on (drivers only)

boot_start

Started automatically by the system loader at the start of the boot phase (drivers only)

auto_start

Started automatically by the SCM when the system boots

demand_start

Started on a request from another service or program

disabled

Indicates service is disabled and will not be started, even on request.

4.1.5. Service accounts

A service executes under the security context of an account. This account may be a standard user account created for the service or one of the special built-in system accounts in System accounts.

Table 6. System accounts
Account Description

LocalSystem

This account is very highly privileged and has more or less complete control over the local system (hence the name). Any vulnerabilities therefore put the entire computer at risk. Network resources are accessed using the credentials of the local system, which may be a domain account if it belongs to a domain.

LocalService

This account is similar to an authenticated user account and has limited privileges. It can only access network resources (like shares) using anonymous credentials. For these reasons, any vulnerabilities have relatively limited impact on the system itself and are unlikely to spread to other systems through the network.

NetworkService

This account is almost identical to the LocalService account except that when accessing network resources, it uses the credentials of the computer’s account similar to the LocalSystem account. Thus a vulnerability that results in a malware infection has a greater chance of spreading to other systems.

Choosing an account under which to run a service is crucial for two opposing reasons:

  • The account must have sufficient privileges to let the service perform its function.

  • To limit the potential damage arising from a security vulnerability in the service, the account must have no more than the minimum required privileges.

Microsoft recommends following a least-privilege hierarchy, trying accounts in the following order, and selecting the first that allows the service to be fully functional.

  • LocalService, always the first choice if possible

  • NetworkService, if anonymous network credentials are insufficient

  • a local user account, possibly created specifically for the service with the exact privileges required

  • LocalSystem, puts the entire system at risk but relatively limited network access using the computer credentials

  • a domain user account, may be needed if the service needs to access network resources for which the local computer account does not have access

  • local adminstrator account, only if a local user account, even with specific privileges added, will not do

  • domain administrator, should never be needed for a properly written service.

As far as possible, services should be written to run under one of the first three account types.

When installing a service, the -account option is used to specify the account under which the service is to run. The password for the account also needs to be specified with the -password option if the account is not one of the built-in system accounts.

Caution For historical reasons, the service is installed under the LocalSystem account if the -account option is not specified which, as stated earlier, is not generally desirable.

4.1.6. Dependencies

A service may make use of other services and require them to be running before it can start. These other services are then dependencies of the specified service while it is their dependent.

The SCM ensures that a service’s dependencies are running before it is started. Conversely, it will not stop a service if there are any dependent services running.

Note Service control programs such as sc.exe will explictly stop dependent services before stopping the specified service if asked to do so.

Service dependencies can be configured with the -dependencies option to create_service or set_service_configuration.

4.1.7. Load order

Windows loads services in a defined sequence based on load order groups. All services in a group are loaded before those in a group that comes later in the sequence. Note however that any dependencies for a service will be loaded before that service even if they belong to a group that appears later in the order.

The list of load order groups can be obtained from the Windows registry.

% print_list [registry get \
    HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\ServiceGroupOrder List]
→ System Reserved
  EMS
  WdfLoadGroup
  Boot Bus Extender
  System Bus Extender
...Additional lines omitted...

The -loadordergroup option can be specified with create_service and set_service_configuration to place the service in a specific group. Not specifying this option will mean the service is started after services in all the groups. For most application services, this is the correct behaviour.

4.1.8. Error control

Services can be crucial to a system and failure to start a service may make a system inoperational. The error control configuration settings specifies the action the SCM should take if a service fails to start. The possible actions and the corresponding values to use with the -errorcontrol option to create_service are shown in Service error controls.

Table 7. Service error controls
-errorcontrol value Action

ignore

Ignore the error and continue with the system start up.

normal

Log the error in the Windows event log and continue with the system start up.

severe

Log the error in the Windows event log. Then if the last known good configuration is being started, continue the start up. Otherwise, restart the system with the last known good configuration.

critical

Log the error in the Windows event log. Then if the last known good configuration is being started, abort the start up. Otherwise, restart the system with the last known good configuration.

4.1.9. Desktop interaction

In older versions of Windows, services that need to display a user interface could be configured to allow interaction with the desktop. However, this feature was deprecated and is not even available in newer versions of Windows. We therefore do not discuss it further.

The recommended method if user interaction is required is to implement a separate program that runs on the user’s desktop and communicates with the service through a secured mechanism like named pipes with appropriate security descriptors.

4.2. Installing a service

Having looked at the configuration options for a service, installation of a service is a straightforward task. We simply need to call create_service passing the command line the SCM needs to execute to run the service. In addition we can specify any configuration options whose default values are not suitable for the service.

Let us continue with our sample service demonstation by installing it with the following options:

  • We will run it under the LocalService account since we do not require any special privileges.

  • We will set a display name for the service, just for the heck of it

  • We do not want it automatically started so we set it to demand start.

% set exe_path [file nativename [file attributes [info nameofexecutable] \
    -shortname]]
→ D:\tcl\magic\bin\tclsh.exe
% set script_path [file nativename [file attributes [file join [pwd] scripts \
    timeserver.tcl] -shortname]]
→ D:\src\tcl-on-windows\book\scripts\timeserver.tcl
% create_service MyTimeServer "$exe_path $script_path" -account "NT \
    Authority\\LocalService" -displayname "Time Server" -starttype demand_start

Although the command itself is straightforward, a few points are worth noting in the above example.

  • Windows services are expected to be console applications and Tcl scripts that run as services must be run using the console version of Tcl, tclsh.exe, and not the GUI version wish.exe. The above code assume you are running tclsh and not wish. In the latter case, you need to replace the executable path with the path to tclsh.

  • The command line for the service invokes tclsh and passes it the script we wrote earlier. You may of course have to change the script path to match your system.

  • To avoid quoting problems in case of paths with spaces in them, we construct a command line that uses the short names of the file paths.

  • We also pass the file names in native format using \ rather than /. Although many Windows programs will accept either, some do not and hence it is always recommended practice to use file nativename to convert them.

Important When constructing the command line, keep in mind that the account under which the service will run is quite likely different than the account used for installation. This means differences in drive letter assignments, environment settings like PATH and TCLLIBPATH etc. have to be taken into account. You have to also ensure that the script is accessible to the service account which it may not be if it is placed in your own secured Documents area.

4.3. Uninstalling a service

Like installation, uninstalling a service can be done either with the sc.exe Windows program or directly from Tcl with the delete_service command.

However, we leave illustrating uninstallation for the end as we still have to demonstrate our sample service.

4.4. Querying and modifying configuration

All configuration parameters of a service can be queried and modified at any time. Yet again, we can use sc.exe or do it from Tcl with get_service_configuration and set_service_configuration.

We do not discuss this further except to mention that the options used with create_service are applicable to get_service_configuration and set_service_configuration as well. In addition, they also take a -command option which corresponds to the service invocation command line that is passed as the second argument to create_service.

% set_service_configuration MyTimeServer -displayname "Super Accurate Time \
    Service"
% print_dict [get_service_configuration MyTimeServer -starttype -command \
    -displayname]
→ -command       = D:\tcl\magic\bin\tclsh.exe D:\src\tcl-on-windows\book\script...
  -displayname   = Super Accurate Time Service
  -starttype     = demand_start

5. Controlling services

We are now ready for the final steps needed to illustrate our sample service by running it and having it respond to control signals.

However, we will first define a small procedure that we can call to query it for the time.

proc query_time {} {
    set so [socket 127.0.0.1 9999]
    set response [read $so]
    close $so
    return $response
}

5.1. Starting a service

The start_service command starts up a specified service.

% start_service MyTimeServer
→ 0

Let us verify that the service indeed started by querying its status and retrieving the time.

% query_time
→ Fri Oct 09 10:54:11 IST 2020
% get_service_state MyTimeServer
→ running

5.2. Pausing a service

We can ask the service to temporarily pause its function by issuing the pause_service command.

% pause_service MyTimeServer
→ 0
% query_time
% get_service_state MyTimeServer
→ paused

Note we get an empty response back from our time server since it is paused.

5.3. Resuming a service

The continue_service command sends a continue signal causing the paused process to resume service.

% continue_service MyTimeServer
→ 0
% query_time
→ Fri Oct 09 10:54:11 IST 2020
% get_service_state MyTimeServer
→ running

5.4. Sending notifications

Let us send a user defined notification which will switch the service to returning UTC time. The notify_service command can send any user defined signal in the range 128-255.

Note This command is new in TWAPI 4.1. If you are using version 4.0, you can use control command of the Windows sc.exe program instead.
% notify_service MyTimeServer 128
% query_time
→ Fri Oct 09 05:24:11 GMT 2020

Hey, seems to all work.

5.5. Stopping a service

The stop_service command stops a running service.

% stop_service MyTimeServer
→ 0
% get_service_state MyTimeServer
→ stop_pending

Before moving on, let us uninstall the service as we have not more use for it in this chapter and there is no point having it clutter up the services database.

% delete_service MyTimeServer

6. Monitoring services

The final task we need to discuss is monitoring of service status.

6.1. Monitoring a service

The most basic command to retrieve the state of a service is get_service_state which we have already seen earlier. More detailed information is available through the get_service_status command. This command retrieves the last reported status of the service. We can use interrogate_service to force the service to update its status.

% interrogate_service rpcss
% print_dict [get_service_status rpcss]
→ checkpoint          = 0
  controls_accepted   = 192
  exitcode            = 0
  interactive         = 0
  pid                 = 1168
  service_code        = 0
  serviceflags        = 0
  servicetype         = win32_share_process
  state               = running
  wait_hint           = 0

Refer to the TWAPI documentation for details about the fields. The ones of particular interest are state which holds one of the values in Service states and pid which gives the process ID of the process hosting the service.

6.2. Monitoring all services

If the status of all services is desired, or for enumerating services, use get_multiple_service_status command. This command returns a recordarray with the same fields as returned by get_service_status and accepts options to limit the services returned.

For example, names of all driver services can be enumerated as follows.

% print_list [recordarray column [get_multiple_service_status -kernel_driver \
    -file_system_driver] displayname]
→ 1394 OHCI Compliant Host Controller
  3ware
  Microsoft ACPI Driver
  ACPI Devices driver
  Microsoft ACPIEx Driver
...Additional lines omitted...

Alternatively, more detailed information can be extracted, this time for user mode services that are active.

% recordarray iterate arec [get_multiple_service_status -win32_share_process \
    -win32_own_process -active] {
    puts "***Service $arec(displayname)***"
    parray arec
}
→ ***Service Adobe Acrobat Update Service***
  arec(checkpoint)        = 0
  arec(controls_accepted) = 1
  arec(displayname)       = Adobe Acrobat Update Service
  arec(exitcode)          = 0
  arec(interactive)       = 0
  arec(name)              = AdobeARMservice
  arec(pid)               = 4828
  arec(service_code)      = 0
  arec(serviceflags)      = 0
  arec(servicetype)       = win32_own_process
  arec(state)             = running
  arec(wait_hint)         = 0
  ***Service Application Information***
  arec(checkpoint)        = 0
  arec(controls_accepted) = 129
  arec(displayname)       = Application Information
  arec(exitcode)          = 0
  arec(interactive)       = 0
  arec(name)              = Appinfo
...Additional lines omitted...

6.3. Monitoring dependent services

A related command is get_dependent_service_status which is similar to get_multiple_service_status but takes an additional argument - the name of a service - and returns the status of all services that are dependent on it. This is useful when you want to shut down a service so that you can stop its dependents first.

The following command prints the list of all dependents of rpcss that are running.

% print_list [recordarray column [get_dependent_service_status rpcss -active] \
    name]
→ ZeroConfigService
  WSearch
  wscsvc
  WpnService
  WlanSvc
...Additional lines omitted...
Tip The dependent services are returned in reverse order of starting. Thus they should be stopped in the same order that they are returned.

7. Managing Remote Services

So far all our discussion and examples have targeted services on the local system. However, all commands we have described take a -system option which allows them to work with services on a remote system as well.

For example, a small change to one of the earlier examples shows the status of rpcss on a remote system.

% print_dict [get_service_status rpcss -system IO]
→ checkpoint          = 0
  controls_accepted   = 192
  exitcode            = 0
  interactive         = 0
  pid                 = 1168
...Additional lines omitted...

This capability is not just for status and monitoring commands, but for commands related to control, installation and configuration as well.

For remote access to work, the process must have an authenticated connection to the remote system.

8. References

RICH2000

Programming Server-Side Applications for Windows 2000, Richter, Clark, Microsoft Press, 2000. Chapters 3 and 4 detail writing Windows services and control programs.

SDKSERV

Windows Service Reference, Windows SDK documentaion, http://msdn.microsoft.com/en-us/library/windows/desktop/ms685974(v=vs.85).aspx.