Recently I received an email forwarded to me from a fan of the F# language, asking about the steps required to build a Windows service (the Windows equivalent to a background daemon from Unix) in F#. It’s not hard, but getting the F# bits in the right place can be tricky—the key being, the Installer (that will be invoked when installutil.exe is asked to install your service) has to have the right custom attribute in place, and the service has to have all the bits lined up perfectly. So, without further ado….
- Create an F# Application project
- Add references to “System.ServiceProcess” and “System.Configuration.Install”
- Create the service itself:
type WindowsService() as this =
this.ServiceName <- "My Windows Service"
this.EventLog.Log <- "Application"
override this.OnStart(args:string) =
override this.OnStop() =
static member Main() =
- Create the service installer class (inside the same namespace, for easy reference):
type MyInstaller() as this =
let spi = new ServiceProcessInstaller()
let si = new ServiceInstaller()
spi.Account <- ServiceAccount.LocalSystem
spi.Username <- null
spi.Password <- null
si.DisplayName <- "My New F# Windows Service"
si.StartType <- ServiceStartMode.Automatic
si.ServiceName <- "My Windows Service"
this.Installers.Add(spi) |> ignore
this.Installers.Add(si) |> ignore
- Take the resulting executable, install the service by using the “installutil.exe” utility from the .NET SDK: “installutil /I FSService.exe”
- Verify that the service has been installed in the Services Control Panel.
MSDN documentation describes Windows services and the various overridable methods from ServiceBase, as well as how the ServiceInstaller configuration options describe the service itself (account to use, start mode, etc).
Update: Whoops! I forgot something else—the service above will install, but it won’t start! The symptom is that you’ll get a “timeout” error immediately after trying to start the service, and the reason for that is because F# (unlike C#) doesn’t recognize the Main() member as an assembly entry point. (I knew that, I swear.)
The fix is to move Main() outside of the WindowsService type and into a module (which EntryPoint requires), like so:
module Entry =
let Main(args) =
Sorry about that. Bear in mind, too, that the service may have additional properties (CanShutdown, CanPauseAndContinue, etc) that you may also want to set, in order to receive those events (OnShutdown, OnPause, etc) in the service.
From here, though, things should be smooth sailing.