Need Help with Package as Service

Is there a way without using Start-Job to execute a while loop function as part of the OnStart() function and still have it return the service to Started State?

The Problem I am having is that because of the while loop the service is in a perpetual state of “Starting” even though it is running. Is there any way to accomodate this without using Jobs? I ask because when you use a job it creates a new powershell.exe process that is more difficult to monitor. I want it so that if the EXE stops for any reason to include user termination that the service will stop as well. and in the job scenario, if the powershell.exe instance is closed the service does not stop.

Here is the script I am trying to make a service. There are other functions it calls that are not included for security reasons, and it seems to have no problem calling them when it gets stuck in “starting” state

Just hangs on the while($true) loop which prevents it from reaching “started/running” state

  # Script Start
  $servicepath = Split-Path (Get-WmiObject win32_service | Where-Object{$_.Name -eq 'Pega-PagerDuty'} | Select-Object @{Name="Path"; Expression={$_.PathName.split('"')[1]}}).Path
  $exePath = Convert-Path "$servicepath"
  Set-Location $exePath
  Write-Host "Started: $(Get-Date)" -ForegroundColor Green
  Out-File -InputObject "$(Get-Date): Service Started" -FilePath "$exePath\Log.txt" -Append
  $enablePagerDuty = $true
  $script:config = Get-ConfigFile -file "$exePath\Config.ini"
  $config | Out-File -FilePath "$exePath\testconfig.txt"
  while($true) {
    #Get Pega OAUTH Token
    if(!($tokenDetails)) {
      #Get Initial Token
      $script:tokenDetails = Get-PegaAuthToken
      $script:tokenExpire = (Get-Date).AddSeconds(($tokenDetails.expires_in - 300))
      $script:refreshToken = $tokenDetails.refresh_token
    }
    if ((Get-Date) -ge $tokenExpire) {
    #Refresh Token
      $script:tokenDetails = Update-PegaAuthToken($refreshToken)
      $script:tokenExpire = (Get-Date).AddSeconds($tokenDetails.expires_in - 300)
      $script:refreshToken = $tokenDetails.refresh_token
    }
    Out-File -InputObject $tokenDetails -FilePath "$exePath\testtokendetails.txt"
    if(((get-date).Second % $script:config.CheckInterval) -eq 0) {
      #Fetch and Post Data
      $pegaData = (Get-PegaNotifications($tokenDetails.access_token) | Sort-Object GeneratedDateTime)
      $pegaData | Out-File -FilePath "$exePath\testpegaData.txt"
      if($pegaData) {
        Write-Host "$(Get-Date): Processing $($pegaData.count) Alerts" -ForegroundColor Yellow
        Write-EventLog -LogName Application -Source "Pega-PagerDuty" -EntryType Information -EventId 1 -Message "Processing $($pegaData.count) Alerts"
        Out-File -InputObject "$(Get-Date): Processing $($pegaData.count) Alerts" -FilePath "$exePath\Log.txt" -Append
        Foreach($item in $pegaData) {
          $formattedItem = Format-DataforPagerDuty($item)
          if($enablePagerDuty) {
            $pagerDutyResponse = Add-DataToPagerDuty($formattedItem)
            $pagerDutyResponse | Tee-Object -FilePath "$exePath\Log.txt" -Append
          }
        }
        Write-Host "$(Get-Date): Alert Processing Completed" -ForegroundColor Green
        Out-File -InputObject "$(Get-Date): Alert Processing Completed" -FilePath "$exePath\Log.txt" -Append
        Write-EventLog -LogName Application -Source "Pega-PagerDuty" -EntryType Information -EventId 1 -Message "Alert Processing Completed"
        start-sleep 1
      }else{
        #Current Second not 00 or 30
        start-sleep 1
      }
    }
  }
# Script End

Did you manage to solve this? I did try using Register-ObjectEvent and get notified if the job dies, but the call to $Service.Stop() in the event action hangs and the service ends up in the “Stopping” state forever…

function OnStart() {
    # $Service.Stop()
    $serviceJob = Start-Job -ScriptBlock {Start-Sleep -Miliseconds 1000; exit 1}
    $jobEvent = Register-ObjectEvent -InputObject $serviceJob -EventName "StateChanged" -Action {
        if ($sender.State -ne "Running") {
            $Service.Stop()
        }
        $jobEvent | Unregister-Event
    }
}

function OnStop() {
    $jobEvent | Unregister-Event
    if ($serviceJob.State -eq "Running") {
        $serviceJob.Stop()
    }
}

$CanStop = $true

No, I never figured out how to make it work with Powershell Pro Tools. But I have a feeling it’s because of the While Loop, the OnStart never completes.

I ended up having to use another developer’s Module to accomplish this task. Would still love to have it work with Powershell Pro Tools though.

I have created several Windows Services with PowerShellProTools without problems, some of them quite complex. Here some rules:

(1) OnStart() function must complete quite rapidly. It cannot have loops, sleeps or waits.
(2) Extra processing must be done by executing a separate thread as in the second example here
(3) The execution logic first executes the script (not the OnStart function) so global variables can be set at this point. Once that completes, the Service Manager will execute the OnStart() function, however it is bad practice to modify the global variable values. I tried that with unpredictable results so I decided to not rely on that and therefore I prefer to pass parameters to the jobs or event handlers.

In the logic below, during OnStart() the Service Name is obtained from the process id ($PID), and then passed as parameter to the job event handler. The background job is started, and then the event handler is registered. Once the job stops running, the event handler is executed which will then start another job to stop the service gracefully, using the service name passed in the MessageData parameter. Then it will remove the job and also remove itself. During the OnStop() the background job is checked if still running, and then forced to stop and removed. This way, the service will not get stuck in either Starting or Stopping. Believe, this is the only way I could make it work. The script below is just a modification of the code above where I included the framework I used for all services I have created.

Check the updated response at
Need Help with Package as Service - PowerShell Tools - Ironman Software Forums (universaldashboard.io)

Check the updated post at

Need Help with Package as Service - PowerShell Tools - Ironman Software Forums (universaldashboard.io)

It should work that way. However unfortunately the response was too late for the project I was needing the solution for. So I found another developer’s Free Module that was able to do it without creating additional powershell processes. I will try out the solution when I have the need to make another service. Thanks for the examples.

Please share the Module name and location

Sorlov.Powershell
https://www.powershellgallery.com/packages/Sorlov.PowerShell/5.0.0.1

Specifically the New-SelfHostedPS Cmdlet

Thanks @banyula! I’ve been able to make it work successfully. The key was not to rely on the global variables.