Python Windows service pyinstaller executables error 1053

Я написал службу Windows в python. Если я запустил script из командной строки

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)

UPDATE

Я решил эту проблему, используя 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

Ниже приведен файл setup.py, который используется с py2exe. Это было взято из примера, включенного в установку 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 вы можете установить службу из команды, используя следующую команду

winservice_py2exe.exe -install

затем запустите службу, которую вы можете использовать:

 net start aTest

или из диспетчера служб Windows. Все остальные функции командной строки Windows теперь работают как с сервисом, так и с диспетчером служб Windows.

Ответы

Ответ 1

Попробуйте изменить последние несколько строк на

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

Ответ 2

У MrTorture был ключ к этому ответу, но я хотел бы остановиться на этом. Важно отметить, что даже при использовании функций win32serviceutil для управления услугой программно (с установкой, запуском и т.д. Через командную строку) вы должны включить отправку командной строки точки входа, чтобы это работало в автономном контексте (т.е. при создании exe с pyinstaller или py2exe). Если вы этого не сделаете, Windows не сможет запустить службу. Вы получите страшную ошибку 1053!

В дополнение к этому, обратите внимание, что если вы создаете сервис как часть более крупного проекта, вам нужно будет создать exe, предназначенный для службы. Вы не можете запустить его как дополнительный компонент в расширенном exe (по крайней мере, мне не повезло!). Я включил мою функцию установки, чтобы продемонстрировать это.

Опять же, при использовании сценариев .py, управляемых с помощью pythonservice.exe, ни одна из этих проблем не присутствует, это относится только к автономным exes.

Вот некоторые фрагменты INCOMPLETE моего функционального кода, но они могут сэкономить массу неприятностей:

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()