svcwrap

svcwrap
Login

svcwrap is a Windows service which acts as a wrapper around custom commands, much like the old srvany.exe tool -- but with instsrv.exe integrated.

Installation

Check out the code and build the executable using cargo:

PS> cargo build --release

Note: When svcwrap is built from the default source tree, cargo is configured to statically link to the C runtime for the i686-pc-windows-msvc and x86_64-pc-windows-msvc targets, so the resulting executables should not require vcredist.

Locate the generated svcwrap.exe executable and copy it to the target system. It does not need to be used in any specific location, but it's important that it does not get moved after it has been installed as a service.

The svcwrap.exe command is installed using the subcommand install-service, which can be run with --help to get a command line help:

PS> .\svcwrap.exe install-service --help

To install a service called myservice, with the display name My Service and the description This is my service, and which depends on the Tcpip service, open an elevated command line and run:

PS> .\svcwrap.exe install-service --display-name "My Service" --description "This is my service" --depends Tcpip myservice

--depends NAME can be specifed multiple times to depend on multiple services.

The resulting service will be configured to autostart on boot, but it won't actually do anything by default.

Configuration

Once the service has been installed it needs to be configured. This is done using the registry. The configuration parameters are located in service's Parameters subkey (HKLM:\SYSTEM\CurrentControlSet\Services\<servicename>\Parameters).

By default the Parameters subkey will contain a LogLevel value. This can be used to control how much is logged to the Windows event log. It will default to warn. It can be set to off, error, warn, info, debug or trace. If an invalid value is used, it will be interpreted as error.

The most central value is Handler which must contain multi-string data (using multi-string avoids the need to escape/quote, at least at first level) where the first entry is the executable to run, and any following entries are the arguments to pass to the executable.

There are two special case handlers which can be added as InitHandler and TermHandler, both of which are also multi-string data. These are intended to be used in case some kind of pre-processing or post-processing is needed. InitHandler is run during the service's StartPending phase and TermHandler is run during the StopPending phase, which means they must complete quickly.

To run a single script (stored in C:\Temp\myhandler.ps1 for the purposes of this example) with all three stages, add the following multi-string values:

Then create a file C:\Temp\myhandler.ps1 with the contents:

Param (
  $stage
)

if($stage -eq "init") {
  # Run for InitHandler
}

if($stage -eq "main") {
  # Run for Handler
}

if($stage -eq "term") {
  # Run for TermHandler
}

Notes:

Windows services do not run with standard input/outputs, but svcwrap can be configured to capture the output of the InitHandler, Handler and TermHandler commands and store them to a file using the CaptureLog value. To log the commands' output to C:\Temp\myservice_commands.log, add the registry string value CaptureLog with the string data C:\Temp\myservice_commands.log. This log file will be cleared for each new run of the service.

Uninstallation

To uninstall a service run svcwrap.exe with the subcommand uninstall-service, followed by the service name. To uninstall the service myservice open an elevated command line and run:

PS> .\svcwrap.exe uninstall-service myservice

Example

One use-case for svcwrap is to conditionally upgrade a service binary at boot for another service, before it is started (because Windows locks executables in use, so replacing it should be done before starting it).

Assumptions:

Start by registering the update service; we'll call it myservice-update:

PS> .\svcwrap.exe install-service --display-name "Update MyService" --description "Wrapper for updating MyService prior to launching it" myservice-update

In the registry, under HKLM:\SYSTEM\CurrentControlSet\Services\myservice-update\Parameters add the multi-string value Handler with the data:

powershell.exe
-ExecutionPolicy
Bypass
-File
C:\Temp\update-myservice.ps1
main

Then add the multi-string value TermHandler with the data:

powershell.exe
-ExecutionPolicy
Bypass
-File
C:\Temp\update-myservice.ps1
term

Create a file C:\Temp\update-myservice.ps1 with the contents:

Param (
  $stage
)

if($stage -eq "main") {
  if(Test-Path -Path C:\Temp\myservice.new -PathType Leaf) {
    # A new file exists -- copy it over the old one
    Copy-Item C:\Temp\myservice.new C:\Temp\myservice.exe

    # Make sure it isn't copied over and over again
    Remove-Item C:\Temp\myservice.new
  }
}

if($stage -eq "term") {
  # Kick off myservice
  Start-Service myservice
}

And finally make sure that myservice is set to Manual start so its executable isn't locked before myservice-update is run.

To get simple logging in update-myservice.ps1, set CaptureLog as described in the Configuration section, and use Write-Host in the script.