Updated 2010-03-12 13:20:30 by idb

Winserv [1] is a utility that can create an NT service running any application. When the application exits, the service becomes stopped.

Download winserv at http://www.sw4me.com/winserv.zip [36k]

Winserv can be used as a command-line utility to configure, control, view status of any NT service, whether it is winserv-based or not.

Winserv can operate with a remote machine's service control manager. When you use winserv from a command prompt, just prepend \\Machine\ to the service name if you want to operate with remote service:
 winserv status \\MyServer\alerter

The service name that winserv requires is not the display name of the service, but rather the internal unique name.

Winserv is designed with scripting languages in mind, TCL in particular. It can forward SCM signals to the application in different ways, thus enabling you to do any cleanup on stop and to implement "paused" service state.

Winserv contains a little Tcl package that lets you use it as a drop-in replacement for tclsvc.

Getting started

The basic syntax for winserv invocation is the following:
 winserv subcommand service-name options [ args ... ]

The only exception to this rule is the help subcommand that doesn't need any arguments (and ignores them, if any).
 winserv install service-name service-options command args ...

creates a service that runs command (any executable) when started and stops when the command completes. Command-line parameters for the command may also be specified.
 winserv configure service-name service-options [ command args ... ]

modifies various parameters for the service in SCM and registry databases. If the command is specified, it is stored in registry as a new command for the winserv-based service.
 winserv uninstall service-name

marks a service for deletion. When it is stopped and all handles to it are closed, the service is removed from the SCM database.
 winserv showconfig service-name

show the current service's parameters that may be modified with configure subcommand. Some parameters make sense only for winserv-based services, and they will not be shown for other services (read further for parameters description).
 winserv stop service-name  [ -nowait ]
 winserv pause service-name [ -nowait ]
 winserv continue service-name [ -nowait ]
 winserv usercontrol service-name [ -code <128-255> ]
 winserv paramchange service-name

These subcommands send control signals to the running service. Option -nowait means that the utility shouldn't wait until the service will report an appropriate status for the request (stopped for stop, paused for pause, running for continue).
 winserv start service-name [ args ... ]

Starts the service with the given command-line arguments.
 winserv restart service-name [ args ... ]

Restarts the service, i.e. stops it, waits for it to be stopped, and then starts it with the arguments given.
 winserv status service-name

Prints out the current status of the service, one of: RUNNING, STOPPED, PAUSED, START_PENDING, PAUSE_PENDING, CONTINUE_PENDING, STOP_PENDING.

Service options

Using install and configure subcommands, you can specify parameters for the newly-created or configured service. Some of the service options require an argument. Here is the list of supported service options:
 -displayname  <user-visible display name of the service>
 -description  <the description of the service, usually one or two sentences>
 -binary <pathname of winserv.exe, defaults to the invoked instance's pathname>
 -ipcmethod <must be blind, pipe, stdio or qstdio>
 -start <auto, demand or disabled>
 -errorcontrol <ignore, normal, severe or critical>
 -[no]expand
 -[non]interactive
 -loadordergroup <group>
 -depends service1,service2...
 -user <logon as user>
 -password <password>

Winserv will refuse to set binary pathname and some winserv-specific options for non-winserv based services. Use -forceforeign option to suppress this behavior.

Use -expand to store the application's command-line in registry as a REG_EXPAND_SZ type of value. In this case, all references to environment variables will be auto-expanded before starting the application:
 winserv install myappsrv -expand %SystemRoot%\MyApp.exe %ServiceArgs%

Note that you have to use -expand with %ServiceArgs% to pass the service's command-line parameters as extra arguments.

IPC methods

Winserv can communicate with the underlying application or script in three different ways:

  • -ipcmethod blind

It's the simpliest case, when the application is terminated with TerminateProcess if the service is stopped. There is no way for the application to do any cleanup, and it can't write to the event log or accept pause/continue and other signals. This method must be used only for 3rd-party closed-source applications that don't have any data in memory that must be written on exit.

  • -ipcmethod stdio

Winserv forwards the SCM signals in the textual form to the applications's stdin, and the application reports its state on stdout. The application can use special escape sequences to write to the eventlog with specific level (error, information, success, etc.), to signal its current status (paused, running), to declare what SCM control codes it accepts.

Any plain-text (escapeless) line from stdout is just written to the event log at the "information" level, and any line from stderr is written at the "error" level.

This IPC method may be used for closed-source application that doesn't know anything about winserv. In this case you won't be able to terminate the application with SCM control code (winserv stop); it must terminate by itself.

  • -ipcmethod qstdio

