shawl: a Windows service wrapper for any command
Windows service wrapper for arbitrary commands
At a glance
- What is it?
- shawl is a small Rust program that turns an arbitrary executable into a Windows service. Your program does not have to know anything about the service control manager; it only has to respond to a control-C. Everything else, the service lifecycle, the restart policy, the logging and the status reporting, happens in the wrapper.
- Who is it for?
- shawl suits anyone who has written a service that works from a terminal and then had to make it a Windows service, since the only requirement it places on the program is that it stops when interrupted.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 42 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The service API, handled on your behalf
The description in the readme is the design in two sentences.
Shawl is a wrapper for running arbitrary programs as Windows services, written in Rust. It handles the Windows service API for you, so that your program only needs to respond to a control-C, which on the service side is the interrupt signal.
That is a small contract, and it is the whole reason the wrapper exists. Writing a Windows service means implementing the control handler protocol, reporting start and stop status to the service control manager, and surviving being asked to stop. Almost nobody wants to write that for a program that already runs fine from a terminal, so the wrapper takes it.
The deployment story follows from the contract. If you are creating a project that needs to run as a service, you bundle shawl with your project, set it as the entry point, and pass the command to run through the command line.
That is a different shape from the alternatives, and the readme is explicit about it later on: the wrapper does not prepare the service for you. It has no install step of its own. You create the service in whatever way suits your deployment, and shawl is simply the executable that the service runs.
For a project distributed as an installer package, that is the decisive property. A wrapper that has to be installed first, with its own setup, cannot be dropped into an existing installer without extra work, and a wrapper that does not need setup can be the entry point of an installer that already knows how to register a service.
Four ways to install, and one of them needs nothing
The installation section lists four routes, and the first one is the simplest thing a tool can offer.
Prebuilt binaries are available on the releases page, and it is portable, so you can download it and put it anywhere without going through an installer. There is no registry entry to add, no service to register, no state written outside its own directory.
If you have Rust installed, you can install it with the package manager, and the locked flag is worth noticing, because it pins the dependency versions to the ones the author tested.
cargo install --locked shawlThen there are the two Windows package managers, and both have a separate update command, which is what you would expect from a tool that is expected to be kept current against a moving operating system.
scoop bucket add extras && scoop install shawlwinget install -e --id mtkennerly.shawlThe Scoop route adds a bucket first, because the package is not in the default bucket, and the Winget route uses the exact identifier of the package.
For a service wrapper, the portable binary is the option that matters most, because the wrapper is usually going to be shipped inside another artefact rather than installed on a developer's machine.
Creating the service, and the double dash that matters
The usage section shows two ways to create a service, and the difference between them is the point.
The first is the wrapper's own convenience command, which creates the service for you.
shawl add --name my-app -- C:/path/my-app.exeThe second is the standard Windows service control command, which gives you more control.
sc create my-app binPath= "C:/path/shawl.exe run --name my-app -- C:/path/my-app.exe"In both cases the double dash separates shawl's own options from the command it should run, and the readme calls that out explicitly. It is a small thing, but it is the kind of thing that goes wrong silently, because without it the wrapper would try to interpret your program's arguments as its own.
After the service exists you start or configure it as you would any other service, setting the start type to automatic and starting it.
The readme's comparison section later explains why the second form is the important one, and it is worth reading now, because it changes how you would use the tool. Existing solutions require running a special install command to prepare the service, which is inconvenient when you are installing a service through an installer package, because you would need to add a custom action. With shawl you can configure the service however you want, using the normal service install entry of an installer or the service control command, because shawl has no special setup of its own. The add command is an optional convenience, and nothing more.
Restart policy, and the three second stop
The behaviour of the wrapper under failure is the part that determines whether your service is reliable, and it is specified precisely.
Shawl inspects the state of the program in order to report the correct status to Windows. By default, when the program exits, shawl restarts it if the exit code is nonzero. You can change that with a flag that applies to all exit codes, or with a flag that applies to specific exit codes, and the readme notes that these four options are mutually exclusive, which is a helpful warning because they overlap in meaning and the wrong combination would be ambiguous.
When the service is asked to stop, shawl sends the program a control-C event, and then waits up to three thousand milliseconds, controlled by the stop timeout option, before forcibly killing the process if necessary.
The three second grace period is a reasonable default and it is the kind of number you would otherwise have to invent. A program that needs to close a database connection or finish writing a file gets three seconds to do it, and a program that ignores the interrupt gets killed rather than leaving the service in a stuck state.
And if shawl is not restarting the program, it reports the exit code to Windows as a service-specific error, unless the exit code is zero or a code you have configured to pass through. That last part is what makes the service manager show a meaningful failure rather than a generic one, and the option to treat specific exit codes as success is there for the case where a particular nonzero code means something benign to your program.
Logging, accounts and the working directory
Three operational details, and the middle one is the one that will bite you.
On logging, shawl creates a log file for each service, named after the service, in the same location as the shawl executable, and it contains both shawl's own messages and the output of the commands it runs. The readme's advice is practical: if anything goes wrong, read the log to find out. You can disable all logging, or just the command logs, and by default each log file is limited to two megabytes with up to two rotated copies retained.
On accounts, the readme warns twice. The default account for new services is the Local System account, which has a different path environment variable than your user account, so a service configured to run a command such as a package manager start needs that tool on the Local System path, or you change the account instead. And running a service as Local System is as dangerous as running a Unix service as root, because it greatly increases the risk of the machine being compromised if you expose a port to the public. The recommendation is a restricted account such as Network Service, which means granting that account read, write and execute permissions on the installation directory and setting the service object to it.
sc config my-app obj= "NT AUTHORITY\Network Service" password= ""If the service also needs to read and write files, that account needs permissions on the directory it accesses.
On the working directory, the default for Windows services is the system directory, and the readme suggests using the working directory option to set something else, which you will want to do.
How it differs from the established wrappers
The comparison section names the two tools this one is usually weighed against, and the difference is a single design decision.
The existing solutions require running a special install command to prepare the service. That can be inconvenient in cases like installing a service through an installer package, where you would need to run a custom action.
With shawl, you can configure the service however you want, with the normal service install entry of an installer or by running the service control command, because shawl has no special setup of its own. The add command is just an optional convenience.
That is the whole comparison, and it is a comparison about deployment rather than about features. The other tools give you a wrapper that installs and manages the service, which is convenient for a one-off install and awkward for a repeatable one. Shawl gives you a wrapper that is the entry point of the service and nothing more, which is the shape you want when the service definition is already part of your product's installer.
There is also a note on the recovery feature of Windows itself. If you want to use the operating system's own service recovery actions for the case where shawl has given up restarting the wrapped command, you need to turn on the option to enable actions for stops with errors in the service properties.
So the division of labour is clean. Shawl owns the process and the exit codes, Windows owns the machine level recovery, and the two are configured to talk to each other.
The build, and what the dependencies reveal
The manifest is short and tells you the shape of the implementation.
The package is written in the 2024 edition of Rust, it is MIT licensed, and the default run is the wrapper itself. The dependencies are a command line parser with derive support and help wrapping, a control-C handler, a library for Windows paths that work with the long path form, a flexible logger, the logging facade, the Windows API bindings, and the Windows service library.
The Windows bindings are configured with a specific feature list, and the feature names describe exactly what the wrapper touches: console, threading, job objects, foundation and security. Job objects in particular are how the wrapper can put the child process in a job so that it is cleaned up, and the security feature is for the account configuration.
The development dependencies are a regular expression engine, a small test framework, and a library for querying system process information. The test framework choice is worth noticing, because the wrapper's logic is full of edge cases around exit codes and restart rules, and a test harness is how you check that kind of logic without enumerating every combination by hand.
There is a build dependency for embedding Windows resources into the executable, which is what puts the version information and the icon in the file, and the release profile uses thin link time optimisation with symbols stripped, so the binary you drop next to your program is small.
The repository layout is a normal Rust project, with the source, the tests, the documentation, a change log, a contributing guide, a Python file for task automation, and a pre-commit configuration. The release history shows a project on a steady one major at a time, with 1.7.0 in January 2025, 1.8.0 in December 2025 and 1.9.0 in May 2026, and the last push on 2026-09-11 with the repository not archived.
Editorial conclusion
shawl suits anyone who has written a service that works from a terminal and then had to make it a Windows service, since the only requirement it places on the program is that it stops when interrupted. It is a poor fit if you want the wrapper to install and configure the service for you, because that is exactly the step it refuses to do in order to fit installer workflows, and a poor fit if you want rich process supervision, since the restart policy is a set of exit code rules rather than a supervisor. Before you deploy it, change the account the service runs as, because the default is the Local System account and the readme compares that to running a Unix service as root.
Frequently asked questions
What does shawl do?
It wraps an arbitrary program as a Windows service. The wrapper handles the Windows service API so that the wrapped program only has to respond to a control-C, and it manages the service lifecycle, the restart policy, the logging and the status reporting.
How do I create a service with shawl?
You can use the wrapper's own convenience command, which creates the service for you, or the standard Windows service control command with the wrapper as the binary path. The double dash separates shawl's options from the command it should run. The readme recommends the second form when the service is installed by an installer package.
How does shawl decide whether to restart the program?
By default it restarts the program when it exits with a nonzero code. You can apply the choice to all exit codes or to specific ones, and the readme notes that those four options are mutually exclusive. When the service is asked to stop, the program gets a control-C and then up to three seconds to exit before it is killed.
Is it safe to run a shawl-wrapped service as Local System?
The readme says no. It notes that the default account is Local System, that this is as dangerous as running a Unix service as root, and that it greatly increases the risk of the machine being compromised if you expose a port to the public. It recommends a restricted account such as Network Service, with the appropriate permissions granted.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mtkennerly-shawl)Was this useful?