|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071 |
- # 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)
|