This method is similar to stdio, except that unescaped plain-text strings aren't forwarded to the event log. It may be useful if the application is too chatty.

  • -ipcmethod pipe

This method was designed especially for non-console tclkits, where we don't have access to normal stdin or stdout, but only to their emulation. The application must open two named pipes on startup:
 open \\\\.\\pipe\\winserv.scm.out.$service_name w+
 open \\\\.\\pipe\\winserv.scm.in.$service_name w+

and use the first one instead of stdout, and the second one instead of stdin. In all other aspects this method is equivalent to -ipcmethod stdio.

Don't change the order in which the pipes are opened! If you do it, the communication between winserv and the application can't be established any more!

If the named pipes are not opened after 30 seconds, winserv will terminate the application.

Porting tclsvc-based applications to winserv

is easy. You should install a winserv support package to a place where your interpreter can find it, and then add two lines of code at the beginning of your script:
 package require winserv
 winserv::startup

Notice that it won't prevent your script from being run by tclsvc: winserv support package checks tcl_service global varible and doesn't try to connect winserv if the variable already exists.

When running under winserv, winserv::startup sets the tcl_service and tcl_service_winserv global variables to 1. It imports eventlog command into the global namespace. This command is a mostly-compatible (though less powerful) replacement for the tclsvc's eventlog. It doesn't open the eventlog directly; instead, it uses the active IPC method to pass messages to winserv.

A lot of winserv-specific facilities become available after winserv::startup.
 winserv::accept ?[-]pause? ?[-]paramchange? ?[-]shutdown? ...

This command lets the application accept certain SCM control code groups. If the dash precedes the group name, it means that this group is not accepted any more.
 winserv::accept reset

Use it to accept only the STOP code, as winserv does by default.
 winserv::handle code script

This command defines a script to handle particular SCM control code (STOP, PAUSE, CONTINUE, PARAMCHANGE, NETBINDADD, NETBINDREMOVE, NETBINDDISABLE, NETBINDENABLE, as well as user-defined codes CODE128..CODE255).

For PAUSE and CONTINUE control codes the script can break or throw an error to indicate that the service status wasn't really changed (so it must leave running or paused, respectively).

Use empty script to remove the handler.

Internals

You know enough to use winserv with TCL. As of another scripting languages, you may want to implement helper modules, similar to TCL winserv support package. To do it, you have to know what escape sequences winserv interprets when the application writes it to stdout or named pipe.

Each string that winserv will parse must be terminated by a newline. If you use escape sequences, you must put each sequence on a line by itself.
 \033 a         accept/deny control codes:
 \033 a p       accept pause/continue control codes.
 \033 a c       accept PARAMCHANGE
 \033 a s       accept SHUTDOWN
 \033 a n       accept NETBIND... codes
 \033 a r       reset; accept STOP and nothing more.
 \033 a P       don't accept pause/continue
 \033 a C       don't accept PARAMCHANGE
 \033 a S       don't accept SHUTDOWN
 \033 a N       don't accept NETBIND...

 \033 s         set service status:
 \033 s p       the service is now paused
 \033 s P       the service is going to pause (PAUSE_PENDING)
 \033 s C       the service is going to continue (CONTINUE_PENDING)
 \033 s r       the service is running
 \033 s S       the service is going to stop (STOP_PENDING)

 \033 e         add message to the event log:
 \033 e i       at the information level
 \033 e e       at the error level
 \033 e s       at the success level
 \033 e w       at the warning level
 \033 e a       at the audit/success level
 \033 e A       at the audit/failure level.

For eventlog escapes, the message that will be added must follow the escape sequence on the same line. If the message contains embedded newlines, they must be replaced with \014 (form feed) control character.

Remote installation issues

Winserv can manage the services remotely. You can even install a service over the network, but there is something to remember:

  • Use remote registry access to get remote SystemRoot and Program Files paths.
  • Use administrative shared folders (C$, D$...) to copy all necessary files to the remote machine
  • Use the -binary option for winserv to specify the location of winserv.exe

on the remote machine.

Console applications

Unfortunately, applications for the console subsystem will almost always require some modification to survive logoff. It's only 7 lines of C code or so, and you can see src/tcl-nologoff.c for an example.

A lot of scripting language interpreters have a special variant of executable in their Windows versions: the executable is linked for the windows subsystem, not the console one, though it doesn't provide GUI per se. I recommend to use this kind of interpreters for your service, together with -ipcmethod pipe.

