Creating a Service

This forum can be browsed by the general public. Posting is limited to current SAPIEN license holders with active maintenance and does not offer a response time guarantee.
Forum rules
DO NOT POST LICENSE NUMBERS, ACTIVATION KEYS OR ANY OTHER LICENSING INFORMATION IN THIS FORUM.
Only the original author and our tech personnel can reply to a topic that is created in this forum. If you find a topic that relates to an issue you are having, please create a new topic and reference the other in your post.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
User avatar
Nillth
Posts: 34
Last visit: Wed Jun 09, 2021 3:32 am
Has voted: 2 times

Creating a Service

Post by Nillth »

Product, version and build: PowerShell Studio 2018 v5.5.150 & PrimalScript Version 7.4.113
32 or 64 bit version of product: 64
Operating system: Windows 10 - Tech Preview 1803 (17134.1)
32 or 64 bit OS: 64

There seems to be a major difference in how a Service is compiled in PowerShell Studio (PSS) vs PrimalScript.

When a service is built in PSS it seems to just run as a back ground script, and when StopService() is called it just terminates the PowerShell thread...
Where as in PrimalScript the StopService() call kicks off the Stop-MyService Function.
Is there anyway to capture the StopSerivce and PauseService commands in PSS?
I would like to get these in order to get it to cleanly shutdown the service rather than just terminating the thread?

Ideally, I would also like to be able to install the created service and have it linked to dependencies.
I.e. dont run unless the dependent service is running...
but am unable to find a way to do this...
and finally, if there was some way for the installer to selectively prompt for a service account during the install.
i.e. would you like to use a service account enter details.

The following is an example of what i am building..
It uses the .Net File watcher, to monitor for File changes specified in the config file.

