We saw our Windows service - a guide for “not real programmers”
One day you will think how to turn a script or application into a Windows service. Most likely, the task will not be so trivial - the application will need at least a special interface to receive commands from the system. And since there are requirements and limitations, that is, both the scripts and sweethearts crutches to overcome.
The article will be useful to those who, like me - "the programmer is not real."
Why do I need a service if there are scheduled tasks
Unlike scheduled tasks, the service runs continuously, starts at the start of the PC, and can be controlled by Windows tools. And a regularly running script may need data from a previous run, and it may be useful to get data from external sources — for example, in the case of TCP or Web server.
Personally, over the past five years I have had to create a service three and a half times:
- It was necessary to create a service for fail2ban for Windows 2003. , which worked with the FileZilla and Apache logs, and if it was suspected, brute-force blocked IP by standard Windows tools — ipsec.
- Analogue telnet server for home versions of Windows. It took to execute commands on remote workstations that were running Windows 7 Home. In fact, the second attempt to play in the service.
- Music player for the trading floor under Windows. The task of the TZ could be solved with the help of mpd and packs of scripts, but I decided - if you do scripts, then why not “pile” the player yourself. Based on the library BASS.dll .
- When choosing a web server with support for downloading files under Windows, one option was HFS . By itself, he can not work, so I had to "shove" him into the service. As a result, I did not like the solution, and simply installed the Apaxy “theme” on the Apache web server.
To create a service, you can use adult programming languages like C. But if you don’t want to contact Visual Studio, then get ready-made utilities. There are paid solutions like FireDaemon Pro or AlwaysUp , but we traditionally focus on free ones.
Method one. From Microsoft
This is a middle-aged mechanism consists of two components: instsrv.exe utility for installing the service and srvany.exe , the process for running any executable files. Suppose we created a web server on PowerShell using the Polaris module . The script will be extremely simple:
New-PolarisGetRoute -Path '/helloworld' -Scriptblock {
$Response.Send('Hello World!')
}
Start-Polaris -Port 8080while($true) {
Start-Sleep -Milliseconds 10
}
The work of the so-called "server".
Now we will try to turn the script into a service. To do this, download the Windows Resource Kit Tools , where our utilities will be. Let's start by installing an empty service with the command:
instsrv WebServ C:\temp\rktools\srvany.exe
Where WebServ is the name of our new service. If necessary, using the services.msc snap-in, you can specify the user under which the service will run, and allow interaction with the desktop.
Now let's write the path to our script with the help of registry magic. The service settings are in the registry key HKLM \ SYSTEM \ CurrentControlSet \ Services \ WebServ . In it, we need to add a new Parameters section and create an Application string parameter there , indicating in it the path to the executable file. In the case of the PowerShell script, it will look like this:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File C:\temp\Polaris\server.ps1
Customized service.
You can run and enjoy.
Running service
However, this method has disadvantages:
- The utilities are old, developed before the invention of PowerShell, UAC and other things.
- Srvany does not control the operation of the application. Even if it falls into error, the service will continue its work as if nothing had happened.
- We'll have to tune in and dig into the registry. You remember that it is not safe to dig in the registry?
Therefore, we turn to the method, partially devoid of these problems.
Method two, almost adult
There is a utility called NSSM - Non-Sucking Service Manager , which can be translated as a non-bad service manager . Unlike the previous one, it is supported by the developer, and the source code is published on the site. In addition to the usual method, installation is also available through the Chocolately package manager .
You can create a service from the usual command line, armed with documentation on the developer's site. But we will use PowerShell. Because we can, of course.
$nssm = (Get-Command ./nssm).Source
$serviceName = 'WebServ'
$powershell = (Get-Command powershell).Source
$scriptPath = 'C:\temp\Polaris\server.ps1'
$arguments = '-ExecutionPolicy Bypass -NoProfile -File "{0}"' -f $scriptPath
& $nssm install $serviceName $powershell $arguments
& $nssm status $serviceName
Start-Service $serviceName
Get-Service $serviceName
Installation via PowerShell.
For a change, we’ll check the operation of the service not with a browser, but also through PowerShell with the Invoke-RestMethod command.
It really works.
Unlike srvany , this method allows you to restart the application at startup, redirect stdin and stdout, and more. In particular, if you do not want to write commands on the command line, then it is enough to launch the GUI and enter the necessary parameters through a convenient interface.
The GUI is started with the command:
nssm.exe install ServiceName
You can even adjust the priority and use of processor cores.
Indeed, the possibilities are much greater than those of srvany and a number of other analogues . Of the minuses is the lack of control over the whole process.
There is a lack of "tin". Therefore, I turn to the most hardcore method of all tested.
The third way. Autoit
Since I am a longtime lover of this scripting language, I could not pass by a library called _Services_UDF v4 . It is equipped with rich documentation and examples, so under the spoiler I will immediately give the full text of the resulting script.
So, let's try to “wrap” our web service in it:
#NoTrayIcon#RequireAdmin#Region#AutoIt3Wrapper_Version=Beta#AutoIt3Wrapper_UseUpx=n#AutoIt3Wrapper_Compile_Both=y#AutoIt3Wrapper_UseX64=y#EndRegion
Dim $MainLog = @ScriptDir & "\test_service.log"#include <services.au3>#include <WindowsConstants.au3>
$sServiceName="WebServ"If $cmdline[0] > 0 Then
Switch $cmdline[1]
Case"install", "-i", "/i"
InstallService()
Case"remove", "-u", "/u", "uninstall"
RemoveService()
CaseElse
ConsoleWrite(" - - - Help - - - " & @CRLF)
ConsoleWrite("params : " & @CRLF)
ConsoleWrite(" -i : install service" & @CRLF)
ConsoleWrite(" -u : remove service" & @CRLF)
ConsoleWrite(" - - - - - - - - " & @CRLF)
ExitEndSwitchElse
_Service_init($sServiceName)
ExitEndIf
Func _main($iArg, $sArgs)
If Not _Service_ReportStatus($SERVICE_RUNNING, $NO_ERROR, 0) Then
_Service_ReportStatus($SERVICE_STOPPED, _WinAPI_GetLastError(), 0)
ExitEndIf
$bServiceRunning = True
$PID=Run("C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File C:\temp\Polaris\server.ps1")
While $bServiceRunning
_sleep(1000)
WEnd
ProcessClose($PID)
_Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, 1000)
DllCallbackFree($tServiceMain)
DllCallbackFree($tServiceCtrl)
_Service_ReportStatus($SERVICE_STOPPED, $NO_ERROR, 0)
DllClose($hAdvapi32_DLL)
DllClose($hKernel32_DLL)
EndFunc
Func _Sleep($delay)
Local $result = DllCall($hKernel32_DLL, "none", "Sleep", "dword", $delay)
EndFunc
Func InstallService()
#RequireAdmin
Local $bDebug = TrueIf $cmdline[0] > 1 Then
$sServiceName = $cmdline[2]
EndIfIf $bDebug Then ConsoleWrite("InstallService("&$sServiceName &"): Installing service, please wait")
_Service_Create($sServiceName, $sServiceName, $SERVICE_WIN32_OWN_PROCESS, $SERVICE_AUTO_START, $SERVICE_ERROR_SEVERE, '"' & @ScriptFullPath & '"');,"",False,"","NT AUTHORITY\NetworkService")
If @error Then
Msgbox("","","InstallService(): Problem installing service, Error number is " & @error & @CRLF & " message : " & _WinAPI_GetLastErrorMessage())
ElseIf $bDebug Then ConsoleWrite("InstallService(): Installation of service successful")
EndIfExit
EndFunc
Func RemoveService()
_Service_Stop($sServiceName)
_Service_Delete($sServiceName)
If Not @error Then
EndIfExit
EndFunc
Func _exit()
_Service_ReportStatus($SERVICE_STOPPED, $NO_ERROR, 0);
EndFunc
Func StopTimer()
_Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, $iServiceCounter)
$iServiceCounter += -100
EndFunc
Func _Stopping()
_Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, 3000)
EndFunc
I will analyze in more detail the moment you start the application It starts after the operation $ bServiceRunning = True and turns into a seemingly infinite loop. In fact, this process will be interrupted as soon as the service receives a signal for termination - be it to log out or stop manually.
Since the script program is external (powershell.exe), after exiting the loop we need to finish its work using ProcessClose .
To do this, the script must be compiled into .exe , and then install the service by running exe with the -i key .
It is working!
Of course, this method is not the most convenient, and all additional features will have to be implemented independently, whether it be a restart of the application in case of failure or log rotation. But then he gives full control over what is happening. Yes, and in the end you can do much more - from the notice in the Telegram about the failure of the service to IPC-interaction with other programs. And in addition - in the scripting language, without installing and learning Visual Studio.
Tell me, did you have to turn scripts and applications into services?