Python servicio de Windows pyinstaller ejecutables error 1053
service py2exe (2)
He escrito un servicio de Windows en Python. Si ejecuto mi script desde el símbolo del sistema
python runService.py
Cuando hago esto el servicio se instala y se inicia correctamente. He estado intentando crear un ejecutable utilizando pyinstaller porque he visto el mismo problema con py2exe. Cuando ejecuto el archivo .exe, el servicio se instala pero no se inicia y aparece el siguiente error
error 1053 the service did not respond to the start or control request in a timely fashion
He visto que muchas personas han tenido este problema, pero parece que no puedo encontrar una respuesta definitiva sobre cómo solucionar este problema.
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)
ACTUALIZAR
Resolví este problema usando py2exe pero los mismos cambios también pueden funcionar para pyinstaller. No he tenido tiempo de comprobarlo yo mismo.
Tuve que quitar la función instart . A continuación es cómo se lee mi winservice.py ahora.
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
A continuación se muestra el archivo setup.py que usé con py2exe. Esto se tomó del ejemplo incluido con la instalación de 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]
)
Una vez que haya creado el archivo ejecutable, puede instalar el servicio desde el comando utilizando el siguiente comando
winservice_py2exe.exe -install
Luego para iniciar el servicio puedes usar:
net start aTest
o desde el administrador de servicios de Windows. Todas las demás funciones de la línea de comandos de Windows ahora funcionan tanto en el servicio como en el administrador de servicios de Windows.
Intenta cambiar las últimas líneas para
if __name__ == ''__main__'':
if len(sys.argv) == 1:
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(Service)
servicemanager.StartServiceCtrlDispatcher()
else:
win32serviceutil.HandleCommandLine(Service)
MrTorture tenía la clave para esta respuesta, pero me gustaría continuar con eso aquí. Algo crítico que se debe tener en cuenta es que incluso al usar las funciones win32serviceutil para administrar el servicio mediante programación (vs instalación, inicio, etc. a través del símbolo del sistema), debe incluir el envío de línea de comando del punto de entrada para que esto funcione en un contexto independiente (es decir, al construir un exe con pyinstaller o py2exe). Si no lo hace, Windows no podrá iniciar el servicio. Obtendrás el temido error 1053!
Además de eso, tenga en cuenta que si está creando un servicio como parte de un proyecto más grande, tendrá que crear un exe dedicado al servicio. No puedes ejecutarlo como un subcomponente dentro de un exe más grande (¡al menos no tuve suerte al intentarlo!). He incluido mi función de instalación para demostrar eso.
Nuevamente, cuando se usan scripts .py, administrados a través de pythonservice.exe, ninguno de estos problemas está presente, solo son preocupaciones para los exes independientes.
Aquí hay algunos fragmentos INCOMPLETOS de mi código funcional, pero pueden ahorrarle muchos problemas:
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
En el módulo donde define su servicio (derivado de win32serviceutil.ServiceFramework), incluya esto al final de este:
if __name__ == "__main__":
WinServiceManager( MyServiceClass, "MyServiceBinary.exe" ).dispatch()