title: Windows Service written in python, not detecting shutdown events and stopping gracefully
body: <p>I've written a service for Windows XP+ in python using the <a href="http://timgolden.me.uk/python/win32_how_do_i/track-session-events.html" rel="noreferrer">With Extended Service Notifications</a> 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.</p>
  
  <p>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.</p>
  
  <p>Here's the code:</p>
  
  <pre><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)
  </code></pre>