Writing a Windows Service in Go

by Hadley Bradley

Introduction

To compliment the lab receipting application I wrote recently, 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 written using Go and this article outlines some of the lessons learned developing such an application. Go is an excellent language to write Windows Services in as it’s a strongly typed language and produces very fast executables. Go can cross compile your code to multiple architectures which means the same service could easily run on either a Windows Server or 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

GOARCH=amd64 GOOS=windows go build -ldflags="-w -s" -o lamp90rs.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.

nssm install LAMP90RS C:\lamp90rs\lamp90rs.exe
nssm set LAMP90RS AppDirectory C:\lamp90rs
nssm set LAMP90RS DisplayName "LAMP90RS"
nssm set LAMP90RS Description "LAMP90 Results Service"
nssm set LAMP90RS Start SERVICE_AUTO_START
nssm 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 easily 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 Goroutine to perform the actual business logic. In the example below we call the function processLabResults as a Goroutine defined by the go keyword in front of the function name. Goroutines are functions or methods that run concurrently with other functions or methods. Goroutines can be thought of as light weight threads. The cost of creating a Goroutine is tiny when compared to a thread. Hence it’s common for Go applications to have thousands of Goroutines running concurrently.

func main() {
    app := &App{}
    app.startLogging()
    app.monitorOperatingSystemSignals()

    go app.processLabResults()

    select {} // block, so the program stays resident
}

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.

func (app *App) processLabResults() {
    // start an infinite loop
    for {

        // business logic goes here for
        // processing the queue of lab results
        // we then pause between each iteration

        time.Sleep(1 * time.Minute)
    }
}

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 in developing a Windows Service using Go; then please get in touch I’d be happy to help.