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:
InitHandler:powershell.exe -ExecutionPolicy Bypass -File C:\Temp\myhandler.ps1 initHandler:powershell.exe -ExecutionPolicy Bypass -File C:\Temp\myhandler.ps1 mainTermHandler:powershell.exe -ExecutionPolicy Bypass -File C:\Temp\myhandler.ps1 term
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:
- The success/failure of the three handlers are logged to the Windows event log, if the appropriate log level is set.
- The failure of an earlier handler does not inhibit the later ones to run (this can be useful if the TermHandler needs to perform some action regardless of whether any of the earlier handlers were successful or not).
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:
- There's already a service called myservice in the system.
- myservice's executable is stored in
C:\Temp\myservice.exe. - If myservice's executable has been updated, the new version is stored in
C:\Temp\myservice.new.
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.