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 init
Handler
:powershell.exe -ExecutionPolicy Bypass -File C:\Temp\myhandler.ps1 main
TermHandler
: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.