提醒:本页面将不再更新、维护或者支持,文章、评论所叙述内容存在时效性,涉及技术细节或者软件使用方面不保证能够完全有效可操作,请谨慎参考!

之前写过一篇文章 介绍srvany.exe 这个小工具的,今天有网友反映能否使用命令行方式创建基于srvany.exe的Windows服务,我想到之前编写的 SrvanyUI软件 没有命令参数直接调用的功能,本来想完善一下SrvanyUI这个软件的,苦于最近较忙,于是先用VBScript凑合写着一个命令行控制的脚本 srvany.vbs ,其中用到了 RestartService 子过程来自 Rob van der Woude ,在此表示感谢!

基本的思路很简单,就是先创建srvany.exe为可执行映像的NT系统服务,然后在利用注册表修改其间的配置参数,最后再启动服务。

先把 srvany.vbs 的代码贴在这儿,具体的使用方法稍后附上。

'
' Usage: 
'    WScript.exe srvany.vbs -name   [ServiceName]
'                           -srvany [SrvAnyFileName]
'                           -file   [ExeFileName]
'                           -dir    [ExeFileDirectory]
'                           -params [ExeFileParameters]
'
' For more information please visit http://wangye.org
'
Option Explicit

Const HKLM = &H80000002

' Service Type
Const KERNEL_DRIVER        =   1
Const FS_DRIVER            =   2
Const ADAPTER              =   4
Const RECOGNIZER_DRIVER    =   8
Const OWN_PROCESS          =  16
Const SHARE_PROCESS        =  32
Const INTERACTIVE_PROCESS  = 256
 
Const INTERACT_WITH_DESKTOP = FALSE
 
' Error Control
Const NOT_NOTIFIED         = 0
Const USER_NOTIFIED        = 1
Const SYSTEM_RESTARTED     = 2
Const SYSTEM_STARTS        = 3

Class CommandLineParser
    ' Written by WangYe
    ' http://wangye.org
    Private parameters
    Private splitargs
    
    Private Sub Class_Initialize()
        Set parameters = WSH.CreateObject("Scripting.Dictionary")
        Set splitargs = WSH.CreateObject("Scripting.Dictionary")
    End Sub
    
    Private Sub Class_Terminate()
        splitargs.RemoveAll
        Set splitargs = Nothing
        parameters.RemoveAll
        Set parameters = Nothing
    End Sub
    
    Public Sub addSplitter(key, value)
        splitargs.Add key, value
    End Sub
    
    Public Sub parse(args)
        Dim i, j, k, token, c, statusChanged
        Dim statusSkipped, tokens
        Dim keys, items
        
        statusChanged = False
        keys = splitargs.Keys
        items = splitargs.Items
        
        For i = 0 To args.Count-1
            If statusChanged Then
                parameters.Add token, args(i)
                statusChanged = False
            End If
            c = Left(Trim(args(i)), 1)
            If c = "-" Then
                statusChanged = True
                statusSkipped = False
                For j=2 To Len(args(i))-1
                    c = MID(args(i), j, 1)
                    statusSkipped = CBool(c = "-")
                    If Not statusSkipped Then Exit For
                Next
                token = Right(args(i), Len(args(i)) - j+1)
                
                For k = 0 To splitargs.Count - 1
                    tokens = Split(token, items(k))
                
                    If UBound(tokens) > 0 Then
                        If keys(k) = tokens(0) Then
                            parameters.Add tokens(0), _
                                        tokens(1)
                            statusChanged = False
                        End If
                    End If
                Next
            End If
            
            If i = args.Count-1 And statusChanged Then
                parameters.Add token, ""
            End If
        Next
    End Sub
    
    Public Sub dump()
        Dim i, Keys, Items
        Keys = parameters.Keys
        Items = parameters.Items
        
        For i = 0 To parameters.Count - 1
            WSH.Echo Keys(i) & "=" & Items(i)
        Next
    End Sub
    
    Public Function hasArgument(name)
        hasArgument = parameters.Exists(name)
    End Function
    
    Public Function getArgument(name)
        getArgument = ""
        If parameters.Exists(name) Then
            getArgument = parameters(name)
        End If
    End Function
    
    Public Default Property Get Item(name)
        Item = getArgument(name)
    End Property
