Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows Service written in python, not detecting shutdown events and stopping gracefully

Tags:

I've written a service for Windows XP+ in python using the With Extended Service Notifications example code. It works great for detecting user logon/logoff lock screen, and other events. The problem is that it never executes shutdown events and gracefully stops the service upon reboot/shutdown. It stays alive through logon/logoff events as well as starting up again after the reboot. Any help would be appreciated.

I don't want to use RegisterServiceCtrlHandlerEx and handle console signals if I can help it--services have this functionality built in, I've just mangled it somehow.

Here's the code:

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)
like image 813
chisaipete Avatar asked Jan 09 '12 21:01

chisaipete


1 Answers

Did you try the PRESHUTDOWN? Usually SHUTDOWN is too late.

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

Then in SvcOtherEx() intercept the Pre Shutdown event and ask for more time.

if win32service.SERVICE_CONTROL_PRESHUTDOWN:
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING, waitHint=10000)
    win32event.SetEvent(self.stop_event)
like image 54
Dorian B. Avatar answered Oct 09 '22 05:10

Dorian B.