python winapi service pywin32

Servicio de Windows escrito en python, que no detecta los eventos de apagado y se detiene con gracia



winapi service (1)

He escrito un servicio para Windows XP + en python usando el código de ejemplo Con notificaciones de servicio extendido . Funciona muy bien para detectar la pantalla de bloqueo de inicio / cierre de sesión del usuario y otros eventos. El problema es que nunca ejecuta eventos de apagado y detiene con gracia el servicio al reiniciar / apagar. Permanece activo a través de los eventos de inicio / cierre de sesión, y también se reinicia después del reinicio. Cualquier ayuda sería apreciada.

No quiero usar RegisterServiceCtrlHandlerEx y manejar las señales de la consola si puedo ayudarlo. Los servicios tienen esta funcionalidad incorporada, solo la he destrozado.

Aquí está el código:

from os.path import splitext, abspath from sys import modules import win32serviceutil import win32service import win32event import win32api import win32security import win32ts class Service(win32serviceutil.ServiceFramework): _svc_name_ = ''_unNamed'' _svc_display_name_ = ''_Service Template'' def __init__(self, *args): win32serviceutil.ServiceFramework.__init__(self, *args) self.log(''Initializing Service'') self.stop_event = win32event.CreateEvent(None, 0, 0, None) self.server = None def log(self, msg): import servicemanager servicemanager.LogInfoMsg(str(msg)) def logErr(self, msg): import servicemanager servicemanager.LogErrorMsg(str(msg)) def logWarn(self, msg): import servicemanager servicemanager.LogWarningMsg(str(msg)) def sleep(self, sec): win32api.Sleep(sec*1000, True) def GetAcceptedControls(self): # Accept SESSION_CHANGE control rc = win32serviceutil.ServiceFramework.GetAcceptedControls(self) rc |= win32service.SERVICE_ACCEPT_SESSIONCHANGE rc |= win32service.SERVICE_ACCEPT_SHUTDOWN return rc def GetUserInfo(self, sess_id): sessions = win32security.LsaEnumerateLogonSessions()[:-5] for sn in sessions: sn_info = win32security.LsaGetLogonSessionData(sn) if sn_info[''Session''] == sess_id: return sn_info def getUserSessionInfo(self, sess_id): msg = "" try: for key, val in self.GetUserInfo(sess_id).items(): msg += ''%s : %s/n''%(key, val) if key == "UserName": self.server.username = val except Exception, e: msg += ''%s''%e return msg # All extra events are sent via SvcOtherEx (SvcOther remains as a # function taking only the first args for backwards compatability) def SvcOtherEx(self, control, event_type, data): # This is only showing a few of the extra events - see the MSDN # docs for "HandlerEx callback" for more info. if control == win32service.SERVICE_CONTROL_SESSIONCHANGE: sess_id = data[0] msg = "" if event_type == 5: # logon msg = "Logon event: type=%s, sessionid=%s/n" % (event_type, sess_id) # user_token = win32ts.WTSQueryUserToken(int(sess_id)) self.server.status = 1 #logon event self.getUserSessionInfo(sess_id) self.sendHeartbeat() self.server.status = 2 #active user elif event_type == 6: # logoff msg = "Logoff event: type=%s, sessionid=%s/n" % (event_type, sess_id) self.server.status = 3 #logoff event self.sendHeartbeat() self.server.username = "" self.server.status = 0 #no user elif event_type == 7: # lock msg = "Lock event: type=%s, sessionid=%s/n" % (event_type, sess_id) self.server.status = 1 #logon event self.getUserSessionInfo(sess_id) self.sendHeartbeat() self.server.status = 2 #active user elif event_type == 8: # unlock self.server.status = 3 #logoff event self.server.username = "" self.sendHeartbeat() self.server.status = 0 #no user else: msg = "Other session event: type=%s, sessionid=%s/n" % (event_type, sess_id) # msg += self.getUserSessionInfo(sess_id) self.log(msg) # elif control == win32service.SERVICE_CONTROL_SHUTDOWN: # msg = "Server being shutdown..." # self.log(msg) def SvcDoRun(self): self.ReportServiceStatus(win32service.SERVICE_START_PENDING) try: self.ReportServiceStatus(win32service.SERVICE_RUNNING) self.log(''Starting Service'') self.start() self.log(''Waiting'') win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE) self.log(''Done'') except Exception, x: self.logErr(''Exception : %s'' % x) self.SvcStop() def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) self.log(''Stopping Service'') self.stop() self.log(''Stopped'') win32event.SetEvent(self.stop_event) self.ReportServiceStatus(win32service.SERVICE_STOPPED) def sendHeartbeat(self): # 0 = no user, standard beat (format: 0||hostname) # 1 = login event (format: 1|username|hostname) # 2 = active user session # 3 = logout event (format: 2|username|hostname) # 4 = Service exception (format 3||hostname) return self.server.sendHeartbeat(category=self.server.status, username=self.server.username) def start(self): pass # to be overridden def stop(self): pass # to be overridden #reboot/halt makes a different call than ''net stop mytestservice'' def SvcShutdown(self): msg = "Server being shutdown..." self.log(msg) self.SvcStop() def instart(cls, name, display_name=None, stay_alive=True, exe_name="caedmSAM.exe"): '''''' 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 stay_alive : Service will stop on logout if False '''''' cls._svc_name_ = name cls._svc_display_name_ = display_name or name cls._exe_name_ = exe_name cls._svc_description_ = "CAEDM SAM Server registration and montioring service" try: module_path=modules[cls.__module__].__file__ except AttributeError: # maybe py2exe went by from sys import executable 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 ) # print ''Install: OK'' win32serviceutil.StartService( cls._svc_name_ ) # print ''Start: OK'' except Exception, x: print str(x)


¿Has probado el PRESHUTDOWN? Por lo general, SHUTDOWN es demasiado tarde.

def GetAcceptedControls(self): # Accept SESSION_CHANGE control rc = win32serviceutil.ServiceFramework.GetAcceptedControls(self) rc |= win32service.SERVICE_ACCEPT_SESSIONCHANGE rc |= win32service.SERVICE_ACCEPT_SHUTDOWN rc |= win32service.SERVICE_ACCEPT_PRESHUTDOWN return r

Luego, en SvcOtherEx() intercepte el evento Pre Shutdown y solicite más tiempo.

if win32service.SERVICE_CONTROL_PRESHUTDOWN: self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING, waitHint=10000) win32event.SetEvent(self.stop_event)