powershellserviceservernssm

Converting .ps1 file to a Windows Service


I'm trying to convert a .ps1 file to run as a windows service. This needs to run as a service as it's requirements for Business Continuity (scheduled task is not an option). i've always used NSSM to wrap the .ps1 as it will then run via NSSM as an exe.

This works for different scripts in Windows Server 2012, but this script is slightly different and i'm required to get this service to work on Windows Server 2016. The script itself, connects to a large amount of other servers (in total i'll have 3 services - Windows Service / Windows Process / Linux Process) which all work when just running within PowerShell.

Below is an example of the start of the script so you get an idea how it works (may not be relevant);

 while ($test = 1)
{

         [string]$query
          [string]$dbServer = "DBSERVER"   # DB Server (either IP or hostname)
          [string]$dbName   = "DBNAME" # Name of the database
          [string]$dbUser   = "CONNECTIONUSER"    # User we'll use to connect to the database/server
          [string]$dbPass   = "CONNECTIONPASSWORD"     # Password for the $dbUser


    $conn = New-Object System.Data.Odbc.OdbcConnection
    $conn.ConnectionString = "Driver={PostgreSQL Unicode(x64)};Server=$dbServer;Port=5432;Database=$dbName;Uid=$dbUser;Pwd=$dbPass;"
    $conn.open()
    $cmd = New-object System.Data.Odbc.OdbcCommand("select * from DBNAME.TABLENAME where typeofcheck = 'Windows Service' and active = 'Yes'",$conn)
    $ds = New-Object system.Data.DataSet
    (New-Object system.Data.odbc.odbcDataAdapter($cmd)).fill($ds) | out-null
    $conn.close()

$results = $ds.Tables[0]
$Output = @()

        foreach ($result in $results)
        {

            $Hostone = $Result.hostone
            $Hosttwo = $Result.hosttwo
            $Hostthree = $Result.hostthree
            $Hostfour = $Result.hostfour

        Write-Output "checking DB ID $($result.id)"

            #Host One Check
            if (!$result.hostone)
            {
            $hostonestatus = 17
            $result.hostone = ""
            }
            else
            {
            try
               {
                    if(Test-Connection -ComputerName $result.hostone -quiet -count 1)
                    {
                    $hostoneres = GET-SERVICE -COMPUTERNAME $result.hostone -NAME $result.ServiceName -ErrorAction Stop
                    $hostonestatus = $hostoneres.status.value__
                    $Result.HostOneCheckTime = "Last checked from $env:COMPUTERNAME at $(Get-date)"
                    }
                    else
                    {
                    $hostonestatus = 0
                    $result.hostonestatus = "Failed"
                    $Result.HostOneCheckTime = "Last checked from $env:COMPUTERNAME at $(Get-date)"
                    }
                }
                catch
                {
                    $hostonestatus = 0
                    $result.hostonestatus = "Failed"
                    $Result.HostOneCheckTime = "Last checked from $env:COMPUTERNAME at $(Get-date) Errors Found"
                }
                    if ($hostonestatus -eq 4)
                        {
                            $result.hostonestatus = "Running"
                        }
                    if ($hostonestatus -eq 1)
                        {
                            $result.hostonestatus = "Stopped"
                        }
                    elseif ($hostonestatus -eq 0)
                        {
                            $result.hostonestatus = "Failed"
                        }

                    }

As mentioned, the exact script running standalone works seamlessly.

Whats the best way to run this as a service or are there any known issues with NSSM when using it with Windows Server 2016?

I've also found the below which may be pointing in the right direction as i've intermittently got these in the logs;

DCOM event ID 10016 is logged in Windows


Solution

  • Windows sysadmin here.

    Quiet a few different ways to accomplish this from a purely service-orientated perspective.

    --- 1 ---

    If you are using Server 2016, I believe that the Powershell command 'New-Service' may be one of the cleanest ways. Have a look at the following for syntax and if it suits your use case --

    https://support.microsoft.com/en-au/help/137890/how-to-create-a-user-defined-service

    This CMDlet takes a credential parameter, so depending on your use case may be good to access resources on other foreign machines.

    --- 2 ---

    Another way is to use the old trusty in-built SC.exe utility in windows.

    SC CREATE <servicename> Displayname= "<servicename>" binpath= "srvstart.exe <servicename> -c <path to srvstart config file>" start= <starttype>
    

    An example --

    SC CREATE Plex Displayname= "Plex" binpath= "srvstart.exe Plex -c C:PlexService.ini" start= auto
    

    As far as I can tell, this will create a service that will execute under the Local System context. For more information, have a look at the following:

    https://support.microsoft.com/en-au/help/251192/how-to-create-a-windows-service-by-using-sc-exe
    
    https://www.howtogeek.com/50786/using-srvstart-to-run-any-application-as-a-windows-service/
    

    --- 3 ---

    You may want to consider manually injecting some registry keys to create your own service (which is essentially what SC.exe does under the hood).

    Although I'm unfortunately in no position at the moment to provide boiler-plate code, I'd encourage that you have a look at the following resource:

    https://www.osronline.com/article.cfm%5Eid=170.htm 
    

    NOTE - you will need to provide all required sub-keys for it to work.

    As with any registry changes, please make a backup of your registry and perform edits at your own risk prior to making any changes. I can only recommend to try this on a spare/test VM if possible prior to implementing to prod.