As of Tcl (tclsh*.exe console interpreter), the nologoff.dll included in the winserv support package takes care of making the interpreter ready to survive logoff. Winserv::startup will load this dll. If you use freewrap to create a stand-alone executable, you should copy this dll to the real filesystem and let winserv know where it is:
 package require winserv
 set winserv::nologoff_dll c:/Unwrapped/nologoff.dll
 winserv::startup

Again, it applies only to the console applications and console interpreters.

Licensing

Winserv is free software. It comes with a one-line license:
 Do what you want with this software, but don't blame me

The reason for such a license is that winserv is created to be shipped with other applications, and I don't want a developer to bother with any licensing issues. It doesn't mean that I'm not going to get money for this software.

Voluntary donations are gladly accepted. Go http://secure.emetrix.com/order/product.asp?PID=46366953 to support development of winserv with a donation of $20.

You will get a registration code that is unnecessary for winserv itself, as it doesn't have any limitations and doesn't require registration. But you'd better remember the code: it can become useful to get a discount for my other products.

Matthias Hoffmann: Just tried to run tclhttpd with winserv; only a few modifications are needed. Principally, it works great, but then noticed, as with tclsvc, that is doesn't work as expected, because logging off the user closes the tclsh84.exe-process. When logging off, win32 informs all running processes (windows) about the shutdown to give them the chance to gracefully shutdown which, in this case, is wrong! Perhaps hiding the tclsh84.exe process helps (e.g., with hidewndw.exe), just haven't tried this..... Also, another aspect: It would be nice if one could turn off the behaviour that winserv redirects stdout and stderr to the eventlog, because some applications are relatively chatty...

Anton Kovalenko: 30-Oct-2004. The logoff problem doesn't exist any more for TCL. Winserv support package loads a small dll that does the right thing. This problem affects console applications only. If you were trying wish with -ipcmethod pipe, the service wouldn't stop when a user is logging off.

As of your last suggestion: new ipc method qstdio was introduced, that is slightly different from stdio: all unescaped text strings are ignored, so it's unlikely that any application can pollute the event log unintentionally.

Matthias Hoffmann: Ok, I will try using wish. But this will blow up the application sizes, and services do not need any GUI... A new question: What does the following line mean:
 file delete d:/winserv.log

Anton Kovalenko: Nothing useful. I've just overlooked a piece of old debugging stuff.

Again, you don't have to use wish now, with current version of Winserv. Tclsh will work with any ipc method.

[David Burns]: Using winserv to run a starpack (using tclkit.exe 8.4.2.11) as a service encountered the following problem. The Tcl script within the starpack attempts to gain access to a starkit CUdatapack.dat that resides in the same directory as the starpack by constructing the full path to the starkit (using previously set variable current_outside_WD):
 set fullCUDfname [file join $current_outside_WD "CUdatapack.dat"]

It then tries to open the starkit with this command:
 vfs::mk4::Mount $fullCUDfname dhiway.kit -readonly

A reasonable handle is returned, but when the nominal name of the mount point is constructed:
 set kitPath [file join $current_outside_WD "dhiway.kit"]

