Python Windows服务pyinstaller可执行文件错误1053

21

我用Python编写了一个Windows服务。如果我在命令提示符下运行脚本

python runService.py 
当我这样做时,服务已成功安装并启动。我一直在尝试使用pyinstaller创建可执行文件,因为我在使用py2exe时也遇到了同样的问题。当我运行.exe文件时,服务会安装但不会启动,并且我会收到以下错误信息。
error 1053 the service did not respond to the start or control request in a timely fashion 

我看到很多人都遇到了这个问题,但似乎找不到确定的答案来解决它。

winservice.py

from os.path import splitext, abspath
from sys import modules, executable
from time import *
import win32serviceutil
import win32service
import win32event
import win32api

class Service(win32serviceutil.ServiceFramework):
    _svc_name_ = '_unNamed'
    _svc_display_name_ = '_Service Template'
    _svc_description_ = '_Description template'
    def __init__(self, *args):
        win32serviceutil.ServiceFramework.__init__(self, *args)
        self.log('init')
        self.stop_event = win32event.CreateEvent(None, 0, 0, None)

    #logs into the system event log
def log(self, msg):
    import servicemanager
    servicemanager.LogInfoMsg(str(msg))

def sleep(self, minute):
        win32api.Sleep((minute*1000), True)
def SvcDoRun(self):
    self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
    try:
        self.ReportServiceStatus(win32service.SERVICE_RUNNING)
        self.log('start')
        self.start()
        self.log('wait')
        win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE)
        self.log('done')
    except Exception, x:
        self.log('Exception : %s' % x)
        self.SvcStop()
def SvcStop(self):
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
    #self.log('stopping')
    self.stop()
    #self.log('stopped')
    win32event.SetEvent(self.stop_event)
    self.ReportServiceStatus(win32service.SERVICE_STOPPED)
# to be overridden
def start(self): pass
# to be overridden
def stop(self): pass

def instart(cls, name, description, display_name=None, stay_alive=True):
    ''' Install and  Start (auto) a Service

        cls : the class (derived from Service) that implement the Service
        name : Service name
        display_name : the name displayed in the service manager
        decription: the description 
        stay_alive : Service will stop on logout if False
    '''
    cls._svc_name_ = name
    cls._svc_display_name_ = display_name or name
    cls._svc_desciption_ = description
    try:
        module_path=modules[cls.__module__].__file__
    except AttributeError:

        module_path=executable
    module_file = splitext(abspath(module_path))[0]
    cls._svc_reg_class_ = '%s.%s' % (module_file, cls.__name__)
    if stay_alive: win32api.SetConsoleCtrlHandler(lambda x: True, True)
    try:
        win32serviceutil.InstallService(
            cls._svc_reg_class_,
            cls._svc_name_,
            cls._svc_display_name_,
            startType = win32service.SERVICE_AUTO_START,
            description = cls._svc_desciption_
        )
        print 'Install ok'
        win32serviceutil.StartService(
        cls._svc_name_
    )
    print 'Start ok'
except Exception, x:
    print str(x)

更新

我通过使用py2exe解决了这个问题,但是同样的更改可能也适用于pyinstaller。我还没有时间自己验证。

我不得不删除instart函数。以下是我的winservice.py现在的读取方式。

winservice_py2exe.py

from os.path import splitext, abspath
from sys import modules, executable
from time import *
import win32serviceutil
import win32service
import win32event
import win32api

class Service(win32serviceutil.ServiceFramework):
    _svc_name_ = 'actualServiceName' #here is now the name you would input as an arg for instart
    _svc_display_name_ = 'actualDisplayName' #arg for instart
    _svc_description_ = 'actualDescription'# arg from instart
    def __init__(self, *args):
        win32serviceutil.ServiceFramework.__init__(self, *args)
        self.log('init')
        self.stop_event = win32event.CreateEvent(None, 0, 0, None)

    #logs into the system event log
def log(self, msg):
    import servicemanager
    servicemanager.LogInfoMsg(str(msg))

def sleep(self, minute):
        win32api.Sleep((minute*1000), True)
def SvcDoRun(self):
    self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
    try:
        self.ReportServiceStatus(win32service.SERVICE_RUNNING)
        self.log('start')
        self.start()
        self.log('wait')
        win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE)
        self.log('done')
    except Exception, x:
        self.log('Exception : %s' % x)
        self.SvcStop()
def SvcStop(self):
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
    #self.log('stopping')
    self.stop()
    #self.log('stopped')
    win32event.SetEvent(self.stop_event)
    self.ReportServiceStatus(win32service.SERVICE_STOPPED)
# to be overridden
def start(self): pass
# to be overridden
def stop(self): pass

