20

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)
chisaipete
  • 884
  • 9
  • 20
  • 1
    Normally a service can be signaled whenever it is being terminated, do you not receive this event? This would occur if the user is manually terminating the service or Windows is shutting down. – Martin Evans Jul 08 '15 at 07:56

1 Answers1

4

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)
Dorian B.
  • 1,101
  • 13
  • 22
  • Argh, I lost so much time on your code because I misunderstood it at one point. If you want to diferenciate Sessionchange(logoff) and Shutdown you should check "control" value : if control is win32service.SERVICE_CONTROL_SESSIONCHANGE – Nicos44k Feb 15 '22 at 15:57