锯切Windows服务-面向“非真正程序员”的指南


一旦考虑了如何将脚本或应用程序转换为Windows服务。 最有可能的是,任务不会那么琐碎-至少应用程序需要一个特殊的接口来接收来自系统的命令。 而且由于存在一些要求和限制,因此需要克服的问题是剧本和拐杖,它们真是令人动容。


这篇文章对像我一样不是“真正的程序员”的人很有用。


如果有计划任务,为什么需要服务


与分配的任务相反,该服务持续运行,从PC的启动处启动,并且可以由Windows工具控制。 同样,定期启动的脚本可能需要先前启动的数据,并且从外部源获取数据可能很有用-例如,在TCP或Web服务器的情况下。


就个人而言,在过去的五年中,我不得不创建一个服务三倍半:


  • 需要在Windows 2003的fail2ban上创建一个服务,该服务可与FileZilla和Apache日志一起使用,如果怀疑是蛮力的,则可以使用常规Windows工具ipsec阻止IP。
  • Windows家庭版的telnet服务器的类似物。 它需要在运行Windows 7 Home的远程工作站上执行命令。 其实是第二次尝试打服务。
  • Windows下交易大厅的音乐播放器。 可以通过mpd和一堆脚本来解决有关TK的任务,但是我决定-如果我们要编写脚本,那为什么不自己“堆”玩家。 基于库BASS.dll
  • 选择支持在Windows下下载文件的Web服务器时,一种选择是HFS 。 他不能独自工作,因此他不得不“推”他去服务。 结果,我不喜欢该解决方案,而是在Apache Web服务器上安装了Apaxy主题

要创建服务,可以使用成人编程语言(例如C)。但是,如果您不想与Visual Studio通信,则可以使用现成的实用程序。 有付费解决方案,例如FireDaemon ProAlwaysUp ,但传统上我们将专注于免费解决方案。


第一种方式。 来自微软


这种已经过时的机制包括两个组件:用于安装服务的instsrv.exe实用程序和用于启动任何可执行文件的过程srvany.exe 。 假设我们使用Polaris模块在PowerShell上创建了Web服务器。 该脚本将非常简单:


New-PolarisGetRoute -Path '/helloworld' -Scriptblock { $Response.Send('Hello World!') } Start-Polaris -Port 8080 while($true) { Start-Sleep -Milliseconds 10 } 


所谓的“服务器”工作。


现在,让我们尝试将脚本转换为服务。 为此,请下载Windows Resource Kit工具 ,该工具将位于我们的实用程序中。 让我们开始使用以下命令安装空服务:


 instsrv WebServ C:\temp\rktools\srvany.exe 

WebServ是我们新服务的名称。 如有必要,通过services.msc管理单元,您可以指定将在其下启动服务的用户,并允许与桌面进行交互。


现在,让我们使用注册表魔术来编写脚本的路径。 服务参数位于注册表项HKLM \ SYSTEM \ CurrentControlSet \ Services \ WebServ中 。 在其中,我们需要添加一个新的Parameters部分,并在其中创建一个字符串参数Application ,在其中指示可执行文件的路径。 如果使用PowerShell脚本,它将如下所示:


 C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File C:\temp\Polaris\server.ps1 


定制服务。


您可以尽情享受。



工作服务。


但是,此方法有缺点:


  • 实用程序很老,是在PowerShell,UAC和其他东西发明之前开发的。
  • Srvany不控制应用程序的操作。 即使出现错误,服务也将继续其工作,好像什么都没发生。
  • 将不得不调整并深入研究注册表。 您还记得挖掘注册表不安全吗?

因此,我们转向一种部分没有这些问题的方法。


第二种方式,几乎是成人


有一个名为NSSM - Non-Sucking Service Manager的实用程序,可以将其转换为非错误服务管理器 。 与前一个不同,它由开发人员支持,并且源代码在站点上发布。 除了通常的方法外,还可以通过Chocolately软件包管理器进行安装。


您可以从通常的命令行创建服务,并附带开发人员站点上的文档 。 但是我们将使用PowerShell。 因为我们当然可以。


 $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 


通过PowerShell安装。


要进行更改,我们将不使用浏览器来检查服务的工作,而是使用Invoke-RestMethod命令通过PowerShell检查该服务的工作



它确实有效。


srvany不同,此方法使您可以在启动时重新启动应用程序,重定向stdinstdout等。 特别是,如果您不想在命令行上编写命令,则只需运行GUI并通过方便的界面输入必要的参数即可。


GUI使用以下命令启动:


 nssm.exe install ServiceName 


您甚至可以配置处理器内核的优先级和使用。


确实,比srvany和许多其他类似物有更多的机会。 在缺点中,对整个过程的控制不足。


缺少锡。 因此,我将继续介绍所有经过测试的最严格的方法。


第三种方式。 自动IT


由于我是该脚本语言的长期爱好者,因此无法超越名为_Services_UDF v4的库。 它配备了丰富的文档和示例,因此在“破坏者”下,我将立即提供结果脚本的全文。


脚本清单

因此,让我们尝试将我们的Web服务“包装”到其中:


 #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() Case Else ConsoleWrite(" - - - Help - - - " & @CRLF) ConsoleWrite("params : " & @CRLF) ConsoleWrite(" -i : install service" & @CRLF) ConsoleWrite(" -u : remove service" & @CRLF) ConsoleWrite(" - - - - - - - - " & @CRLF) Exit EndSwitch Else _Service_init($sServiceName) Exit EndIf Func _main($iArg, $sArgs) If Not _Service_ReportStatus($SERVICE_RUNNING, $NO_ERROR, 0) Then _Service_ReportStatus($SERVICE_STOPPED, _WinAPI_GetLastError(), 0) Exit EndIf $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 = True If $cmdline[0] > 1 Then $sServiceName = $cmdline[2] EndIf If $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()) Else If $bDebug Then ConsoleWrite("InstallService(): Installation of service successful") EndIf Exit EndFunc Func RemoveService() _Service_Stop($sServiceName) _Service_Delete($sServiceName) If Not @error Then EndIf Exit 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 

启动应用程序后,我将进行更详细的分析。 它在$ bServiceRunning = True操作之后开始,并变成看似无限的循环。 实际上,一旦服务收到完成信号-无论是注销还是手动停止,此过程都将被中断。


由于脚本的程序是外部程序(powershell.exe),因此在退出循环后,我们需要使用ProcessClose完成其工作。


为此,必须将脚本编译为.exe ,然后通过使用-i开关运行exe来安装服务



有效!


当然,这种方法不是最方便的,并且所有其他功能都必须独立实现,无论是在出现故障还是日志轮换时重新启动应用程序。 但是他完全控制正在发生的事情。 最后,您可以做更多的事情-从通知 Telegram服务故障到IPC与其他程序的交互。 另外-使用脚本语言,无需安装和学习Visual Studio。


告诉我们,您是否必须将脚本和应用程序转换为服务?

Source: https://habr.com/ru/post/zh-CN421019/


All Articles