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.

Code: Select all

<#
.NOTES
========================================================================
Windows PowerShell Source File
Created with SAPIEN Technologies PrimalScript 2018
NAME: Marc Collins (Nillth)
DATE : 23/04/2018
==========================================================================
#>

# Warning: Do not rename Start-MyService, Invoke-MyService and Stop-MyService functions

function Get-ScriptDirectory
{
if ($null -ne $hostinvocation)
{
Split-Path $hostinvocation.MyCommand.path
}
else
{
Split-Path $script:MyInvocation.MyCommand.Path
}
}


function Start-MyService
{
$Global:StopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
$Global:StopWatch.Start()
$global:bRunService = $true
$global:bServiceRunning = $false


#region Register Events to Monitor

###########################################################################
#region ServiceMonitor
$RunningService = Get-WmiObject Win32_Service -Filter "ProcessId='$($PID)'"
$query = @"
SELECT *
FROM __InstanceModificationEvent
WITHIN 2
WHERE TargetInstance ISA 'Win32_Service'
AND TargetInstance.Name = '$($RunningService.Name)'
"@
Unregister-Event ServiceChangeWIM -ErrorAction SilentlyContinue
Register-WmiEvent -Query $query ServiceChangeWIM
#endregion ServiceMonitor

###########################################################################
#region FileSystem Watcher/s

$ScriptDirectory = Get-ScriptDirectory
$TriggerConfig = "$($ScriptDirectory)\TriggerConfig.xml"

#Creates a Trigger config and Template file on first run if they do not exist
if (!(Test-Path $TriggerConfig))
{
$TriggerExample = "$($ScriptDirectory)TriggerTemplate.txt"
$TriggerTemplate = @"
<?xml version="1.0"?>
<Triggers>
<Trigger Name="MyApp">
<TriggerFile>$($TriggerExample)</TriggerFile>
</Trigger>
</Triggers>
"@
#Create a empty template triggerfile
if (!(Test-Path $TriggerExample))
{
"" | Out-File -FilePath $TriggerExample -Encoding utf8 -Force
}
$TriggerTemplate | Out-File -FilePath $TriggerConfig -Encoding utf8 -Force
}

$XMLTriggerConfig = New-Object XML
$XMLTriggerConfig.Load($TriggerConfig)

foreach ($Trigger in $XMLTriggerConfig.Triggers.Trigger)
{

$TriggerFolder = [system.io.path]::GetDirectoryName($Trigger.TriggerFile)
$TriggerFile = [system.io.path]::GetFileName($Trigger.TriggerFile)
if ($null -ne $Trigger.Name)
{
$TriggerName = "$($Trigger.Name)"
}
else
{
$TriggerName = "$($TriggerFile)"
}
if (Test-Path -LiteralPath $TriggerFolder)
{
#Create the File System Watcher
$fsw = New-Object IO.FileSystemWatcher $TriggerFolder, $TriggerFile -Property @{ IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite' }
$SourceIdentifier = "FileTrigger - $($TriggerFile)"
}

Register-ObjectEvent $fsw Changed -SourceIdentifier $SourceIdentifier -Action {New-Event -SourceIdentifier TriggerTouch -MessageData $Event} | Out-Null

}

#endregion FileSystem Watcher
#endregion Register Events to Monitor

}

function Invoke-MyService
{
$global:bServiceRunning = $true
while ($global:bRunService)
{
$EventReceived = Wait-Event -SourceIdentifier TriggerTouch -Timeout 5

#Check for shutdown/pause etc activity
$EventAction = Get-Event -SourceIdentifier Action
if ($null -ne $EventAction)
{
if ($EventReceived.MessageData -eq "ShutdownService")
{
#Termination code
}

}
else
{
#Process Something.
#This should contain the Information returned from the FileWatcher
#$EventReceived.MessageData
#e.g.
Write-Host "File $($EventReceived.MessageData.SourceArgs[1].ChangeType) - $($EventReceived.MessageData.SourceArgs[1].FullPath)"

if ($null -ne $EventReceived)
{
Remove-Event -EventIdentifier $EventReceived.EventIdentifier -ErrorAction SilentlyContinue
}
}
}
$global:bServiceRunning = $false
}

function Stop-MyService
{
$global:bRunService = $false # Signal main loop to exit
while ($global:bServiceRunning)
{
$EventReceived = Wait-Event ServiceChangeWIM -Timeout 1
if ($null -ne $EventReceived)
{
Remove-Event -EventIdentifier $EventReceived.EventIdentifier -ErrorAction SilentlyContinue
if ($EventReceived.SourceEventArgs.NewEvent.TargetInstance.State -eq "Stop Pending")
{
New-Event -SourceIdentifier Action -MessageData "ShutdownService"
$global:bServiceRunning = $false
}
}
}
$EventSubscriber = Get-EventSubscriber -ErrorAction SilentlyContinue
if ($null -ne $EventSubscriber)
{
$EventSubscriber.SourceIdentifier | %{ Unregister-Event $_ }
}
}


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: 7049
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 ...


Code: Select all

$Global:ServiceRunning = $true
$RunningService = Get-WmiObject Win32_Service -Filter "ProcessId='$($PID)'"
$query = @"
SELECT *
FROM __InstanceModificationEvent
WITHIN 2
WHERE TargetInstance ISA 'Win32_Service'
AND TargetInstance.Name = '$($RunningService.Name)'
"@
Unregister-Event ServiceChangeWIM -ErrorAction SilentlyContinue
Register-WmiEvent -Query $query ServiceChangeWIM -Action {
"StateChange" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
if ($event.SourceEventArgs.NewEvent.TargetInstance.State -ne "Running")
{
$Global:ServiceRunning = $false
}
New-Event -SourceIdentifier StateChange -MessageData $Event
} | Out-Null



$Counter = 0
while ($Global:ServiceRunning)
{
$Counter | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
$Counter++
}

"Loop Exit" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
"Before Sleep" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
Start-Sleep -Seconds 10
"After Sleep" | Out-File -LiteralPath "C:\temp\Service\log.txt" -Encoding ascii -Append
"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.