End Class

Sub RestartService( myService, blnQuiet )
' This subroutine restarts a service
' Arguments:
' myService     use the service's DisplayName
' blnQuiet      if False, the state of the service is displayed
'               every second during the restart procedure
'
' Written by Rob van der Woude
' http://www.robvanderwoude.com

    ' Standard housekeeping
    Dim colServices, colServicesTest, objService
    Dim objServiceTest, objWMIService, strQuery, strTest

    ' Create a WMI object
    Set objWMIService = GetObject( "winmgmts:\\.\root\CIMV2" )

    ' Query the services for "our" service
    strQuery = "SELECT * FROM Win32_Service WHERE DisplayName='" & myService & "'"
    Set colServices = objWMIService.ExecQuery( strQuery, "WQL", 48 )

    ' Loop through the "collection" of returned services
    For Each objService In colServices
        ' See if we need to tell the user we're going to stop the service
        If Not blnQuiet Then
            WScript.Echo "Stopping " & myService
        End If

        ' Stop the service
        objService.StopService

        ' Wait until the service is stopped
        Do Until strTest = "Stopped"
            ' Create a new object for our service; this work-around is required
            ' since otherwise the service's state information isn't properly updated
            Set colServicesTest = objWMIService.ExecQuery( strQuery, "WQL", 48 )

            ' Loop through the "collection" of returned services
            For Each objServiceTest In colServicesTest
                ' Check the service's state
                strTest = objServiceTest.State
                ' See if we need to show the progress
                If Not blnQuiet Then
                    WScript.Echo "State: " & strTest
                End If
                ' Wait 1 second
                WScript.Sleep 1000
            Next

            ' Clear the temporary object
            Set colServicesTest = Nothing
        Loop

        ' See if we need to tell the user we're going to (re)start the service
        If Not blnQuiet Then
            WScript.Echo "Starting " & myService
        End If

        ' Start the service
        objService.StartService

        ' Wait until the service is running again
        Do Until strTest = "Running"
            ' Create a new object for our service; this work-around is required
            ' since otherwise the service's state information isn't properly updated
            Set colServicesTest = objWMIService.ExecQuery( strQuery, "WQL", 48 )

            ' Loop through the "collection" of returned services
            For Each objServiceTest In colServicesTest
                ' Check the service's state
                strTest = objServiceTest.State
                ' See if we need to show the progress
                If Not blnQuiet Then
                    WScript.Echo "State: " & strTest
                End If
                ' Wait 1 second
                WScript.Sleep 1000
            Next

            ' Clear the temporary object
            Set colServicesTest = Nothing
        Loop
    Next
End Sub

Const L_Argument_Service_Name    = "name"
Const L_Argument_SrvAnyFile_Name = "srvany"
Const L_Argument_AppFile_Name    = "file"
Const L_Argument_AppParams_Name  = "params"
Const L_Argument_AppDir_Name     = "dir"

