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 SUBSCRIPTION NUMBERS, LICENSE 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: 15
Joined: Thu Aug 01, 2013 6:14 pm
Location: Melbourne, Australia

Creating a Service

Post by Nillth » Mon Apr 23, 2018 10:30 pm

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.  
  3.     .NOTES
  4.  
  5.     ========================================================================
  6.  
  7.          Windows PowerShell Source File
  8.  
  9.          Created with SAPIEN Technologies PrimalScript 2018
  10.  
  11.          NAME: Marc Collins (Nillth)
  12.  
  13.          DATE  : 23/04/2018
  14.  
  15.     ==========================================================================
  16.  
  17. #>
  18.  
  19.  
  20.  
  21. # Warning: Do not rename Start-MyService, Invoke-MyService and Stop-MyService functions
  22.  
  23.  
  24.  
  25. function Get-ScriptDirectory
  26.  
  27. {
  28.  
  29.     if ($null -ne $hostinvocation)
  30.  
  31.     {
  32.  
  33.         Split-Path $hostinvocation.MyCommand.path
  34.  
  35.     }
  36.  
  37.     else
  38.  
  39.     {
  40.  
  41.         Split-Path $script:MyInvocation.MyCommand.Path
  42.  
  43.     }
  44.  
  45. }
  46.  
  47.  
  48.  
  49.  
  50.  
  51. function Start-MyService
  52.  
  53. {
  54.  
  55.     $Global:StopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
  56.  
  57.     $Global:StopWatch.Start()
  58.  
  59.     $global:bRunService = $true
  60.  
  61.     $global:bServiceRunning = $false
  62.  
  63.    
  64.  
  65.    
  66.  
  67.     #region Register Events to Monitor
  68.  
  69.    
  70.  
  71.     ###########################################################################
  72.  
  73.     #region ServiceMonitor
  74.  
  75.     $RunningService = Get-WmiObject Win32_Service -Filter "ProcessId='$($PID)'"
  76.  
  77.     $query = @"
  78.  
  79.        SELECT *
  80.  
  81.        FROM __InstanceModificationEvent
  82.  
  83.        WITHIN 2
  84.  
  85.        WHERE TargetInstance ISA 'Win32_Service'
  86.  
  87.        AND TargetInstance.Name = '$($RunningService.Name)'
  88.  
  89. "@
  90.  
  91.     Unregister-Event ServiceChangeWIM -ErrorAction SilentlyContinue
  92.  
  93.     Register-WmiEvent -Query $query ServiceChangeWIM
  94.  
  95.     #endregion ServiceMonitor  
  96.  
  97.    
  98.  
  99.     ###########################################################################
  100.  
  101.     #region FileSystem Watcher/s
  102.  
  103.    
  104.  
  105.     $ScriptDirectory = Get-ScriptDirectory
  106.  
  107.     $TriggerConfig = "$($ScriptDirectory)\TriggerConfig.xml"
  108.  
  109.    
  110.  
  111.     #Creates a Trigger config and Template file on first run if they do not exist
  112.  
  113.     if (!(Test-Path $TriggerConfig))
  114.  
  115.     {
  116.  
  117.         $TriggerExample = "$($ScriptDirectory)TriggerTemplate.txt"
  118.  
  119.         $TriggerTemplate = @"
  120.  
  121. <?xml version="1.0"?>
  122.  
  123. <Triggers>
  124.  
  125.     <Trigger Name="MyApp">
  126.  
  127.         <TriggerFile>$($TriggerExample)</TriggerFile>
  128.  
  129.     </Trigger>
  130.  
  131. </Triggers>
  132.  
  133. "@
  134.  
  135.         #Create a empty template triggerfile
  136.  
  137.         if (!(Test-Path $TriggerExample))
  138.  
  139.         {
  140.  
  141.             "" | Out-File -FilePath $TriggerExample -Encoding utf8 -Force
  142.  
  143.         }
  144.  
  145.         $TriggerTemplate | Out-File -FilePath $TriggerConfig -Encoding utf8 -Force
  146.  
  147.     }
  148.  
  149.    
  150.  
  151.     $XMLTriggerConfig = New-Object XML
  152.  
  153.     $XMLTriggerConfig.Load($TriggerConfig)
  154.  
  155.    
  156.  
  157.     foreach ($Trigger in $XMLTriggerConfig.Triggers.Trigger)
  158.  
  159.     {
  160.  
  161.        
  162.  
  163.         $TriggerFolder = [system.io.path]::GetDirectoryName($Trigger.TriggerFile)
  164.  
  165.         $TriggerFile = [system.io.path]::GetFileName($Trigger.TriggerFile)
  166.  
  167.         if ($null -ne $Trigger.Name)
  168.  
  169.         {
  170.  
  171.             $TriggerName = "$($Trigger.Name)"
  172.  
  173.         }
  174.  
  175.         else
  176.  
  177.         {
  178.  
  179.             $TriggerName = "$($TriggerFile)"
  180.  
  181.         }
  182.  
  183.         if (Test-Path -LiteralPath $TriggerFolder)
  184.  
  185.         {
  186.  
  187.             #Create the File System Watcher
  188.  
  189.             $fsw = New-Object IO.FileSystemWatcher $TriggerFolder, $TriggerFile -Property @{ IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite' }
  190.  
  191.             $SourceIdentifier = "FileTrigger - $($TriggerFile)"
  192.  
  193.         }
  194.  
  195.        
  196.  
  197.         Register-ObjectEvent $fsw Changed -SourceIdentifier $SourceIdentifier -Action {New-Event -SourceIdentifier TriggerTouch -MessageData $Event} | Out-Null
  198.  
  199.  
  200.  
  201.     }
  202.  
  203.    
  204.  
  205.     #endregion FileSystem Watcher
  206.  
  207.     #endregion Register Events to Monitor
  208.  
  209.    
  210.  
  211. }
  212.  
  213.  
  214.  
  215. function Invoke-MyService
  216.  
  217. {
  218.  
  219.     $global:bServiceRunning = $true
  220.  
  221.     while ($global:bRunService)
  222.  
  223.     {
  224.  
  225.         $EventReceived = Wait-Event -SourceIdentifier TriggerTouch -Timeout 5
  226.  
  227.        
  228.  
  229.         #Check for shutdown/pause etc activity
  230.  
  231.         $EventAction = Get-Event -SourceIdentifier Action
  232.  
  233.         if ($null -ne $EventAction)
  234.  
  235.         {
  236.  
  237.             if ($EventReceived.MessageData -eq "ShutdownService")
  238.  
  239.             {
  240.  
  241.                 #Termination code  
  242.  
  243.             }
  244.  
  245.            
  246.  
  247.         }
  248.  
  249.         else
  250.  
  251.         {
  252.  
  253.             #Process Something.
  254.  
  255.             #This should contain the Information returned from the FileWatcher
  256.  
  257.             #$EventReceived.MessageData
  258.  
  259.             #e.g.
  260.  
  261.             Write-Host "File $($EventReceived.MessageData.SourceArgs[1].ChangeType) - $($EventReceived.MessageData.SourceArgs[1].FullPath)"
  262.  
  263.            
  264.  
  265.             if ($null -ne $EventReceived)
  266.  
  267.             {
  268.  
  269.                 Remove-Event -EventIdentifier $EventReceived.EventIdentifier -ErrorAction SilentlyContinue
  270.  
  271.             }
  272.  
  273.         }
  274.  
  275.     }
  276.  
  277.     $global:bServiceRunning = $false
  278.  
  279. }
  280.  
  281.  
  282.  
  283. function Stop-MyService
  284.  
  285. {
  286.  
  287.     $global:bRunService = $false # Signal main loop to exit
  288.  
  289.     while ($global:bServiceRunning)
  290.  
  291.     {
  292.  
  293.         $EventReceived = Wait-Event ServiceChangeWIM -Timeout 1
  294.  
  295.         if ($null -ne $EventReceived)
  296.  
  297.         {
  298.  
  299.             Remove-Event -EventIdentifier $EventReceived.EventIdentifier -ErrorAction SilentlyContinue
  300.  
  301.             if ($EventReceived.SourceEventArgs.NewEvent.TargetInstance.State -eq "Stop Pending")
  302.  
  303.             {
  304.  
  305.                 New-Event -SourceIdentifier Action -MessageData "ShutdownService"
  306.  
  307.                 $global:bServiceRunning = $false
  308.  
  309.             }
  310.  
  311.         }
  312.  
  313.     }
  314.  
  315.     $EventSubscriber = Get-EventSubscriber -ErrorAction SilentlyContinue
  316.  
  317.     if ($null -ne $EventSubscriber)
  318.  
  319.     {
  320.  
  321.         $EventSubscriber.SourceIdentifier | %{ Unregister-Event $_ }
  322.  
  323.     }
  324.  
  325. }
  326.  
  327.  
  328.  
  329.  

User avatar
davidc
Posts: 5913
Joined: Thu Aug 18, 2011 4:56 am

Re: Creating a Service

Post by davidc » Tue Apr 24, 2018 8:17 am

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: 7099
Joined: Tue May 29, 2007 4:43 pm

Re: Creating a Service

Post by Alexander Riedel » Tue Apr 24, 2018 8:37 am

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: 15
Joined: Thu Aug 01, 2013 6:14 pm
Location: Melbourne, Australia

Re: Creating a Service

Post by Nillth » Tue Apr 24, 2018 6:49 pm

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.  
  3. $RunningService = Get-WmiObject Win32_Service -Filter "ProcessId='$($PID)'"
  4.  
  5. $query = @"
  6.  
  7.        SELECT *
  8.  
  9.        FROM __InstanceModificationEvent
  10.  
  11.        WITHIN 2
  12.  
  13.        WHERE TargetInstance ISA 'Win32_Service'
  14.  
  15.        AND TargetInstance.Name = '$($RunningService.Name)'
  16.  
  17. "@
  18.  
  19. Unregister-Event ServiceChangeWIM -ErrorAction SilentlyContinue
  20.  
  21. Register-WmiEvent -Query $query ServiceChangeWIM -Action {
  22.  
  23.     "StateChange" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  24.  
  25.     if ($event.SourceEventArgs.NewEvent.TargetInstance.State -ne "Running")
  26.  
  27.     {
  28.  
  29.         $Global:ServiceRunning = $false
  30.  
  31.     }
  32.  
  33.     New-Event -SourceIdentifier StateChange -MessageData $Event
  34.  
  35. } | Out-Null
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43. $Counter = 0
  44.  
  45. while ($Global:ServiceRunning)
  46.  
  47. {
  48.  
  49.     $Counter | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  50.  
  51.     $Counter++
  52.  
  53. }
  54.  
  55.  
  56.  
  57. "Loop Exit" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  58.  
  59. "Before Sleep" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  60.  
  61. Start-Sleep -Seconds 10
  62.  
  63. "After Sleep" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
  64.  
  65. "Last Line?" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append

User avatar
davidc
Posts: 5913
Joined: Thu Aug 18, 2011 4:56 am

Re: Creating a Service

Post by davidc » Thu Apr 26, 2018 8:06 am

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