Running A Powershell Script As A Service

A few people have asked if it was possible to run PSKindleWatch as a Windows service. This is a guide on how to accomplish that using NSSM. To be clear, this method can be used to run any PowerShell script as a Windows service, but we’ll use PSKindleWatch an an example.

The PowerShell Script

Our script needs to have some kind of controlled infinite loop, we first define an email alert ScriptBlock (this is specific to PSKindleWatch) and then run Update-KindleBookPrices with a 6 hour sleep between cycles. The code doesn’t particularly matter here, you would replace this with whatever you wanted to run as a service, as long as it had a never-ending loop.

[ScriptBlock]$SendEmail = { 
    Param(
        $Book,
        $Msg
    )

    $Body = "<h1><a href=$($Book.URL)>Get it!</a></h1>"

    Send-MailMessage `
        -From "alerts@mydomain.com" `
        -To "me@mydomain.com" `
        -Subject $Msg `
        -Body $Body `
        -BodyAsHtml `
        -SmtpServer "mail.mydomain.com"
}

while ($true) {
    Update-KindleBookPrices -DataFile C:\Users\md\Documents\KindleBooks.json -AlertScriptBlock $SendEmail
    Start-Sleep -Seconds (60*60*6)
}

For the example we’ll save this to C:\Code\PSKindleWatchSvc.ps1

A Note on Event Listeners

If your script contains event listeners such as the FileSystemWatcher class, you still need to have an infinite loop in your script. A short while ($True) {Start-Sleep -Seconds 1} at the bottom will suffice. Don’t make the mistake of putting the Register-ObjectEvent calls inside the loop.

You will want to keep the sleep time low as any triggered events are processed ‘in-between’ loop cycles.

NSSM

Once NSSM is downloaded and extracted we’re provided with both 64-bit and 32-bit binaries. There is nothing to install, however, you will need to keep the NSSM executable as the created service will reference it, so place it somewhere sensible (i.e., don’t run it from your Downloads folder).

Creating a service using the command line

To make this easier to read we can define a few variables first, and then create the service.

# The path to PowerShell
$Binary = (Get-Command Powershell).Source

# The necessary arguments, including the path to our script
$Arguments = '-ExecutionPolicy Bypass -NoProfile -File "C:\Code\PSKindleWatchSvc.ps1"'

# Creating the service
.\nssm.exe install PSKindleWatch $Binary $Arguments

What this is doing is instructing NSSM to create a service called PSKindleWatch that runs PowerShell with the provided arguments.

Creating a service using the GUI

Running nssm.exe install will bring up the GUI, this gives us a few more options such as defining the display name, a description, and the startup type (default is Auto)

We need to define the same properties

  • The path to the PowerShell executable, and
  • The arguments, including the path to our script

NSSM Application Tab

We can define the additional properties on the Details tab.

Using either of the above methods should result in a new service being present.

NSSM Application Tab

There is however one thing left to take care of.

Permissions

You may have noticed that the default account for an NSSM created service is Local System. The System account has full privileges on a host, for our example we don’t need this level of access to interact with a few text files and make HTTP requests, so let’s change that. Instead of the Local System account we’ll use Local Service, this is a limited default account that is designed for running low privilege services. Depending on your particular needs, you should limit the service privileges to what is required for it to run.

In the service properties navigate to the Log On field and change the service to run as Local Service. This account has no password, so anything can be entered in to the password field.

Lastly, being an unprivileged account, we need to update permissions on our files to allow Local Service to access them.

We need to grant Modify rights to our Data File.

NSSM Application Tab

And we only need to grant Read rights to the service PowerShell script.

That’s it, we can restart our service and it will run under the lower privilege account.

Deleting the service

This can be accomplished by running sc.exe from an elevated prompt.

sc delete <ServiceName>