Function VBMain(argc, args)

    VBMain = 0
    
    If argc<1 Then
        VBMain = -1
        WSH.Echo "Usage:  -name   [ServiceName]" & vbCrLf & _
                "        -srvany [SrvAnyFileName]" & vbCrLf & _
                "        -file   [ExeFileName]" & vbCrLf & _
                "        -dir    [ExeFileDirectory]" & vbCrLf & _
                "        -params [ExeFileParameters]"
        Exit Function
    End If
    
    Dim nResult
    Dim strAppFileName, strAppParameters, strAppDirectory
    Dim strComputer, strServiceName, strSrvAnyFileName
    strComputer       = "."
    
    Dim objCommandLineParser
    Set objCommandLineParser = New CommandLineParser
        
    Call objCommandLineParser.parse(args)
    ' objCommandLineParser.dump
    
    strServiceName    = objCommandLineParser(L_Argument_Service_Name)
    strSrvAnyFileName = objCommandLineParser(L_Argument_SrvAnyFile_Name)
    strAppFileName    = objCommandLineParser(L_Argument_AppFile_Name)
    strAppParameters  = objCommandLineParser(L_Argument_AppParams_Name)
    strAppDirectory   = objCommandLineParser(L_Argument_AppDir_Name)
    
    If strServiceName = "" Then
        WSH.Echo "Service Name is invalid (Usage: -name [ServiceName])"
        VBMain = -2
        Exit Function
    End If
    
    Dim objFileSystemObject
    Set objFileSystemObject = WSH.CreateObject("Scripting.FileSystemObject")
    
    If strSrvAnyFileName <> "" Then
        strSrvAnyFileName = objFileSystemObject.GetAbsolutePathName(strSrvAnyFileName)
        If Not objFileSystemObject.FileExists(strSrvAnyFileName) Then
            strSrvAnyFileName = ""
        End If
    End If
    
    If strSrvAnyFileName = "" Then
        WSH.Echo "SrvAny File Name is invalid (Usage: -srvany [SrvAnyFileName])"
        VBMain = -2
        Exit Function
    End If
    
    If strAppFileName <> "" Then
        strAppFileName = objFileSystemObject.GetAbsolutePathName(strAppFileName)
        If Not objFileSystemObject.FileExists(strAppFileName) Then
            strAppFileName = ""
        End If
    End If
    
    If strAppFileName = "" Then
        WSH.Echo "Application File Name is invalid (Usage: -file [SrvAnyFileName])"
        VBMain = -2
        Exit Function
    End If
    
    If strAppDirectory = "" Then
        strAppDirectory = objFileSystemObject.GetParentFolderName(strAppFileName)
    End If
    
    Set objFileSystemObject = Nothing
    Dim objWMI, objService
    Set objWMI = GetObject("winmgmts:\\" & _
                    strComputer & "\root\cimv2")
    Set objService = objWMI.Get("Win32_Service")
    nResult = objService.Create( _
        strServiceName    , _
        strServiceName    , _
        strSrvAnyFileName , _
        OWN_PROCESS       , _
        NOT_NOTIFIED      , _
        "Automatic"       , _
        INTERACT_WITH_DESKTOP, _
        "NT AUTHORITY\LocalService", _
        "" _
        )
    
    Set objService = Nothing
    Set objWMI = Nothing
    
    If nResult > 0 Then
        VBMain = nResult
        Exit Function
    End If
    
    ' Write Registry
    Dim strKeyPath, objReg
    strKeyPath = "SYSTEM\CurrentControlSet\Services\" & _
                strServiceName & "\Parameters"
    Set objReg = GetObject("winmgmts:\\" & _
            strComputer & "\root\default:StdRegProv")
    objReg.CreateKey HKLM,strKeyPath
    objReg.SetStringValue HKLM, strKeyPath, "Application"   , strAppFileName
    objReg.SetStringValue HKLM, strKeyPath, "AppParameters" , strAppParameters
    objReg.SetStringValue HKLM, strKeyPath, "AppDirectory" , strAppDirectory
    
    Set objReg = Nothing
    
    RestartService strServiceName, True
End Function

WSH.Quit(VBMain(WScript.Arguments.Count, WScript.Arguments))

调用方法,编写批处理文件(*.bat)或者直接调用命令:

WScript srvany.vbs -name 服务名称 -srvany "C:\Windows\srvany.exe" -file "C:\Windows\notepad.exe"

我这里假设 srvany.exe 位于 C:\Windows 路径下,然后希望将 notepad.exe 作为服务启动,当然上面的调用参数没有写全,省去的参数是 -params ,如果你不希望你的程序启动时传入启动参数,这个可以省略;还有一个参数就是 -dir 这个是指程序启动时的目录,如果省去则默认为 -file 所指派的程序所在目录。

当然这个脚本还略显简陋,我简单的在Windows XP测试了一下算是通过了,代码供大家参考吧。