if __name__ == '__main__':
     # Note that this code will not be run in the 'frozen' exe-file!!!
     win32serviceutil.HandleCommandLine(VidiagService) #added from example included with py2exe

以下是我在使用py2exe时所用的setup.py文件。这是从py2exe安装包中附带的示例中提取的:

setup.py

from distutils.core import setup
import py2exe
import sys
if len(sys.argv) == 1:
    sys.argv.append("py2exe")
    sys.argv.append("-q")

class Target:
    def __init__(self, **kw):
    self.__dict__.update(kw)
     # for the versioninfo resources
     self.version = "0.5.0"
     self.company_name = "No Company"
     self.copyright = "no copyright"
     self.name = "py2exe sample files"

 myservice = Target(
     # used for the versioninfo resource
     description = "A sample Windows NT service",
     # what to build.  For a service, the module name (not the
     # filename) must be specified!
     modules = ["winservice_py2exe"]
     )

 setup(
     options = {"py2exe": {"typelibs":
                      # typelib for WMI
                       [('{565783C6-CB41-11D1-8B02-00600806D9B6}', 0, 1, 2)],
                       # create a compressed zip archive
                       "compressed": 1,
                       "optimize": 2}},
     # The lib directory contains everything except the executables and the python dll.
     # Can include a subdirectory name.
     zipfile = "lib/shared.zip",

     service = [myservice]
    )

创建exe文件后,您可以使用以下命令从命令行安装服务:

command

winservice_py2exe.exe -install

然后启动服务,您可以使用以下命令:

 net start aTest

或从Windows服务管理器中启动。现在,所有其他Windows命令行功能都可以在服务上以及从Windows服务管理器中工作。


setup.py 的作用是什么? - programmer
当我输入安装命令时,出现错误提示“无法执行脚本”。 - programmer
3个回答

26

尝试将最后几行更改为:

if __name__ == '__main__':
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(Service)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(Service)

1
这对我使用pyinstaller有效。但是为什么作为Python文件安装时没有if也能工作? - enthus1ast
因为PythonService.exe负责处理那个。 - MrTorture
1
当我使用pyinstaller时,服务启动后立即退出。查看代码,我期望servicemanager.StartServiceCtrlDispatcher()是一个阻塞调用并保持进程运行,但似乎并非如此。 - Icode4food
已为我解决了!谢谢! - std''OrgnlDave
@MrTorture 当我输入安装命令时,出现了“无法执行脚本”的错误? - programmer

6

MrTorture已经提供了这个答案的关键,但我想在此基础上进一步完善。需要注意的是,即使在使用win32serviceutil函数以编程方式管理服务(而不是通过命令提示符安装、启动等),您也必须包含入口点命令行调度,才能使其在独立环境中工作(例如在使用pyinstaller或py2exe构建可执行文件时)。如果没有,Windows将无法启动该服务,您将会遇到1053错误!

此外,如果您正在创建一个较大项目的一部分,那么您将需要构建一个专门用于服务的exe。您不能将其作为较大exe的子组件运行(至少我试过没有成功!)。我包含了我的安装函数来证明这一点。

同样地,当使用.py脚本通过pythonservice.exe进行管理时,这些问题并不存在,这只是独立exe的问题。

以下是我的功能代码的不完整片段,但它们可能会帮助您省去很多麻烦:

SUCCESS = winerror.ERROR_SUCCESS
FAILURE = -1

