KEMBAR78
Introduce the dedicated `windows-services` crate by kennykerr · Pull Request #3599 · microsoft/windows-rs · GitHub
Skip to content

Conversation

@kennykerr
Copy link
Collaborator

@kennykerr kennykerr commented May 13, 2025

This update introduces the windows-services crate as a simple and safe way to implement a Windows service in Rust. I previously added a Windows service sample using the windows-sys crate directly against the service control manager APIs (#3590), but using that approach is quite error-prone and involves a lot of unsafe code. The windows-services crate provides a super-super Service type that lets you write a robust Windows service with minimal boilerplate and no unsafe code. Here's a very simple example:

fn main() {
    windows_services::Service::new()
        .can_pause()
        .can_stop()
        .run(|command| {
            // Respond to service commands...
        })
}

After using the service builder to indicate what service commands to support, the closure will be called with various commands as they are sent by the service control manager. That's all there is to it!

This implementation takes an opinionated approach to Windows service development in order to keep things as simple and safe as possible. For example, the service will always run in its own process. There are other ways to implement a service and even different kinds of services, such as a driver service, but this crate is focused on the most common case: a non-driver service that runs in its own process.

Here's a slightly more interesting example using the windows-threading crate introduced in #3595 to manage a dedicated service thread:

use windows_services::*;
use windows_threading::*;

fn main() {
    let pool = Pool::new();
    pool.set_thread_limits(1, 1);

    Service::new().can_pause().can_stop().run(|command| {
        log(&format!("Command: {command:?}\n"));

        match command {
            Command::Start | Command::Resume => pool.submit(service_thread),
            Command::Pause | Command::Stop => pool.join(),
        }
    })
}

fn service_thread() {
    for i in 0..10 {
        log(&format!("Thread:{}... iteration:{i}\n", thread_id()));

        // Replace with whatever work the service needs to do.
        sleep(1000);

        // Services can use the `state` function to query the current service state.
        if matches!(state(), State::StopPending | State::PausePending) {
            return;
        }
    }

    // Services can use the `set_state` function to update the service state.
    set_state(State::Stopped);
}

// Simple log function can be used to observe service behavior.
fn log(message: &str) {
    // log stuff here...
}

The service will print some useful testing information to the console if you run it directly:

D:\git\windows-rs>cargo run -p sample_service_simple
   Compiling windows-link v0.1.1 (D:\git\windows-rs\crates\libs\link)
   Compiling windows-services v0.0.0 (D:\git\windows-rs\crates\libs\services)
   Compiling sample_service_simple v0.0.0 (D:\git\windows-rs\crates\samples\services\simple)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.03s
     Running `target\debug\sample_service_simple.exe`
Use service control manager to start service.
    
Install:
    > sc create ServiceName binPath= "D:\git\windows-rs\target\debug\sample_service_simple.exe"

Start:
    > sc start ServiceName

Query status:
    > sc query ServiceName

Stop:
    > sc stop ServiceName

Delete (uninstall):
    > sc delete ServiceName

@kennykerr kennykerr merged commit 5638590 into master May 14, 2025
29 checks passed
@kennykerr kennykerr deleted the windows-services branch May 14, 2025 13:27
@kennykerr kennykerr mentioned this pull request May 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant