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.


    Listing 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?


    Also popular now: