Running A Powershell Script As A Service
Posted on February 24, 2020
- and tagged as
- powershell
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
We can define the additional properties on the Details tab.
Using either of the above methods should result in a new service being present.
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.
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>