Page 1 of 1

Creating a Service

Posted: Mon Apr 23, 2018 10:30 pm
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.

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 $_ }
}
}


Re: Creating a Service

Posted: Tue Apr 24, 2018 8:17 am
by davidc
This functionality will be available in the next service release of PowerShell Studio. It should be released very soon.

Re: Creating a Service

Posted: Tue Apr 24, 2018 8:37 am
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.

Re: Creating a Service

Posted: Tue Apr 24, 2018 6:49 pm
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 ...


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

Re: Creating a Service

Posted: Thu Apr 26, 2018 8:06 am
by davidc
We released the PowerShell Studio build (v5.5.151) with the service executable update.