Writing a Windows Service in Go

Written by Paul Bradley

Writing and installing a Windows Service using the Golang programming language

Introduction

To compliment my lab receipting application, I also had to build a Windows Service that would monitor the lab results and send them back to the central architecture.

The Windows service was developed using Go and this article outlines some of the lessons learned developing such a service. Go is an excellent language to write Windows Services in as it’s a strongly typed language and produces fast executables. Go can cross compile your code to multiple architectures which means the same service could run on either a Windows Server or a Linux Server.

Compiling for Windows

Use the following pattern to instruct the go build command to compile your Go code to the smallest possible Windows .EXE

1GOARCH=amd64 GOOS=windows go build -ldflags="-w -s" -o lamp90.exe *.go

Installing as a service

An easy way to install the complied Windows binary as a service is to use NSSM; the Non-Sucking Service Manager. NSSM monitors your running service and will restart it if it dies. With NSSM you know that if a service says it’s running, it really is. Shown below is the sequence of NSSM commands I used to install the results service. It also demonstrates how to set various parameters like the applications’ home directory, display name and description. We also use the Start sub command to auto the service. This ensures that the service is automatically started after the Windows machine is rebooted.

1nssm install LAMP90RS C:\lamp90rs\lamp90.exe
2nssm set LAMP90RS AppDirectory C:\lamp90
3nssm set LAMP90RS DisplayName "LAMP90"
4nssm set LAMP90RS Description "LAMP90 Results Service"
5nssm set LAMP90RS Start SERVICE_AUTO_START
6nssm start LAMP90RS

Creating an MSI

While NSSM is a great tool for installing a Windows service internally to your organisation, it’s not ideal if you want to deploy a service to customers. When selling/deploying a commercial service you’d want to create a professional MSI installer. The installer package could then include company information include a license agreement.

The go-msi package helps to generate MSI package for a Go project. By using the WiX Toolset you can install your application as a windows service.

Go Routine

The key to running to Go program as a Windows Service is to ensure the program stays resident in memory. To do this we use the select command to block the programs execution so that it doesn’t terminate. The select command should be the last statement within your main function.

We then use a Go routine to perform the actual business logic. In the example below we call the function processLabResults as a Go routine defined by the go keyword in front of the function name. Go routines are functions or methods that run concurrently with other functions or methods. Go routines can be thought of as light weight threads. The cost of creating a Go routine is tiny when compared to a thread. Hence it’s common for Go applications to have thousands of Go routines running concurrently.

1func main() {
2    app := &App{}
3    app.startLogging()
4    app.monitorOperatingSystemSignals()
5
6    go app.processLabResults()
7
8    select {} // block, so the program stays resident
9}

Within our Goroutine function processLabResults() we start by defining an infinite loop so that the thread continues to run while the service is active. After each loop we Sleep() for a defined amount of time. In this example one minute. Ideally this would be a configuration option that could be changed externally to the program.

 1func (app *App) processLabResults() {
 2    // start an infinite loop
 3    for {
 4
 5        // business logic goes here for
 6        // processing the queue of lab results
 7        // we then pause between each iteration
 8
 9        time.Sleep(1 * time.Minute)
10    }
11}

Logging

There are different approaches you could use for logging application events. You could use the eventlog package to write your logs directly to a Windows event log.

Alternatively, you could use one of the key value datastores listed on Awesome Go. You could embed a local web server within another Goroutine to serve the UI to view and interrogate the logs for your application.

Notifications

If your Windows service needs to alert the user of a particular issue you could use the toast package to inject your notifications into the Windows notification panel.

Another option would be to use a the syscall package to invoke a Windows MessageBox() using the MessageBoxW function which comes as part of the Windows API.

Need Help or Advice

If you need any help or advice then please send me a direct message on Twitter, I’d be happy to help.