# General purpose service utilities, both for standard Python scripts, # and for for Python programs which run as services... # # Note that most utility functions here will raise win32api.error's # (which is win32service.error, pywintypes.error, etc) # when things go wrong - eg, not enough permissions to hit the # registry etc. import importlib import os import sys import warnings import pywintypes import win32api import win32con import win32service import winerror _d = "_d" if "_d.pyd" in importlib.machinery.EXTENSION_SUFFIXES else "" error = RuntimeError # Returns the full path to an executable for hosting a Python service - typically # 'pythonservice.exe' # * If you pass a param and it exists as a file, you'll get the abs path back # * Otherwise we'll use the param instead of 'pythonservice.exe', and we will # look for it. def LocatePythonServiceExe(exe=None): if not exe and hasattr(sys, "frozen"): # If py2exe etc calls this with no exe, default is current exe, # and all setup is their problem :) return sys.executable if exe and os.path.isfile(exe): return win32api.GetFullPathName(exe) # We are confused if we aren't now looking for our default. But if that # exists as specified we assume it's good. exe = f"pythonservice{_d}.exe" if os.path.isfile(exe): return win32api.GetFullPathName(exe) # Now we are searching for the .exe # We are going to want it here. correct = os.path.join(sys.exec_prefix, exe) # Even if that file already exists, we copy the one installed by pywin32 # in-case it was upgraded. # pywin32 installed it next to win32service.pyd (but we can't run it from there) maybe = os.path.join(os.path.dirname(win32service.__file__), exe) if os.path.exists(maybe): print(f"copying host exe '{maybe}' -> '{correct}'") win32api.CopyFile(maybe, correct) if not os.path.exists(correct): raise error(f"Can't find '{correct}'") # If pywintypes.dll isn't next to us, or at least next to pythonXX.dll, # there's a good chance the service will not run. That's usually copied by # `pywin32_postinstall`, but putting it next to the python DLL seems # reasonable. # (Unlike the .exe above, we don't unconditionally copy this, and possibly # copy it to a different place. Doesn't seem a good reason for that!?) python_dll = win32api.GetModuleFileName(sys.dllhandle) pyw = f"pywintypes{sys.version_info[0]}{sys.version_info[1]}{_d}.dll" correct_pyw = os.path.join(os.path.dirname(python_dll), pyw) if not os.path.exists(correct_pyw): print(f"copying helper dll '{pywintypes.__file__}' -> '{correct_pyw}'") win32api.CopyFile(pywintypes.__file__, correct_pyw) return correct def _GetServiceShortName(longName): # looks up a services name # from the display name # Thanks to Andy McKay for this code. access = ( win32con.KEY_READ | win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_QUERY_VALUE ) hkey = win32api.RegOpenKey( win32con.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services", 0, access ) num = win32api.RegQueryInfoKey(hkey)[0] longName = longName.lower() # loop through number of subkeys for x in range(0, num): # find service name, open subkey svc = win32api.RegEnumKey(hkey, x) skey = win32api.RegOpenKey(hkey, svc, 0, access) try: # find display name thisName = str(win32api.RegQueryValueEx(skey, "DisplayName")[0]) if thisName.lower() == longName: return svc except win32api.error: # in case there is no key called DisplayName pass return None # Open a service given either it's long or short name. def SmartOpenService(hscm, name, access): try: return win32service.OpenService(hscm, name, access) except win32api.error as details: if details.winerror not in [ winerror.ERROR_SERVICE_DOES_NOT_EXIST, winerror.ERROR_INVALID_NAME, ]: raise name = win32service.GetServiceKeyName(hscm, name) return win32service.OpenService(hscm, name, access) def LocateSpecificServiceExe(serviceName): # Return the .exe name of any service. hkey = win32api.RegOpenKey( win32con.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\%s" % (serviceName), 0, win32con.KEY_ALL_ACCESS, ) try: return win32api.RegQueryValueEx(hkey, "ImagePath")[0] finally: hkey.Close() def InstallPerfmonForService(serviceName, iniName, dllName=None): # If no DLL name, look it up in the INI file name if not dllName: # May be empty string! dllName = win32api.GetProfileVal("Python", "dll", "", iniName) # Still not found - look for the standard one in the same dir as win32service.pyd if not dllName: try: tryName = os.path.join( os.path.split(win32service.__file__)[0], "perfmondata.dll" ) if os.path.isfile(tryName): dllName = tryName except AttributeError: # Frozen app? - anyway, can't find it! pass if not dllName: raise ValueError("The name of the performance DLL must be available") dllName = win32api.GetFullPathName(dllName) # Now setup all the required "Performance" entries. hkey = win32api.RegOpenKey( win32con.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\%s" % (serviceName), 0, win32con.KEY_ALL_ACCESS, ) try: subKey = win32api.RegCreateKey(hkey, "Performance") try: win32api.RegSetValueEx(subKey, "Library", 0, win32con.REG_SZ, dllName) win32api.RegSetValueEx( subKey, "Open", 0, win32con.REG_SZ, "OpenPerformanceData" ) win32api.RegSetValueEx( subKey, "Close", 0, win32con.REG_SZ, "ClosePerformanceData" ) win32api.RegSetValueEx( subKey, "Collect", 0, win32con.REG_SZ, "CollectPerformanceData" ) finally: win32api.RegCloseKey(subKey) finally: win32api.RegCloseKey(hkey) # Now do the "Lodctr" thang... try: import perfmon path, fname = os.path.split(iniName) oldPath = os.getcwd() if path: os.chdir(path) try: perfmon.LoadPerfCounterTextStrings("python.exe " + fname) finally: os.chdir(oldPath) except win32api.error as details: print("The service was installed OK, but the performance monitor") print("data could not be loaded.", details) def _GetCommandLine(exeName, exeArgs): if exeArgs is not None: return exeName + " " + exeArgs else: return exeName def InstallService( pythonClassString, serviceName, displayName, startType=None, errorControl=None, bRunInteractive=0, serviceDeps=None, userName=None, password=None, exeName=None, perfMonIni=None, perfMonDll=None, exeArgs=None, description=None, delayedstart=None, ): # Handle the default arguments. if startType is None: startType = win32service.SERVICE_DEMAND_START serviceType = win32service.SERVICE_WIN32_OWN_PROCESS if bRunInteractive: serviceType = serviceType | win32service.SERVICE_INTERACTIVE_PROCESS if errorControl is None: errorControl = win32service.SERVICE_ERROR_NORMAL exeName = '"%s"' % LocatePythonServiceExe(exeName) commandLine = _GetCommandLine(exeName, exeArgs) hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS) try: hs = win32service.CreateService( hscm, serviceName, displayName, win32service.SERVICE_ALL_ACCESS, # desired access serviceType, # service type startType, errorControl, # error control type commandLine, None, 0, serviceDeps, userName, password, ) if description is not None: try: win32service.ChangeServiceConfig2( hs, win32service.SERVICE_CONFIG_DESCRIPTION, description ) except NotImplementedError: pass ## ChangeServiceConfig2 and description do not exist on NT if delayedstart is not None: try: win32service.ChangeServiceConfig2( hs, win32service.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, delayedstart, ) except (win32service.error, NotImplementedError): ## delayed start only exists on Vista and later - warn only when trying to set delayed to True warnings.warn("Delayed Start not available on this system") win32service.CloseServiceHandle(hs) finally: win32service.CloseServiceHandle(hscm) InstallPythonClassString(pythonClassString, serviceName) # If I have performance monitor info to install, do that. if perfMonIni is not None: InstallPerfmonForService(serviceName, perfMonIni, perfMonDll) def ChangeServiceConfig( pythonClassString, serviceName, startType=None, errorControl=None, bRunInteractive=0, serviceDeps=None, userName=None, password=None, exeName=None, displayName=None, perfMonIni=None, perfMonDll=None, exeArgs=None, description=None, delayedstart=None, ): # Before doing anything, remove any perfmon counters. try: import perfmon perfmon.UnloadPerfCounterTextStrings("python.exe " + serviceName) except (ImportError, win32api.error): pass # The EXE location may have changed exeName = '"%s"' % LocatePythonServiceExe(exeName) # Handle the default arguments. if startType is None: startType = win32service.SERVICE_NO_CHANGE if errorControl is None: errorControl = win32service.SERVICE_NO_CHANGE hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS) serviceType = win32service.SERVICE_WIN32_OWN_PROCESS if bRunInteractive: serviceType = serviceType | win32service.SERVICE_INTERACTIVE_PROCESS commandLine = _GetCommandLine(exeName, exeArgs) try: hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS) try: win32service.ChangeServiceConfig( hs, serviceType, # service type startType, errorControl, # error control type commandLine, None, 0, serviceDeps, userName, password, displayName, ) if description is not None: try: win32service.ChangeServiceConfig2( hs, win32service.SERVICE_CONFIG_DESCRIPTION, description ) except NotImplementedError: pass ## ChangeServiceConfig2 and description do not exist on NT if delayedstart is not None: try: win32service.ChangeServiceConfig2( hs, win32service.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, delayedstart, ) except (win32service.error, NotImplementedError): ## Delayed start only exists on Vista and later. On Nt, will raise NotImplementedError since ChangeServiceConfig2 ## doensn't exist. On Win2k and XP, will fail with ERROR_INVALID_LEVEL ## Warn only if trying to set delayed to True if delayedstart: warnings.warn("Delayed Start not available on this system") finally: win32service.CloseServiceHandle(hs) finally: win32service.CloseServiceHandle(hscm) InstallPythonClassString(pythonClassString, serviceName) # If I have performance monitor info to install, do that. if perfMonIni is not None: InstallPerfmonForService(serviceName, perfMonIni, perfMonDll) def InstallPythonClassString(pythonClassString, serviceName): # Now setup our Python specific entries. if pythonClassString: key = win32api.RegCreateKey( win32con.HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\%s\\PythonClass" % serviceName, ) try: win32api.RegSetValue(key, None, win32con.REG_SZ, pythonClassString) finally: win32api.RegCloseKey(key) # Utility functions for Services, to allow persistant properties. def SetServiceCustomOption(serviceName, option, value): try: serviceName = serviceName._svc_name_ except AttributeError: pass key = win32api.RegCreateKey( win32con.HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\%s\\Parameters" % serviceName, ) try: if type(value) == type(0): win32api.RegSetValueEx(key, option, 0, win32con.REG_DWORD, value) else: win32api.RegSetValueEx(key, option, 0, win32con.REG_SZ, value) finally: win32api.RegCloseKey(key) def GetServiceCustomOption(serviceName, option, defaultValue=None): # First param may also be a service class/instance. # This allows services to pass "self" try: serviceName = serviceName._svc_name_ except AttributeError: pass key = win32api.RegCreateKey( win32con.HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\%s\\Parameters" % serviceName, ) try: try: return win32api.RegQueryValueEx(key, option)[0] except win32api.error: # No value. return defaultValue finally: win32api.RegCloseKey(key) def RemoveService(serviceName): try: import perfmon perfmon.UnloadPerfCounterTextStrings("python.exe " + serviceName) except (ImportError, win32api.error): pass hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS) try: hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS) win32service.DeleteService(hs) win32service.CloseServiceHandle(hs) finally: win32service.CloseServiceHandle(hscm) import win32evtlogutil try: win32evtlogutil.RemoveSourceFromRegistry(serviceName) except win32api.error: pass def ControlService(serviceName, code, machine=None): hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_ALL_ACCESS) try: hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS) try: status = win32service.ControlService(hs, code) finally: win32service.CloseServiceHandle(hs) finally: win32service.CloseServiceHandle(hscm) return status def __FindSvcDeps(findName): if type(findName) is pywintypes.UnicodeType: findName = str(findName) dict = {} k = win32api.RegOpenKey( win32con.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services" ) num = 0 while 1: try: svc = win32api.RegEnumKey(k, num) except win32api.error: break num = num + 1 sk = win32api.RegOpenKey(k, svc) try: deps, typ = win32api.RegQueryValueEx(sk, "DependOnService") except win32api.error: deps = () for dep in deps: dep = dep.lower() dep_on = dict.get(dep, []) dep_on.append(svc) dict[dep] = dep_on return __ResolveDeps(findName, dict) def __ResolveDeps(findName, dict): items = dict.get(findName.lower(), []) retList = [] for svc in items: retList.insert(0, svc) retList = __ResolveDeps(svc, dict) + retList return retList def WaitForServiceStatus(serviceName, status, waitSecs, machine=None): """Waits for the service to return the specified status. You should have already requested the service to enter that state""" for i in range(waitSecs * 4): now_status = QueryServiceStatus(serviceName, machine)[1] if now_status == status: break win32api.Sleep(250) else: raise pywintypes.error( winerror.ERROR_SERVICE_REQUEST_TIMEOUT, "QueryServiceStatus", win32api.FormatMessage(winerror.ERROR_SERVICE_REQUEST_TIMEOUT)[:-2], ) def __StopServiceWithTimeout(hs, waitSecs=30): try: status = win32service.ControlService(hs, win32service.SERVICE_CONTROL_STOP) except pywintypes.error as exc: if exc.winerror != winerror.ERROR_SERVICE_NOT_ACTIVE: raise for i in range(waitSecs): status = win32service.QueryServiceStatus(hs) if status[1] == win32service.SERVICE_STOPPED: break win32api.Sleep(1000) else: raise pywintypes.error( winerror.ERROR_SERVICE_REQUEST_TIMEOUT, "ControlService", win32api.FormatMessage(winerror.ERROR_SERVICE_REQUEST_TIMEOUT)[:-2], ) def StopServiceWithDeps(serviceName, machine=None, waitSecs=30): # Stop a service recursively looking for dependant services hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_ALL_ACCESS) try: deps = __FindSvcDeps(serviceName) for dep in deps: hs = win32service.OpenService(hscm, dep, win32service.SERVICE_ALL_ACCESS) try: __StopServiceWithTimeout(hs, waitSecs) finally: win32service.CloseServiceHandle(hs) # Now my service! hs = win32service.OpenService( hscm, serviceName, win32service.SERVICE_ALL_ACCESS ) try: __StopServiceWithTimeout(hs, waitSecs) finally: win32service.CloseServiceHandle(hs) finally: win32service.CloseServiceHandle(hscm) def StopService(serviceName, machine=None): return ControlService(serviceName, win32service.SERVICE_CONTROL_STOP, machine) def StartService(serviceName, args=None, machine=None): hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_ALL_ACCESS) try: hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS) try: win32service.StartService(hs, args) finally: win32service.CloseServiceHandle(hs) finally: win32service.CloseServiceHandle(hscm) def RestartService(serviceName, args=None, waitSeconds=30, machine=None): "Stop the service, and then start it again (with some tolerance for allowing it to stop.)" try: StopService(serviceName, machine) except pywintypes.error as exc: # Allow only "service not running" error if exc.winerror != winerror.ERROR_SERVICE_NOT_ACTIVE: raise # Give it a few goes, as the service may take time to stop for i in range(waitSeconds): try: StartService(serviceName, args, machine) break except pywintypes.error as exc: if exc.winerror != winerror.ERROR_SERVICE_ALREADY_RUNNING: raise win32api.Sleep(1000) else: print("Gave up waiting for the old service to stop!") def _DebugCtrlHandler(evt): if evt in (win32con.CTRL_C_EVENT, win32con.CTRL_BREAK_EVENT): assert g_debugService print("Stopping debug service.") g_debugService.SvcStop() return True return False def DebugService(cls, argv=[]): # Run a service in "debug" mode. Re-implements what pythonservice.exe # does when it sees a "-debug" param. # Currently only used by "frozen" (ie, py2exe) programs (but later may # end up being used for all services should we ever remove # pythonservice.exe) import servicemanager global g_debugService print("Debugging service %s - press Ctrl+C to stop." % (cls._svc_name_,)) servicemanager.Debugging(True) servicemanager.PrepareToHostSingle(cls) g_debugService = cls(argv) # Setup a ctrl+c handler to simulate a "stop" win32api.SetConsoleCtrlHandler(_DebugCtrlHandler, True) try: g_debugService.SvcRun() finally: win32api.SetConsoleCtrlHandler(_DebugCtrlHandler, False) servicemanager.Debugging(False) g_debugService = None def GetServiceClassString(cls, argv=None): if argv is None: argv = sys.argv import pickle modName = pickle.whichmodule(cls, cls.__name__) if modName == "__main__": try: fname = win32api.GetFullPathName(argv[0]) path = os.path.split(fname)[0] # Eaaaahhhh - sometimes this will be a short filename, which causes # problems with 1.5.1 and the silly filename case rule. filelist = win32api.FindFiles(fname) # win32api.FindFiles will not detect files in a zip or exe. If list is empty, # skip the test and hope the file really exists. if len(filelist) != 0: # Get the long name fname = os.path.join(path, filelist[0][8]) except win32api.error: raise error( "Could not resolve the path name '%s' to a full path" % (argv[0]) ) modName = os.path.splitext(fname)[0] return modName + "." + cls.__name__ def QueryServiceStatus(serviceName, machine=None): hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_CONNECT) try: hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_QUERY_STATUS) try: status = win32service.QueryServiceStatus(hs) finally: win32service.CloseServiceHandle(hs) finally: win32service.CloseServiceHandle(hscm) return status def usage(): try: fname = os.path.split(sys.argv[0])[1] except: fname = sys.argv[0] print( "Usage: '%s [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'" % fname ) print("Options for 'install' and 'update' commands only:") print(" --username domain\\username : The Username the service is to run under") print(" --password password : The password for the username") print( " --startup [manual|auto|disabled|delayed] : How the service starts, default = manual" ) print(" --interactive : Allow the service to interact with the desktop.") print( " --perfmonini file: .ini file to use for registering performance monitor data" ) print(" --perfmondll file: .dll file to use when querying the service for") print(" performance data, default = perfmondata.dll") print("Options for 'start' and 'stop' commands only:") print(" --wait seconds: Wait for the service to actually start or stop.") print(" If you specify --wait with the 'stop' option, the service") print(" and all dependent services will be stopped, each waiting") print(" the specified period.") sys.exit(1) def HandleCommandLine( cls, serviceClassString=None, argv=None, customInstallOptions="", customOptionHandler=None, ): """Utility function allowing services to process the command line. Allows standard commands such as 'start', 'stop', 'debug', 'install' etc. Install supports 'standard' command line options prefixed with '--', such as --username, --password, etc. In addition, the function allows custom command line options to be handled by the calling function. """ err = 0 if argv is None: argv = sys.argv if len(argv) <= 1: usage() serviceName = cls._svc_name_ serviceDisplayName = cls._svc_display_name_ if serviceClassString is None: serviceClassString = GetServiceClassString(cls) # Pull apart the command line import getopt try: opts, args = getopt.getopt( argv[1:], customInstallOptions, [ "password=", "username=", "startup=", "perfmonini=", "perfmondll=", "interactive", "wait=", ], ) except getopt.error as details: print(details) usage() userName = None password = None perfMonIni = perfMonDll = None startup = None delayedstart = None interactive = None waitSecs = 0 for opt, val in opts: if opt == "--username": userName = val elif opt == "--password": password = val elif opt == "--perfmonini": perfMonIni = val elif opt == "--perfmondll": perfMonDll = val elif opt == "--interactive": interactive = 1 elif opt == "--startup": map = { "manual": win32service.SERVICE_DEMAND_START, "auto": win32service.SERVICE_AUTO_START, "delayed": win32service.SERVICE_AUTO_START, ## ChangeServiceConfig2 called later "disabled": win32service.SERVICE_DISABLED, } try: startup = map[val.lower()] except KeyError: print("'%s' is not a valid startup option" % val) if val.lower() == "delayed": delayedstart = True elif val.lower() == "auto": delayedstart = False ## else no change elif opt == "--wait": try: waitSecs = int(val) except ValueError: print("--wait must specify an integer number of seconds.") usage() arg = args[0] knownArg = 0 # First we process all arguments which pass additional args on if arg == "start": knownArg = 1 print("Starting service %s" % (serviceName)) try: StartService(serviceName, args[1:]) if waitSecs: WaitForServiceStatus( serviceName, win32service.SERVICE_RUNNING, waitSecs ) except win32service.error as exc: print("Error starting service: %s" % exc.strerror) err = exc.winerror elif arg == "restart": knownArg = 1 print("Restarting service %s" % (serviceName)) RestartService(serviceName, args[1:]) if waitSecs: WaitForServiceStatus(serviceName, win32service.SERVICE_RUNNING, waitSecs) elif arg == "debug": knownArg = 1 if not hasattr(sys, "frozen"): # non-frozen services use pythonservice.exe which handles a # -debug option svcArgs = " ".join(args[1:]) try: exeName = LocateSpecificServiceExe(serviceName) except win32api.error as exc: if exc.winerror == winerror.ERROR_FILE_NOT_FOUND: print("The service does not appear to be installed.") print("Please install the service before debugging it.") sys.exit(1) raise try: os.system("%s -debug %s %s" % (exeName, serviceName, svcArgs)) # ^C is used to kill the debug service. Sometimes Python also gets # interrupted - ignore it... except KeyboardInterrupt: pass else: # py2exe services don't use pythonservice - so we simulate # debugging here. DebugService(cls, args) if not knownArg and len(args) != 1: usage() # the rest of the cmds don't take addn args if arg == "install": knownArg = 1 try: serviceDeps = cls._svc_deps_ except AttributeError: serviceDeps = None try: exeName = cls._exe_name_ except AttributeError: exeName = None # Default to PythonService.exe try: exeArgs = cls._exe_args_ except AttributeError: exeArgs = None try: description = cls._svc_description_ except AttributeError: description = None print("Installing service %s" % (serviceName,)) # Note that we install the service before calling the custom option # handler, so if the custom handler fails, we have an installed service (from NT's POV) # but is unlikely to work, as the Python code controlling it failed. Therefore # we remove the service if the first bit works, but the second doesnt! try: InstallService( serviceClassString, serviceName, serviceDisplayName, serviceDeps=serviceDeps, startType=startup, bRunInteractive=interactive, userName=userName, password=password, exeName=exeName, perfMonIni=perfMonIni, perfMonDll=perfMonDll, exeArgs=exeArgs, description=description, delayedstart=delayedstart, ) if customOptionHandler: customOptionHandler(*(opts,)) print("Service installed") except win32service.error as exc: if exc.winerror == winerror.ERROR_SERVICE_EXISTS: arg = "update" # Fall through to the "update" param! else: print( "Error installing service: %s (%d)" % (exc.strerror, exc.winerror) ) err = exc.winerror except ValueError as msg: # Can be raised by custom option handler. print("Error installing service: %s" % str(msg)) err = -1 # xxx - maybe I should remove after _any_ failed install - however, # xxx - it may be useful to help debug to leave the service as it failed. # xxx - We really _must_ remove as per the comments above... # As we failed here, remove the service, so the next installation # attempt works. try: RemoveService(serviceName) except win32api.error: print("Warning - could not remove the partially installed service.") if arg == "update": knownArg = 1 try: serviceDeps = cls._svc_deps_ except AttributeError: serviceDeps = None try: exeName = cls._exe_name_ except AttributeError: exeName = None # Default to PythonService.exe try: exeArgs = cls._exe_args_ except AttributeError: exeArgs = None try: description = cls._svc_description_ except AttributeError: description = None print("Changing service configuration") try: ChangeServiceConfig( serviceClassString, serviceName, serviceDeps=serviceDeps, startType=startup, bRunInteractive=interactive, userName=userName, password=password, exeName=exeName, displayName=serviceDisplayName, perfMonIni=perfMonIni, perfMonDll=perfMonDll, exeArgs=exeArgs, description=description, delayedstart=delayedstart, ) if customOptionHandler: customOptionHandler(*(opts,)) print("Service updated") except win32service.error as exc: print( "Error changing service configuration: %s (%d)" % (exc.strerror, exc.winerror) ) err = exc.winerror elif arg == "remove": knownArg = 1 print("Removing service %s" % (serviceName)) try: RemoveService(serviceName) print("Service removed") except win32service.error as exc: print("Error removing service: %s (%d)" % (exc.strerror, exc.winerror)) err = exc.winerror elif arg == "stop": knownArg = 1 print("Stopping service %s" % (serviceName)) try: if waitSecs: StopServiceWithDeps(serviceName, waitSecs=waitSecs) else: StopService(serviceName) except win32service.error as exc: print("Error stopping service: %s (%d)" % (exc.strerror, exc.winerror)) err = exc.winerror if not knownArg: err = -1 print("Unknown command - '%s'" % arg) usage() return err # # Useful base class to build services from. # class ServiceFramework: # Required Attributes: # _svc_name_ = The service name # _svc_display_name_ = The service display name # Optional Attributes: _svc_deps_ = None # sequence of service names on which this depends _exe_name_ = None # Default to PythonService.exe _exe_args_ = None # Default to no arguments _svc_description_ = ( None # Only exists on Windows 2000 or later, ignored on windows NT ) def __init__(self, args): import servicemanager self.ssh = servicemanager.RegisterServiceCtrlHandler( args[0], self.ServiceCtrlHandlerEx, True ) servicemanager.SetEventSourceName(self._svc_name_) self.checkPoint = 0 def GetAcceptedControls(self): # Setup the service controls we accept based on our attributes. Note # that if you need to handle controls via SvcOther[Ex](), you must # override this. accepted = 0 if hasattr(self, "SvcStop"): accepted = accepted | win32service.SERVICE_ACCEPT_STOP if hasattr(self, "SvcPause") and hasattr(self, "SvcContinue"): accepted = accepted | win32service.SERVICE_ACCEPT_PAUSE_CONTINUE if hasattr(self, "SvcShutdown"): accepted = accepted | win32service.SERVICE_ACCEPT_SHUTDOWN return accepted def ReportServiceStatus( self, serviceStatus, waitHint=5000, win32ExitCode=0, svcExitCode=0 ): if self.ssh is None: # Debugging! return if serviceStatus == win32service.SERVICE_START_PENDING: accepted = 0 else: accepted = self.GetAcceptedControls() if serviceStatus in [ win32service.SERVICE_RUNNING, win32service.SERVICE_STOPPED, ]: checkPoint = 0 else: self.checkPoint = self.checkPoint + 1 checkPoint = self.checkPoint # Now report the status to the control manager status = ( win32service.SERVICE_WIN32_OWN_PROCESS, serviceStatus, accepted, # dwControlsAccepted, win32ExitCode, # dwWin32ExitCode; svcExitCode, # dwServiceSpecificExitCode; checkPoint, # dwCheckPoint; waitHint, ) win32service.SetServiceStatus(self.ssh, status) def SvcInterrogate(self): # Assume we are running, and everyone is happy. self.ReportServiceStatus(win32service.SERVICE_RUNNING) def SvcOther(self, control): try: print("Unknown control status - %d" % control) except IOError: # services may not have a valid stdout! pass def ServiceCtrlHandler(self, control): return self.ServiceCtrlHandlerEx(control, 0, None) # The 'Ex' functions, which take additional params def SvcOtherEx(self, control, event_type, data): # The default here is to call self.SvcOther as that is the old behaviour. # If you want to take advantage of the extra data, override this method return self.SvcOther(control) def ServiceCtrlHandlerEx(self, control, event_type, data): if control == win32service.SERVICE_CONTROL_STOP: return self.SvcStop() elif control == win32service.SERVICE_CONTROL_PAUSE: return self.SvcPause() elif control == win32service.SERVICE_CONTROL_CONTINUE: return self.SvcContinue() elif control == win32service.SERVICE_CONTROL_INTERROGATE: return self.SvcInterrogate() elif control == win32service.SERVICE_CONTROL_SHUTDOWN: return self.SvcShutdown() else: return self.SvcOtherEx(control, event_type, data) def SvcRun(self): # This is the entry point the C framework calls when the Service is # started. Your Service class should implement SvcDoRun(). # Or you can override this method for more control over the Service # statuses reported to the SCM. # If this method raises an exception, the C framework will detect this # and report a SERVICE_STOPPED status with a non-zero error code. self.ReportServiceStatus(win32service.SERVICE_RUNNING) self.SvcDoRun() # Once SvcDoRun terminates, the service has stopped. # We tell the SCM the service is still stopping - the C framework # will automatically tell the SCM it has stopped when this returns. self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)