When one is identified the Invoke-MyService loop will pick up the Event Item and process it.. e.g. when a file is changed write to the event log.
  1. <#        
  2.     .NOTES
  3.     ========================================================================
  4.          Windows PowerShell Source File
  5.          Created with SAPIEN Technologies PrimalScript 2018
  6.          NAME: Marc Collins (Nillth)
  7.          DATE  : 23/04/2018
  8.     ==========================================================================
  9. #>
  10.  
  11. # Warning: Do not rename Start-MyService, Invoke-MyService and Stop-MyService functions
  12.  
  13. function Get-ScriptDirectory
  14. {
  15.     if ($null -ne $hostinvocation)
  16.     {
  17.         Split-Path $hostinvocation.MyCommand.path
  18.     }
  19.     else
  20.     {
  21.         Split-Path $script:MyInvocation.MyCommand.Path
  22.     }
  23. }
  24.  
  25.  
  26. function Start-MyService
  27. {
  28.     $Global:StopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
  29.     $Global:StopWatch.Start()
  30.     $global:bRunService = $true
  31.     $global:bServiceRunning = $false
  32.    
  33.    
  34.     #region Register Events to Monitor
  35.    
  36.     ###########################################################################
  37.     #region ServiceMonitor
  38.     $RunningService = Get-WmiObject Win32_Service -Filter "ProcessId='$($PID)'"
  39.     $query = @"
  40.        SELECT *
  41.        FROM __InstanceModificationEvent
  42.        WITHIN 2
  43.        WHERE TargetInstance ISA 'Win32_Service'
  44.        AND TargetInstance.Name = '$($RunningService.Name)'
  45. "@
  46.     Unregister-Event ServiceChangeWIM -ErrorAction SilentlyContinue
  47.     Register-WmiEvent -Query $query ServiceChangeWIM
  48.     #endregion ServiceMonitor  
  49.    
  50.     ###########################################################################
  51.     #region FileSystem Watcher/s
  52.    
  53.     $ScriptDirectory = Get-ScriptDirectory
  54.     $TriggerConfig = "$($ScriptDirectory)\TriggerConfig.xml"
  55.    
  56.     #Creates a Trigger config and Template file on first run if they do not exist
  57.     if (!(Test-Path $TriggerConfig))
  58.     {
  59.         $TriggerExample = "$($ScriptDirectory)TriggerTemplate.txt"
  60.         $TriggerTemplate = @"
  61. <?xml version="1.0"?>
  62. <Triggers>
  63.     <Trigger Name="MyApp">
  64.         <TriggerFile>$($TriggerExample)</TriggerFile>
  65.     </Trigger>
  66. </Triggers>
  67. "@
  68.         #Create a empty template triggerfile
  69.         if (!(Test-Path $TriggerExample))
  70.         {
  71.             "" | Out-File -FilePath $TriggerExample -Encoding utf8 -Force
  72.         }
  73.         $TriggerTemplate | Out-File -FilePath $TriggerConfig -Encoding utf8 -Force
  74.     }
  75.    
  76.     $XMLTriggerConfig = New-Object XML
  77.     $XMLTriggerConfig.Load($TriggerConfig)
  78.    
  79.     foreach ($Trigger in $XMLTriggerConfig.Triggers.Trigger)
  80.     {
  81.        
  82.         $TriggerFolder = [system.io.path]::GetDirectoryName($Trigger.TriggerFile)
  83.         $TriggerFile = [system.io.path]::GetFileName($Trigger.TriggerFile)
  84.         if ($null -ne $Trigger.Name)
  85.         {
  86.             $TriggerName = "$($Trigger.Name)"
  87.         }
  88.         else
  89.         {
  90.             $TriggerName = "$($TriggerFile)"
  91.         }
  92.         if (Test-Path -LiteralPath $TriggerFolder)
  93.         {
  94.             #Create the File System Watcher
  95.             $fsw = New-Object IO.FileSystemWatcher $TriggerFolder, $TriggerFile -Property @{ IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite' }
  96.             $SourceIdentifier = "FileTrigger - $($TriggerFile)"
  97.         }
  98.        
  99.         Register-ObjectEvent $fsw Changed -SourceIdentifier $SourceIdentifier -Action {New-Event -SourceIdentifier TriggerTouch -MessageData $Event} | Out-Null
  100.  
  101.     }
  102.    
  103.     #endregion FileSystem Watcher
  104.     #endregion Register Events to Monitor
  105.    
  106. }
  107.  
  108. function Invoke-MyService
  109. {
  110.     $global:bServiceRunning = $true
  111.     while ($global:bRunService)
  112.     {
  113.         $EventReceived = Wait-Event -SourceIdentifier TriggerTouch -Timeout 5
  114.        
  115.         #Check for shutdown/pause etc activity
  116.         $EventAction = Get-Event -SourceIdentifier Action
  117.         if ($null -ne $EventAction)
  118.         {
  119.             if ($EventReceived.MessageData -eq "ShutdownService")
  120.             {
  121.                 #Termination code  
  122.             }
  123.            
  124.         }
  125.         else
  126.         {
  127.             #Process Something.
  128.             #This should contain the Information returned from the FileWatcher
  129.             #$EventReceived.MessageData
  130.             #e.g.
  131.             Write-Host "File $($EventReceived.MessageData.SourceArgs[1].ChangeType) - $($EventReceived.MessageData.SourceArgs[1].FullPath)"
  132.            
  133.             if ($null -ne $EventReceived)
  134.             {
  135.                 Remove-Event -EventIdentifier $EventReceived.EventIdentifier -ErrorAction SilentlyContinue
  136.             }
  137.         }
  138.     }
  139.     $global:bServiceRunning = $false
  140. }
  141.  
  142. function Stop-MyService
  143. {
  144.     $global:bRunService = $false # Signal main loop to exit
  145.     while ($global:bServiceRunning)
  146.     {
  147.         $EventReceived = Wait-Event ServiceChangeWIM -Timeout 1
  148.         if ($null -ne $EventReceived)
  149.         {
  150.             Remove-Event -EventIdentifier $EventReceived.EventIdentifier -ErrorAction SilentlyContinue
  151.             if ($EventReceived.SourceEventArgs.NewEvent.TargetInstance.State -eq "Stop Pending")
  152.             {
  153.                 New-Event -SourceIdentifier Action -MessageData "ShutdownService"
  154.                 $global:bServiceRunning = $false
  155.             }
  156.         }
  157.     }
  158.     $EventSubscriber = Get-EventSubscriber -ErrorAction SilentlyContinue
  159.     if ($null -ne $EventSubscriber)
  160.     {
  161.         $EventSubscriber.SourceIdentifier | %{ Unregister-Event $_ }
  162.     }
  163. }
  164.  
  165.  

User avatar
davidc
Posts: 5913
Last visit: Mon Jul 08, 2019 8:55 am
Been upvoted: 1 time

Re: Creating a Service

Post by davidc »

This functionality will be available in the next service release of PowerShell Studio. It should be released very soon.
David
SAPIEN Technologies, Inc.

User avatar
Alexander Riedel
Posts: 7718
Last visit: Fri Jul 30, 2021 12:49 pm
Answers: 4
Been upvoted: 10 times

Re: Creating a Service

Post by Alexander Riedel »

Let me elaborate a little on that. PowerShell Studio will get the same service template, packager engine and service installer capabilities as PrimalScript has now with its next service build.
Handling pausing a service along with Service specific prerequisites and settings for installers will follow at a later date. No timeline has been established for that at this point.
Alexander Riedel
SAPIEN Technologies, Inc.

User avatar
Nillth
Posts: 34
Last visit: Wed Jun 09, 2021 3:32 am
Has voted: 2 times

Re: Creating a Service

Post by Nillth »

Is there anyway for the PowerShell code to intercept the Start/Stop/Pause commands implemented by the service .exe wrapper?
It seems to me that when the Code is Compiled in PrimalScript the service .exe wrapper will interpret .StopService() and call the Stop-MyService Function.

Only real issue for me at the moment, is that it is not straight forward to open and compile a multi file PowerShell Studio project in PrimalScript. So I find myself writing the code in a single ps1 file in SPSS and building in Primal....

When the project is built in SPSS can you advise as to what the StopService Command actually does?
It appears to set the service to "Stop Pending" then if I use the EventSubscriber to capture the state change and set the Global variable in order break out of the While Loop the script proceeds to the end and completes cleanly

However if I do not use the EventSubscriber to break the loop it seems to force terminate the PowerShell thread ...


  1. $Global:ServiceRunning = $true
  2. $RunningService = Get-WmiObject Win32_Service -Filter "ProcessId='$($PID)'"
  3. $query = @"
  4.        SELECT *
  5.        FROM __InstanceModificationEvent
  6.        WITHIN 2
  7.        WHERE TargetInstance ISA 'Win32_Service'
  8.        AND TargetInstance.Name = '$($RunningService.Name)'
  9. "@
  10. Unregister-Event ServiceChangeWIM -ErrorAction SilentlyContinue
  11. Register-WmiEvent -Query $query ServiceChangeWIM -Action {
  12.     "StateChange" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  13.     if ($event.SourceEventArgs.NewEvent.TargetInstance.State -ne "Running")
  14.     {
  15.         $Global:ServiceRunning = $false
  16.     }
  17.     New-Event -SourceIdentifier StateChange -MessageData $Event
  18. } | Out-Null
  19.  
  20.  
  21.  
  22. $Counter = 0
  23. while ($Global:ServiceRunning)
  24. {
  25.     $Counter | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  26.     $Counter++
  27. }
  28.  
  29. "Loop Exit" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  30. "Before Sleep" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  31. Start-Sleep -Seconds 10
  32. "After Sleep" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  33. "Last Line?" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append

User avatar
davidc
Posts: 5913
Last visit: Mon Jul 08, 2019 8:55 am
Been upvoted: 1 time

Re: Creating a Service

Post by davidc »

We released the PowerShell Studio build (v5.5.151) with the service executable update.
David
SAPIEN Technologies, Inc.