Minimal F# Windows Service

I’ll soon publish a post about how to give your Windows Service lots of time to finish cleanly (by the way, needing all that time is something you should avoid unless you have an exceptionally good excuse to do so). Before doing so, I thought it a good idea to write this post with a minimal “Hello World”-ish service, which can be compared to an equally minimal service in terms of payload, but with all the ceremony needed to make Windows grant it a big chunk of time to finish.

What is a service, anyway?

To put it simply, in the Windows world, a service is a program that runs in the background, usually without interacting with the user and without requiring any user to be logged in. There’s really not much more to it than that, so let’s dive right into it and spend about a screenful of code on a sample service that logs file name changes.

Creating the project

While C# users get a service template in Visual Studio, there is none for F#. Considering how little need there is for one, I haven’t even bothered searching for a community written one, so we’ll simply create a bog standard .NET Core console application project.

One the project is created, we’ll need to throw our .NET Core cross platform compatibility out the window, by adding the Microsoft.Windows.Compatibility package:

Install-Package Microsoft.Windows.Compatibility

The code for the service is as follows:

open System
open System.IO
open System.ServiceProcess

let serviceName = "FSHelloService"

// Not the best place for a log, but it will suffice for this demo.
let logDir = ("c:/" + serviceName)

let log text =    
    File.AppendAllLines(
        Path.Combine(logDir, serviceName + ".log"), 
        [DateTime.Now.ToString() + "  " + text])


type FSHelloService() =
    inherit ServiceBase(ServiceName = serviceName)

    member val private watcher =
        new FileSystemWatcher(logDir, EnableRaisingEvents = true)

    override this.OnStart(args) =
        log "Running OnStart()"
        base.OnStart(args)
        // Do whatever initialization you need to do here, but be done
        // before the 30 second startup timeout expires.
        this.watcher.Renamed.Add(fun args -> 
            log (sprintf "File %s was renamed to %s" args.OldName args.Name))

    override this.OnStop() =
        this.watcher.Dispose()
        log "Running OnStop()"
        base.OnStop()


[<EntryPoint>]
let main args =
    Directory.CreateDirectory(logDir) |> ignore
    File.WriteAllText(Path.Combine(logDir, "Rename Me.txt"), "")
    log "-----------------------------"
    log "Instantiating service object."
    use service = new FSHelloService()
    ServiceBase.Run(service)
    0

The service is fired up in main function by instantiating a service object and running it with ServiceBase.Run(...). For a simple service, we only need to override two methods in our ServiceBase derived class, namely OnStart and OnStop, which unsurprisingly are called when our service is started and stopped by the service control manager.

To take the service for a spin, install in with this console command (as administrator):

sc create FSHelloService binPath=<path to your compiled .exe file>

Then, start the service, for example from the console (again, as administrator):

net start FSHelloService

or via the Services application:

Now, you can test it by going to the log directory, c:\FSHelloService (not a very disciplined place to put a log file, but this isn’t production code) and renaming the Rename Me.txt file. Then, have a look in FSHelloService.log and you’ll see the log output from the service.

The service can be stopped with:

net stop FSHelloService

To remove it, make sure that the Services application and the task manager are closed, and then issue:

sc delete FSHelloService

And that, ladies and gentlemen, is how we do that!

  1. No comments yet.

  1. No trackbacks yet.