class WinServiceManager():  

    # pass the class, not an instance of it!
    def __init__( self, serviceClass, serviceExeName=None ):
        self.serviceClass_ = serviceClass
        # Added for pyInstaller v3
        self.serviceExeName_ = serviceExeName

    def isStandAloneContext( self ) : 
        # Changed for pyInstaller v3
        #return sys.argv[0].endswith( ".exe" ) 
        return not( sys.argv[0].endswith( ".py" ) )

    def dispatch( self ):
        if self.isStandAloneContext() :
            servicemanager.Initialize()
            servicemanager.PrepareToHostSingle( self.serviceClass_ )
            servicemanager.Initialize( self.serviceClass_._svc_name_, 
                os.path.abspath( servicemanager.__file__ ) )
            servicemanager.StartServiceCtrlDispatcher()        
        else :
            win32api.SetConsoleCtrlHandler(lambda x: True, True)  
            win32serviceutil.HandleCommandLine( self.serviceClass_ )        

    # Service management functions
    #            
    # Note: all of these functions return:
    # SUCCESS when explicitly successful
    # FAILURE when explicitly not successful at their specific purpose
    # winerror.XXXXXX when win32service (or related class) 
    # throws an error of that nature
    #------------------------------------------------------------------------

    # Note: an "auto start" service is not auto started upon installation!
    # To install and start simultaneously, use start( autoInstall=True ).
    # That performs both actions for manual start services as well.
    def install( self ):
        win32api.SetConsoleCtrlHandler(lambda x: True, True)        
        result = self.verifyInstall()
        if result == SUCCESS or result != FAILURE: return result
        thisExePath = os.path.realpath( sys.argv[0] )
        thisExeDir  = os.path.dirname( thisExePath )        
        # Changed for pyInstaller v3 - which now incorrectly reports the calling exe
        # as the serviceModPath (v2 worked correctly!)
        if self.isStandAloneContext() :
            serviceModPath = self.serviceExeName_
        else :
            serviceModPath = sys.modules[ self.serviceClass_.__module__ ].__file__        
        serviceModPath = os.path.splitext(os.path.abspath( serviceModPath ))[0] 
        serviceClassPath = "%s.%s" % ( serviceModPath, self.serviceClass_.__name__ )
        self.serviceClass_._svc_reg_class_ = serviceClassPath
        # Note: in a "stand alone context", a dedicated service exe is expected 
        # within this directory (important for cases where a separate master exe 
        # is managing services).  
        serviceExePath = (serviceModPath + ".exe") if self.isStandAloneContext() else None        
        isAutoStart = self.serviceClass_._svc_is_auto_start_
        startOpt = (win32service.SERVICE_AUTO_START if isAutoStart else 
                    win32service.SERVICE_DEMAND_START)        
        try :      
            win32serviceutil.InstallService(
                pythonClassString = self.serviceClass_._svc_reg_class_,
                serviceName       = self.serviceClass_._svc_name_,
                displayName       = self.serviceClass_._svc_display_name_,
                description       = self.serviceClass_._svc_description_,
                exeName           = serviceExePath,
                startType         = startOpt
            ) 
        except win32service.error as e: return e[0]
        except Exception as e: raise e        
        win32serviceutil.SetServiceCustomOption( 
            self.serviceClass_._svc_name_, WORKING_DIR_OPT_NAME, thisExeDir )
        for i in range( 0, MAX_STATUS_CHANGE_CHECKS ) :
            result = self.verifyInstall()
            if result == SUCCESS: return SUCCESS
            time.sleep( STATUS_CHANGE_CHECK_DELAY )            
        return result         

在您定义服务的模块中(派生自win32serviceutil.ServiceFramework),请在其末尾包含以下内容:
if __name__ == "__main__":   
    WinServiceManager( MyServiceClass, "MyServiceBinary.exe" ).dispatch()

由于被调用以检索值的函数是 GetServiceCustomOption,这就解释了我为什么将常量命名为我所做的那样。 - BuvinJ
我意识到我还缺少另一个小的pyInstaller v3调整,其中exe名称被传递给了这个管理器类,所以我再次更新了我的帖子。整个过程真的很粗糙,但我一定在我的使用案例中需要它。这是另一个例子,说明为什么我的确切代码很难发布为完美的通用解决方案。 - BuvinJ
谢谢您!我会在接下来的几个小时内尝试,并告诉您结果如何。 - mafrosis
你能解释一下为什么整个 isStandAloneContext 的东西存在于那里吗? - mafrosis
让我们在聊天中继续这个讨论 - mafrosis
显示剩余7条评论

0
你可以使用这个例子:
import sys
import time

import win32serviceutil  # ServiceFramework and commandline helper
import win32service  # Events
import servicemanager  # Simple setup and logging


class MyService:
    """Silly little application stub"""

    def stop(self):
        """Stop the service"""
        self.running = False

    def run(self):
        """Main service loop. This is where work is done!"""
        self.running = True
        while self.running:
            time.sleep(10)  # Important work
            servicemanager.LogInfoMsg("Service running...")


class MyServiceFramework(win32serviceutil.ServiceFramework):
    _svc_name_ = 'MyService'
    _svc_display_name_ = 'My Service display name'

    def SvcStop(self):
        """Stop the service"""
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        self.service_impl.stop()
        self.ReportServiceStatus(win32service.SERVICE_STOPPED)

    def SvcDoRun(self):
        """Start the service; does not return until stopped"""
        self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
        self.service_impl = MyService()
        self.ReportServiceStatus(win32service.SERVICE_RUNNING)
        # Run the service
        self.service_impl.run()


def init():
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(MyServiceFramework)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(MyServiceFramework)


if __name__ == '__main__':
    init()
  • pyinstaller 命令:
pyinstaller --onefile --runtime-tmpdir=. --hidden-import win32timezone <your_file_name>.py

将.exe文件注册为Windows服务
<path_to_your_exe_file> install

之后你可以像使用Windows服务一样使用它。 这段代码对我来说完美地运行了。 来源:使用pywin32和PyInstaller在Python中创建一个单文件的Windows服务

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接