and used is the target of a glob, e.g.:
 glob {$kitPath/*}

nothing is returned. When this same code is executed standalone (i.e., not under winserv) it executes correctly and access to the starkit is obtained.

The problem was traced to the fact that winserv does not set the "current working directory" parameter in the call to the Win32 API function CreateProcess (it defaults to NULL). As a consequence, at the time the starpack begins to execute, the current working directory (i.e., that returned from tcl pwd) is set to a windows system directory. This seems to cause this fault.

The starpack examines its argument list to check for an argument "SERVICE". This causes it to assume it cannot use Tk, and it runs in 'service-mode' (otherwise it assumes it is a Tk GUI application and puts up a configuration window). The workaround consists of adding another argument to be used as the current working directory before executing the starkit::startup command. So the "main.tcl" stub (inserted at the top of the VFS used to build the starpack) looks like this:
 global argv
 if { [string equal [string toupper [lindex $argv 0]] "SERVICE"] } {
      # Service mode
      cd "[lindex $argv 1]"
      } \
 else {
      # Non-Service 'configure' mode, started as a standard Tk GUI executable
      }

 # Note: This works...don't know if it works to set the cwd -after- the "starkit::startup", so don't change this!
 package require starkit
 starkit::startup
 package require app-dhis

[David Burns]: I encountered two minor bugs in winserv V1.10, to wit:

  1. The ipcmethod command-line switch doesn't seem to actually accept "blind" as a value, even though it is nominally a legal value. The workaround consists of letting it default to "blind" without actually mentioning it on the command-line.
  2. If you allow winserv to figure out the path to itself (which gets placed into the registry) it uses a call to win32 API functon GetModuleFileName. The string returned by this call is devoid of any surrounding double-quotes. However, if this path string contains one or more spaces, before winserv submits it to win32 API function CreateService it is supposed to be surrounded with double-quotes per the MSDN documentation. This isn't done.

As a consequence, if winserv resides in a path with spaces in it, an attempt to start the service being managed by winserv fails (under XP with code 193 "Not a valid executable"). The workaround for this is to use the -binary command-line switch to explicitly set the path to the winserv.exe file (including the required double-quotes) you want to use. (When you use Tcl to actually invoke winserve to install itself, you need to escape the Tcl exec command-line thoroughly to make sure the double-quotes 'get through' to be seen by winserv when it runs in response to the Tcl exec command).

A/AK: Thanks for this report. Both bugs are now fixed in Winserv 1.11.

As of the issue with starpack: it's better to avoid the assumption that the starpack always starts with a working directory containing the starpack. It may not be true in many other cases unrelated to winserv.

info nameofexecutable is the best way to find out the starpack location. All other files of your application will then be found under
 [file dirname [info nameofexecutable]].

[David Burns]: Much obliged for the fixes, winserv is nicely designed and I'm pleased to be able to use it.

The tip to use info nameofexecutable is useful and would serve to simplify the code shown as the workaround for obtaining access to a tclkit that resides in the same directory as the starpack. Lest the thrust of my observation be lost, I'll restate it using this tip.

Access from inside a starpack to a tclkit named CUdatapack.dat residing in the same directory as the starpack via the command:
  vfs::mk4::Mount [file join [file dirname [info nameofexecutable]] "CUdatapack.dat"] dhiway.kit -readonly

will not succeed (failing in the manner described above) unless this code has previously been executed:
 cd [file dirname [info nameofexecutable]]
 package require starkit
 starkit::startup
 package require app-dhis

My experience with winserv simply illustrated this shortcoming of vfs::mk4 Mount. It is not at all obvious why the Mount should fail for want of the current-directory being set appropriately (since the full path is passed into the Mount command), but this was my experience.

MHo: I'm about to turn tclhttpd into a starpack (which is done) and a winserv-driven service, which turned out to be problematic:

  • -ipcmethod (q)stdio works at the first glance, but... it seems that exec isn't working anymore. I think exec'd processes inherit the std handles, which could be the problem - the exec'd prog hangs and never returns, so the whole webserver hangs. perl-CGIs didn't work either.
  • using -ipcmethod pipe instead don't work at all (the service could not be started):
     catch {close stdout; close stdin}
     set stdout [open \\\\.\\pipe\\winserv.scm.out.$service_name w+]
     set stdin  [open \\\\.\\pipe\\winserv.scm.in.$service_name w+]

Has someone any ideas? However, the following test fragment throws no errors; maybe it has something to do with tclhttpd?....:
     winserv::startup
     winserv::accept pause

     proc open_sock {} {
         set ::chanfd [socket -server {echosvr} 999]
     }

     open_sock
     winserv::handle pause {catch {close $chanfd} }
     winserv::handle continue {catch {open_sock} }

     proc echosvr {chan host port} {
         puts -nonewline $chan "Hello, $host:$port! Glad to meet you. I was called like this: [catch {set ::env(ServiceName); set ::env(ServiceArgs)}]."
         close $chan
     }

     # [auto_execok] not available in service mode! let the os do the search (if service is running
     # under an account with a proper PATH/env-setup) or use absolute path's...
     catch {exec -- d:/dos/pbx/cmdauth.exe} rc
     puts [string map {\n \014} $rc]

     vwait forever

I also suspect that pwd isn't correct in service mode...

Other questions/things:

  • the switch -interactive isn't accepted by winserv.
  • winserv runs in the WIN32_OWN_PROCESS mode. Why can't we specify WIN32_SHARE_PROCESS mode? If I do this via sc, winserv won't operate any more...

MHo 2008-11-20: Will there ever be a 64 bit Version of winserv?

IDB: Great piece of work, thank you.

Regarding exec hanging: it does seem to be an issue with stdin. Appending
    << ""

to the command args of exec was enough to solve it for me.

Category Windows