Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

win32serviceutil.py 37KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071
  1. # General purpose service utilities, both for standard Python scripts,
  2. # and for for Python programs which run as services...
  3. #
  4. # Note that most utility functions here will raise win32api.error's
  5. # (which is win32service.error, pywintypes.error, etc)
  6. # when things go wrong - eg, not enough permissions to hit the
  7. # registry etc.
  8. import importlib
  9. import os
  10. import sys
  11. import warnings
  12. import pywintypes
  13. import win32api
  14. import win32con
  15. import win32service
  16. import winerror
  17. _d = "_d" if "_d.pyd" in importlib.machinery.EXTENSION_SUFFIXES else ""
  18. error = RuntimeError
  19. # Returns the full path to an executable for hosting a Python service - typically
  20. # 'pythonservice.exe'
  21. # * If you pass a param and it exists as a file, you'll get the abs path back
  22. # * Otherwise we'll use the param instead of 'pythonservice.exe', and we will
  23. # look for it.
  24. def LocatePythonServiceExe(exe=None):
  25. if not exe and hasattr(sys, "frozen"):
  26. # If py2exe etc calls this with no exe, default is current exe,
  27. # and all setup is their problem :)
  28. return sys.executable
  29. if exe and os.path.isfile(exe):
  30. return win32api.GetFullPathName(exe)
  31. # We are confused if we aren't now looking for our default. But if that
  32. # exists as specified we assume it's good.
  33. exe = f"pythonservice{_d}.exe"
  34. if os.path.isfile(exe):
  35. return win32api.GetFullPathName(exe)
  36. # Now we are searching for the .exe
  37. # We are going to want it here.
  38. correct = os.path.join(sys.exec_prefix, exe)
  39. # Even if that file already exists, we copy the one installed by pywin32
  40. # in-case it was upgraded.
  41. # pywin32 installed it next to win32service.pyd (but we can't run it from there)
  42. maybe = os.path.join(os.path.dirname(win32service.__file__), exe)
  43. if os.path.exists(maybe):
  44. print(f"copying host exe '{maybe}' -> '{correct}'")
  45. win32api.CopyFile(maybe, correct)
  46. if not os.path.exists(correct):
  47. raise error(f"Can't find '{correct}'")
  48. # If pywintypes.dll isn't next to us, or at least next to pythonXX.dll,
  49. # there's a good chance the service will not run. That's usually copied by
  50. # `pywin32_postinstall`, but putting it next to the python DLL seems
  51. # reasonable.
  52. # (Unlike the .exe above, we don't unconditionally copy this, and possibly
  53. # copy it to a different place. Doesn't seem a good reason for that!?)
  54. python_dll = win32api.GetModuleFileName(sys.dllhandle)
  55. pyw = f"pywintypes{sys.version_info[0]}{sys.version_info[1]}{_d}.dll"
  56. correct_pyw = os.path.join(os.path.dirname(python_dll), pyw)
  57. if not os.path.exists(correct_pyw):
  58. print(f"copying helper dll '{pywintypes.__file__}' -> '{correct_pyw}'")
  59. win32api.CopyFile(pywintypes.__file__, correct_pyw)
  60. return correct
  61. def _GetServiceShortName(longName):
  62. # looks up a services name
  63. # from the display name
  64. # Thanks to Andy McKay for this code.
  65. access = (
  66. win32con.KEY_READ | win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_QUERY_VALUE
  67. )
  68. hkey = win32api.RegOpenKey(
  69. win32con.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services", 0, access
  70. )
  71. num = win32api.RegQueryInfoKey(hkey)[0]
  72. longName = longName.lower()
  73. # loop through number of subkeys
  74. for x in range(0, num):
  75. # find service name, open subkey
  76. svc = win32api.RegEnumKey(hkey, x)
  77. skey = win32api.RegOpenKey(hkey, svc, 0, access)
  78. try:
  79. # find display name
  80. thisName = str(win32api.RegQueryValueEx(skey, "DisplayName")[0])
  81. if thisName.lower() == longName:
  82. return svc
  83. except win32api.error:
  84. # in case there is no key called DisplayName
  85. pass
  86. return None
  87. # Open a service given either it's long or short name.
  88. def SmartOpenService(hscm, name, access):
  89. try:
  90. return win32service.OpenService(hscm, name, access)
  91. except win32api.error as details:
  92. if details.winerror not in [
  93. winerror.ERROR_SERVICE_DOES_NOT_EXIST,
  94. winerror.ERROR_INVALID_NAME,
  95. ]:
  96. raise
  97. name = win32service.GetServiceKeyName(hscm, name)
  98. return win32service.OpenService(hscm, name, access)
  99. def LocateSpecificServiceExe(serviceName):
  100. # Return the .exe name of any service.
  101. hkey = win32api.RegOpenKey(
  102. win32con.HKEY_LOCAL_MACHINE,
  103. "SYSTEM\\CurrentControlSet\\Services\\%s" % (serviceName),
  104. 0,
  105. win32con.KEY_ALL_ACCESS,
  106. )
  107. try:
  108. return win32api.RegQueryValueEx(hkey, "ImagePath")[0]
  109. finally:
  110. hkey.Close()
  111. def InstallPerfmonForService(serviceName, iniName, dllName=None):
  112. # If no DLL name, look it up in the INI file name
  113. if not dllName: # May be empty string!
  114. dllName = win32api.GetProfileVal("Python", "dll", "", iniName)
  115. # Still not found - look for the standard one in the same dir as win32service.pyd
  116. if not dllName:
  117. try:
  118. tryName = os.path.join(
  119. os.path.split(win32service.__file__)[0], "perfmondata.dll"
  120. )
  121. if os.path.isfile(tryName):
  122. dllName = tryName
  123. except AttributeError:
  124. # Frozen app? - anyway, can't find it!
  125. pass
  126. if not dllName:
  127. raise ValueError("The name of the performance DLL must be available")
  128. dllName = win32api.GetFullPathName(dllName)
  129. # Now setup all the required "Performance" entries.
  130. hkey = win32api.RegOpenKey(
  131. win32con.HKEY_LOCAL_MACHINE,
  132. "SYSTEM\\CurrentControlSet\\Services\\%s" % (serviceName),
  133. 0,
  134. win32con.KEY_ALL_ACCESS,
  135. )
  136. try:
  137. subKey = win32api.RegCreateKey(hkey, "Performance")
  138. try:
  139. win32api.RegSetValueEx(subKey, "Library", 0, win32con.REG_SZ, dllName)
  140. win32api.RegSetValueEx(
  141. subKey, "Open", 0, win32con.REG_SZ, "OpenPerformanceData"
  142. )
  143. win32api.RegSetValueEx(
  144. subKey, "Close", 0, win32con.REG_SZ, "ClosePerformanceData"
  145. )
  146. win32api.RegSetValueEx(
  147. subKey, "Collect", 0, win32con.REG_SZ, "CollectPerformanceData"
  148. )
  149. finally:
  150. win32api.RegCloseKey(subKey)
  151. finally:
  152. win32api.RegCloseKey(hkey)
  153. # Now do the "Lodctr" thang...
  154. try:
  155. import perfmon
  156. path, fname = os.path.split(iniName)
  157. oldPath = os.getcwd()
  158. if path:
  159. os.chdir(path)
  160. try:
  161. perfmon.LoadPerfCounterTextStrings("python.exe " + fname)
  162. finally:
  163. os.chdir(oldPath)
  164. except win32api.error as details:
  165. print("The service was installed OK, but the performance monitor")
  166. print("data could not be loaded.", details)
  167. def _GetCommandLine(exeName, exeArgs):
  168. if exeArgs is not None:
  169. return exeName + " " + exeArgs
  170. else:
  171. return exeName
  172. def InstallService(
  173. pythonClassString,
  174. serviceName,
  175. displayName,
  176. startType=None,
  177. errorControl=None,
  178. bRunInteractive=0,
  179. serviceDeps=None,
  180. userName=None,
  181. password=None,
  182. exeName=None,
  183. perfMonIni=None,
  184. perfMonDll=None,
  185. exeArgs=None,
  186. description=None,
  187. delayedstart=None,
  188. ):
  189. # Handle the default arguments.
  190. if startType is None:
  191. startType = win32service.SERVICE_DEMAND_START
  192. serviceType = win32service.SERVICE_WIN32_OWN_PROCESS
  193. if bRunInteractive:
  194. serviceType = serviceType | win32service.SERVICE_INTERACTIVE_PROCESS
  195. if errorControl is None:
  196. errorControl = win32service.SERVICE_ERROR_NORMAL
  197. exeName = '"%s"' % LocatePythonServiceExe(exeName)
  198. commandLine = _GetCommandLine(exeName, exeArgs)
  199. hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
  200. try:
  201. hs = win32service.CreateService(
  202. hscm,
  203. serviceName,
  204. displayName,
  205. win32service.SERVICE_ALL_ACCESS, # desired access
  206. serviceType, # service type
  207. startType,
  208. errorControl, # error control type
  209. commandLine,
  210. None,
  211. 0,
  212. serviceDeps,
  213. userName,
  214. password,
  215. )
  216. if description is not None:
  217. try:
  218. win32service.ChangeServiceConfig2(
  219. hs, win32service.SERVICE_CONFIG_DESCRIPTION, description
  220. )
  221. except NotImplementedError:
  222. pass ## ChangeServiceConfig2 and description do not exist on NT
  223. if delayedstart is not None:
  224. try:
  225. win32service.ChangeServiceConfig2(
  226. hs,
  227. win32service.SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
  228. delayedstart,
  229. )
  230. except (win32service.error, NotImplementedError):
  231. ## delayed start only exists on Vista and later - warn only when trying to set delayed to True
  232. warnings.warn("Delayed Start not available on this system")
  233. win32service.CloseServiceHandle(hs)
  234. finally:
  235. win32service.CloseServiceHandle(hscm)
  236. InstallPythonClassString(pythonClassString, serviceName)
  237. # If I have performance monitor info to install, do that.
  238. if perfMonIni is not None:
  239. InstallPerfmonForService(serviceName, perfMonIni, perfMonDll)
  240. def ChangeServiceConfig(
  241. pythonClassString,
  242. serviceName,
  243. startType=None,
  244. errorControl=None,
  245. bRunInteractive=0,
  246. serviceDeps=None,
  247. userName=None,
  248. password=None,
  249. exeName=None,
  250. displayName=None,
  251. perfMonIni=None,
  252. perfMonDll=None,
  253. exeArgs=None,
  254. description=None,
  255. delayedstart=None,
  256. ):
  257. # Before doing anything, remove any perfmon counters.
  258. try:
  259. import perfmon
  260. perfmon.UnloadPerfCounterTextStrings("python.exe " + serviceName)
  261. except (ImportError, win32api.error):
  262. pass
  263. # The EXE location may have changed
  264. exeName = '"%s"' % LocatePythonServiceExe(exeName)
  265. # Handle the default arguments.
  266. if startType is None:
  267. startType = win32service.SERVICE_NO_CHANGE
  268. if errorControl is None:
  269. errorControl = win32service.SERVICE_NO_CHANGE
  270. hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
  271. serviceType = win32service.SERVICE_WIN32_OWN_PROCESS
  272. if bRunInteractive:
  273. serviceType = serviceType | win32service.SERVICE_INTERACTIVE_PROCESS
  274. commandLine = _GetCommandLine(exeName, exeArgs)
  275. try:
  276. hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS)
  277. try:
  278. win32service.ChangeServiceConfig(
  279. hs,
  280. serviceType, # service type
  281. startType,
  282. errorControl, # error control type
  283. commandLine,
  284. None,
  285. 0,
  286. serviceDeps,
  287. userName,
  288. password,
  289. displayName,
  290. )
  291. if description is not None:
  292. try:
  293. win32service.ChangeServiceConfig2(
  294. hs, win32service.SERVICE_CONFIG_DESCRIPTION, description
  295. )
  296. except NotImplementedError:
  297. pass ## ChangeServiceConfig2 and description do not exist on NT
  298. if delayedstart is not None:
  299. try:
  300. win32service.ChangeServiceConfig2(
  301. hs,
  302. win32service.SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
  303. delayedstart,
  304. )
  305. except (win32service.error, NotImplementedError):
  306. ## Delayed start only exists on Vista and later. On Nt, will raise NotImplementedError since ChangeServiceConfig2
  307. ## doensn't exist. On Win2k and XP, will fail with ERROR_INVALID_LEVEL
  308. ## Warn only if trying to set delayed to True
  309. if delayedstart:
  310. warnings.warn("Delayed Start not available on this system")
  311. finally:
  312. win32service.CloseServiceHandle(hs)
  313. finally:
  314. win32service.CloseServiceHandle(hscm)
  315. InstallPythonClassString(pythonClassString, serviceName)
  316. # If I have performance monitor info to install, do that.
  317. if perfMonIni is not None:
  318. InstallPerfmonForService(serviceName, perfMonIni, perfMonDll)
  319. def InstallPythonClassString(pythonClassString, serviceName):
  320. # Now setup our Python specific entries.
  321. if pythonClassString:
  322. key = win32api.RegCreateKey(
  323. win32con.HKEY_LOCAL_MACHINE,
  324. "System\\CurrentControlSet\\Services\\%s\\PythonClass" % serviceName,
  325. )
  326. try:
  327. win32api.RegSetValue(key, None, win32con.REG_SZ, pythonClassString)
  328. finally:
  329. win32api.RegCloseKey(key)
  330. # Utility functions for Services, to allow persistant properties.
  331. def SetServiceCustomOption(serviceName, option, value):
  332. try:
  333. serviceName = serviceName._svc_name_
  334. except AttributeError:
  335. pass
  336. key = win32api.RegCreateKey(
  337. win32con.HKEY_LOCAL_MACHINE,
  338. "System\\CurrentControlSet\\Services\\%s\\Parameters" % serviceName,
  339. )
  340. try:
  341. if type(value) == type(0):
  342. win32api.RegSetValueEx(key, option, 0, win32con.REG_DWORD, value)
  343. else:
  344. win32api.RegSetValueEx(key, option, 0, win32con.REG_SZ, value)
  345. finally:
  346. win32api.RegCloseKey(key)
  347. def GetServiceCustomOption(serviceName, option, defaultValue=None):
  348. # First param may also be a service class/instance.
  349. # This allows services to pass "self"
  350. try:
  351. serviceName = serviceName._svc_name_
  352. except AttributeError:
  353. pass
  354. key = win32api.RegCreateKey(
  355. win32con.HKEY_LOCAL_MACHINE,
  356. "System\\CurrentControlSet\\Services\\%s\\Parameters" % serviceName,
  357. )
  358. try:
  359. try:
  360. return win32api.RegQueryValueEx(key, option)[0]
  361. except win32api.error: # No value.
  362. return defaultValue
  363. finally:
  364. win32api.RegCloseKey(key)
  365. def RemoveService(serviceName):
  366. try:
  367. import perfmon
  368. perfmon.UnloadPerfCounterTextStrings("python.exe " + serviceName)
  369. except (ImportError, win32api.error):
  370. pass
  371. hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
  372. try:
  373. hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS)
  374. win32service.DeleteService(hs)
  375. win32service.CloseServiceHandle(hs)
  376. finally:
  377. win32service.CloseServiceHandle(hscm)
  378. import win32evtlogutil
  379. try:
  380. win32evtlogutil.RemoveSourceFromRegistry(serviceName)
  381. except win32api.error:
  382. pass
  383. def ControlService(serviceName, code, machine=None):
  384. hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_ALL_ACCESS)
  385. try:
  386. hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS)
  387. try:
  388. status = win32service.ControlService(hs, code)
  389. finally:
  390. win32service.CloseServiceHandle(hs)
  391. finally:
  392. win32service.CloseServiceHandle(hscm)
  393. return status
  394. def __FindSvcDeps(findName):
  395. if type(findName) is pywintypes.UnicodeType:
  396. findName = str(findName)
  397. dict = {}
  398. k = win32api.RegOpenKey(
  399. win32con.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services"
  400. )
  401. num = 0
  402. while 1:
  403. try:
  404. svc = win32api.RegEnumKey(k, num)
  405. except win32api.error:
  406. break
  407. num = num + 1
  408. sk = win32api.RegOpenKey(k, svc)
  409. try:
  410. deps, typ = win32api.RegQueryValueEx(sk, "DependOnService")
  411. except win32api.error:
  412. deps = ()
  413. for dep in deps:
  414. dep = dep.lower()
  415. dep_on = dict.get(dep, [])
  416. dep_on.append(svc)
  417. dict[dep] = dep_on
  418. return __ResolveDeps(findName, dict)
  419. def __ResolveDeps(findName, dict):
  420. items = dict.get(findName.lower(), [])
  421. retList = []
  422. for svc in items:
  423. retList.insert(0, svc)
  424. retList = __ResolveDeps(svc, dict) + retList
  425. return retList
  426. def WaitForServiceStatus(serviceName, status, waitSecs, machine=None):
  427. """Waits for the service to return the specified status. You
  428. should have already requested the service to enter that state"""
  429. for i in range(waitSecs * 4):
  430. now_status = QueryServiceStatus(serviceName, machine)[1]
  431. if now_status == status:
  432. break
  433. win32api.Sleep(250)
  434. else:
  435. raise pywintypes.error(
  436. winerror.ERROR_SERVICE_REQUEST_TIMEOUT,
  437. "QueryServiceStatus",
  438. win32api.FormatMessage(winerror.ERROR_SERVICE_REQUEST_TIMEOUT)[:-2],
  439. )
  440. def __StopServiceWithTimeout(hs, waitSecs=30):
  441. try:
  442. status = win32service.ControlService(hs, win32service.SERVICE_CONTROL_STOP)
  443. except pywintypes.error as exc:
  444. if exc.winerror != winerror.ERROR_SERVICE_NOT_ACTIVE:
  445. raise
  446. for i in range(waitSecs):
  447. status = win32service.QueryServiceStatus(hs)
  448. if status[1] == win32service.SERVICE_STOPPED:
  449. break
  450. win32api.Sleep(1000)
  451. else:
  452. raise pywintypes.error(
  453. winerror.ERROR_SERVICE_REQUEST_TIMEOUT,
  454. "ControlService",
  455. win32api.FormatMessage(winerror.ERROR_SERVICE_REQUEST_TIMEOUT)[:-2],
  456. )
  457. def StopServiceWithDeps(serviceName, machine=None, waitSecs=30):
  458. # Stop a service recursively looking for dependant services
  459. hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_ALL_ACCESS)
  460. try:
  461. deps = __FindSvcDeps(serviceName)
  462. for dep in deps:
  463. hs = win32service.OpenService(hscm, dep, win32service.SERVICE_ALL_ACCESS)
  464. try:
  465. __StopServiceWithTimeout(hs, waitSecs)
  466. finally:
  467. win32service.CloseServiceHandle(hs)
  468. # Now my service!
  469. hs = win32service.OpenService(
  470. hscm, serviceName, win32service.SERVICE_ALL_ACCESS
  471. )
  472. try:
  473. __StopServiceWithTimeout(hs, waitSecs)
  474. finally:
  475. win32service.CloseServiceHandle(hs)
  476. finally:
  477. win32service.CloseServiceHandle(hscm)
  478. def StopService(serviceName, machine=None):
  479. return ControlService(serviceName, win32service.SERVICE_CONTROL_STOP, machine)
  480. def StartService(serviceName, args=None, machine=None):
  481. hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_ALL_ACCESS)
  482. try:
  483. hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_ALL_ACCESS)
  484. try:
  485. win32service.StartService(hs, args)
  486. finally:
  487. win32service.CloseServiceHandle(hs)
  488. finally:
  489. win32service.CloseServiceHandle(hscm)
  490. def RestartService(serviceName, args=None, waitSeconds=30, machine=None):
  491. "Stop the service, and then start it again (with some tolerance for allowing it to stop.)"
  492. try:
  493. StopService(serviceName, machine)
  494. except pywintypes.error as exc:
  495. # Allow only "service not running" error
  496. if exc.winerror != winerror.ERROR_SERVICE_NOT_ACTIVE:
  497. raise
  498. # Give it a few goes, as the service may take time to stop
  499. for i in range(waitSeconds):
  500. try:
  501. StartService(serviceName, args, machine)
  502. break
  503. except pywintypes.error as exc:
  504. if exc.winerror != winerror.ERROR_SERVICE_ALREADY_RUNNING:
  505. raise
  506. win32api.Sleep(1000)
  507. else:
  508. print("Gave up waiting for the old service to stop!")
  509. def _DebugCtrlHandler(evt):
  510. if evt in (win32con.CTRL_C_EVENT, win32con.CTRL_BREAK_EVENT):
  511. assert g_debugService
  512. print("Stopping debug service.")
  513. g_debugService.SvcStop()
  514. return True
  515. return False
  516. def DebugService(cls, argv=[]):
  517. # Run a service in "debug" mode. Re-implements what pythonservice.exe
  518. # does when it sees a "-debug" param.
  519. # Currently only used by "frozen" (ie, py2exe) programs (but later may
  520. # end up being used for all services should we ever remove
  521. # pythonservice.exe)
  522. import servicemanager
  523. global g_debugService
  524. print("Debugging service %s - press Ctrl+C to stop." % (cls._svc_name_,))
  525. servicemanager.Debugging(True)
  526. servicemanager.PrepareToHostSingle(cls)
  527. g_debugService = cls(argv)
  528. # Setup a ctrl+c handler to simulate a "stop"
  529. win32api.SetConsoleCtrlHandler(_DebugCtrlHandler, True)
  530. try:
  531. g_debugService.SvcRun()
  532. finally:
  533. win32api.SetConsoleCtrlHandler(_DebugCtrlHandler, False)
  534. servicemanager.Debugging(False)
  535. g_debugService = None
  536. def GetServiceClassString(cls, argv=None):
  537. if argv is None:
  538. argv = sys.argv
  539. import pickle
  540. modName = pickle.whichmodule(cls, cls.__name__)
  541. if modName == "__main__":
  542. try:
  543. fname = win32api.GetFullPathName(argv[0])
  544. path = os.path.split(fname)[0]
  545. # Eaaaahhhh - sometimes this will be a short filename, which causes
  546. # problems with 1.5.1 and the silly filename case rule.
  547. filelist = win32api.FindFiles(fname)
  548. # win32api.FindFiles will not detect files in a zip or exe. If list is empty,
  549. # skip the test and hope the file really exists.
  550. if len(filelist) != 0:
  551. # Get the long name
  552. fname = os.path.join(path, filelist[0][8])
  553. except win32api.error:
  554. raise error(
  555. "Could not resolve the path name '%s' to a full path" % (argv[0])
  556. )
  557. modName = os.path.splitext(fname)[0]
  558. return modName + "." + cls.__name__
  559. def QueryServiceStatus(serviceName, machine=None):
  560. hscm = win32service.OpenSCManager(machine, None, win32service.SC_MANAGER_CONNECT)
  561. try:
  562. hs = SmartOpenService(hscm, serviceName, win32service.SERVICE_QUERY_STATUS)
  563. try:
  564. status = win32service.QueryServiceStatus(hs)
  565. finally:
  566. win32service.CloseServiceHandle(hs)
  567. finally:
  568. win32service.CloseServiceHandle(hscm)
  569. return status
  570. def usage():
  571. try:
  572. fname = os.path.split(sys.argv[0])[1]
  573. except:
  574. fname = sys.argv[0]
  575. print(
  576. "Usage: '%s [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'"
  577. % fname
  578. )
  579. print("Options for 'install' and 'update' commands only:")
  580. print(" --username domain\\username : The Username the service is to run under")
  581. print(" --password password : The password for the username")
  582. print(
  583. " --startup [manual|auto|disabled|delayed] : How the service starts, default = manual"
  584. )
  585. print(" --interactive : Allow the service to interact with the desktop.")
  586. print(
  587. " --perfmonini file: .ini file to use for registering performance monitor data"
  588. )
  589. print(" --perfmondll file: .dll file to use when querying the service for")
  590. print(" performance data, default = perfmondata.dll")
  591. print("Options for 'start' and 'stop' commands only:")
  592. print(" --wait seconds: Wait for the service to actually start or stop.")
  593. print(" If you specify --wait with the 'stop' option, the service")
  594. print(" and all dependent services will be stopped, each waiting")
  595. print(" the specified period.")
  596. sys.exit(1)
  597. def HandleCommandLine(
  598. cls,
  599. serviceClassString=None,
  600. argv=None,
  601. customInstallOptions="",
  602. customOptionHandler=None,
  603. ):
  604. """Utility function allowing services to process the command line.
  605. Allows standard commands such as 'start', 'stop', 'debug', 'install' etc.
  606. Install supports 'standard' command line options prefixed with '--', such as
  607. --username, --password, etc. In addition,
  608. the function allows custom command line options to be handled by the calling function.
  609. """
  610. err = 0
  611. if argv is None:
  612. argv = sys.argv
  613. if len(argv) <= 1:
  614. usage()
  615. serviceName = cls._svc_name_
  616. serviceDisplayName = cls._svc_display_name_
  617. if serviceClassString is None:
  618. serviceClassString = GetServiceClassString(cls)
  619. # Pull apart the command line
  620. import getopt
  621. try:
  622. opts, args = getopt.getopt(
  623. argv[1:],
  624. customInstallOptions,
  625. [
  626. "password=",
  627. "username=",
  628. "startup=",
  629. "perfmonini=",
  630. "perfmondll=",
  631. "interactive",
  632. "wait=",
  633. ],
  634. )
  635. except getopt.error as details:
  636. print(details)
  637. usage()
  638. userName = None
  639. password = None
  640. perfMonIni = perfMonDll = None
  641. startup = None
  642. delayedstart = None
  643. interactive = None
  644. waitSecs = 0
  645. for opt, val in opts:
  646. if opt == "--username":
  647. userName = val
  648. elif opt == "--password":
  649. password = val
  650. elif opt == "--perfmonini":
  651. perfMonIni = val
  652. elif opt == "--perfmondll":
  653. perfMonDll = val
  654. elif opt == "--interactive":
  655. interactive = 1
  656. elif opt == "--startup":
  657. map = {
  658. "manual": win32service.SERVICE_DEMAND_START,
  659. "auto": win32service.SERVICE_AUTO_START,
  660. "delayed": win32service.SERVICE_AUTO_START, ## ChangeServiceConfig2 called later
  661. "disabled": win32service.SERVICE_DISABLED,
  662. }
  663. try:
  664. startup = map[val.lower()]
  665. except KeyError:
  666. print("'%s' is not a valid startup option" % val)
  667. if val.lower() == "delayed":
  668. delayedstart = True
  669. elif val.lower() == "auto":
  670. delayedstart = False
  671. ## else no change
  672. elif opt == "--wait":
  673. try:
  674. waitSecs = int(val)
  675. except ValueError:
  676. print("--wait must specify an integer number of seconds.")
  677. usage()
  678. arg = args[0]
  679. knownArg = 0
  680. # First we process all arguments which pass additional args on
  681. if arg == "start":
  682. knownArg = 1
  683. print("Starting service %s" % (serviceName))
  684. try:
  685. StartService(serviceName, args[1:])
  686. if waitSecs:
  687. WaitForServiceStatus(
  688. serviceName, win32service.SERVICE_RUNNING, waitSecs
  689. )
  690. except win32service.error as exc:
  691. print("Error starting service: %s" % exc.strerror)
  692. err = exc.winerror
  693. elif arg == "restart":
  694. knownArg = 1
  695. print("Restarting service %s" % (serviceName))
  696. RestartService(serviceName, args[1:])
  697. if waitSecs:
  698. WaitForServiceStatus(serviceName, win32service.SERVICE_RUNNING, waitSecs)
  699. elif arg == "debug":
  700. knownArg = 1
  701. if not hasattr(sys, "frozen"):
  702. # non-frozen services use pythonservice.exe which handles a
  703. # -debug option
  704. svcArgs = " ".join(args[1:])
  705. try:
  706. exeName = LocateSpecificServiceExe(serviceName)
  707. except win32api.error as exc:
  708. if exc.winerror == winerror.ERROR_FILE_NOT_FOUND:
  709. print("The service does not appear to be installed.")
  710. print("Please install the service before debugging it.")
  711. sys.exit(1)
  712. raise
  713. try:
  714. os.system("%s -debug %s %s" % (exeName, serviceName, svcArgs))
  715. # ^C is used to kill the debug service. Sometimes Python also gets
  716. # interrupted - ignore it...
  717. except KeyboardInterrupt:
  718. pass
  719. else:
  720. # py2exe services don't use pythonservice - so we simulate
  721. # debugging here.
  722. DebugService(cls, args)
  723. if not knownArg and len(args) != 1:
  724. usage() # the rest of the cmds don't take addn args
  725. if arg == "install":
  726. knownArg = 1
  727. try:
  728. serviceDeps = cls._svc_deps_
  729. except AttributeError:
  730. serviceDeps = None
  731. try:
  732. exeName = cls._exe_name_
  733. except AttributeError:
  734. exeName = None # Default to PythonService.exe
  735. try:
  736. exeArgs = cls._exe_args_
  737. except AttributeError:
  738. exeArgs = None
  739. try:
  740. description = cls._svc_description_
  741. except AttributeError:
  742. description = None
  743. print("Installing service %s" % (serviceName,))
  744. # Note that we install the service before calling the custom option
  745. # handler, so if the custom handler fails, we have an installed service (from NT's POV)
  746. # but is unlikely to work, as the Python code controlling it failed. Therefore
  747. # we remove the service if the first bit works, but the second doesnt!
  748. try:
  749. InstallService(
  750. serviceClassString,
  751. serviceName,
  752. serviceDisplayName,
  753. serviceDeps=serviceDeps,
  754. startType=startup,
  755. bRunInteractive=interactive,
  756. userName=userName,
  757. password=password,
  758. exeName=exeName,
  759. perfMonIni=perfMonIni,
  760. perfMonDll=perfMonDll,
  761. exeArgs=exeArgs,
  762. description=description,
  763. delayedstart=delayedstart,
  764. )
  765. if customOptionHandler:
  766. customOptionHandler(*(opts,))
  767. print("Service installed")
  768. except win32service.error as exc:
  769. if exc.winerror == winerror.ERROR_SERVICE_EXISTS:
  770. arg = "update" # Fall through to the "update" param!
  771. else:
  772. print(
  773. "Error installing service: %s (%d)" % (exc.strerror, exc.winerror)
  774. )
  775. err = exc.winerror
  776. except ValueError as msg: # Can be raised by custom option handler.
  777. print("Error installing service: %s" % str(msg))
  778. err = -1
  779. # xxx - maybe I should remove after _any_ failed install - however,
  780. # xxx - it may be useful to help debug to leave the service as it failed.
  781. # xxx - We really _must_ remove as per the comments above...
  782. # As we failed here, remove the service, so the next installation
  783. # attempt works.
  784. try:
  785. RemoveService(serviceName)
  786. except win32api.error:
  787. print("Warning - could not remove the partially installed service.")
  788. if arg == "update":
  789. knownArg = 1
  790. try:
  791. serviceDeps = cls._svc_deps_
  792. except AttributeError:
  793. serviceDeps = None
  794. try:
  795. exeName = cls._exe_name_
  796. except AttributeError:
  797. exeName = None # Default to PythonService.exe
  798. try:
  799. exeArgs = cls._exe_args_
  800. except AttributeError:
  801. exeArgs = None
  802. try:
  803. description = cls._svc_description_
  804. except AttributeError:
  805. description = None
  806. print("Changing service configuration")
  807. try:
  808. ChangeServiceConfig(
  809. serviceClassString,
  810. serviceName,
  811. serviceDeps=serviceDeps,
  812. startType=startup,
  813. bRunInteractive=interactive,
  814. userName=userName,
  815. password=password,
  816. exeName=exeName,
  817. displayName=serviceDisplayName,
  818. perfMonIni=perfMonIni,
  819. perfMonDll=perfMonDll,
  820. exeArgs=exeArgs,
  821. description=description,
  822. delayedstart=delayedstart,
  823. )
  824. if customOptionHandler:
  825. customOptionHandler(*(opts,))
  826. print("Service updated")
  827. except win32service.error as exc:
  828. print(
  829. "Error changing service configuration: %s (%d)"
  830. % (exc.strerror, exc.winerror)
  831. )
  832. err = exc.winerror
  833. elif arg == "remove":
  834. knownArg = 1
  835. print("Removing service %s" % (serviceName))
  836. try:
  837. RemoveService(serviceName)
  838. print("Service removed")
  839. except win32service.error as exc:
  840. print("Error removing service: %s (%d)" % (exc.strerror, exc.winerror))
  841. err = exc.winerror
  842. elif arg == "stop":
  843. knownArg = 1
  844. print("Stopping service %s" % (serviceName))
  845. try:
  846. if waitSecs:
  847. StopServiceWithDeps(serviceName, waitSecs=waitSecs)
  848. else:
  849. StopService(serviceName)
  850. except win32service.error as exc:
  851. print("Error stopping service: %s (%d)" % (exc.strerror, exc.winerror))
  852. err = exc.winerror
  853. if not knownArg:
  854. err = -1
  855. print("Unknown command - '%s'" % arg)
  856. usage()
  857. return err
  858. #
  859. # Useful base class to build services from.
  860. #
  861. class ServiceFramework:
  862. # Required Attributes:
  863. # _svc_name_ = The service name
  864. # _svc_display_name_ = The service display name
  865. # Optional Attributes:
  866. _svc_deps_ = None # sequence of service names on which this depends
  867. _exe_name_ = None # Default to PythonService.exe
  868. _exe_args_ = None # Default to no arguments
  869. _svc_description_ = (
  870. None # Only exists on Windows 2000 or later, ignored on windows NT
  871. )
  872. def __init__(self, args):
  873. import servicemanager
  874. self.ssh = servicemanager.RegisterServiceCtrlHandler(
  875. args[0], self.ServiceCtrlHandlerEx, True
  876. )
  877. servicemanager.SetEventSourceName(self._svc_name_)
  878. self.checkPoint = 0
  879. def GetAcceptedControls(self):
  880. # Setup the service controls we accept based on our attributes. Note
  881. # that if you need to handle controls via SvcOther[Ex](), you must
  882. # override this.
  883. accepted = 0
  884. if hasattr(self, "SvcStop"):
  885. accepted = accepted | win32service.SERVICE_ACCEPT_STOP
  886. if hasattr(self, "SvcPause") and hasattr(self, "SvcContinue"):
  887. accepted = accepted | win32service.SERVICE_ACCEPT_PAUSE_CONTINUE
  888. if hasattr(self, "SvcShutdown"):
  889. accepted = accepted | win32service.SERVICE_ACCEPT_SHUTDOWN
  890. return accepted
  891. def ReportServiceStatus(
  892. self, serviceStatus, waitHint=5000, win32ExitCode=0, svcExitCode=0
  893. ):
  894. if self.ssh is None: # Debugging!
  895. return
  896. if serviceStatus == win32service.SERVICE_START_PENDING:
  897. accepted = 0
  898. else:
  899. accepted = self.GetAcceptedControls()
  900. if serviceStatus in [
  901. win32service.SERVICE_RUNNING,
  902. win32service.SERVICE_STOPPED,
  903. ]:
  904. checkPoint = 0
  905. else:
  906. self.checkPoint = self.checkPoint + 1
  907. checkPoint = self.checkPoint
  908. # Now report the status to the control manager
  909. status = (
  910. win32service.SERVICE_WIN32_OWN_PROCESS,
  911. serviceStatus,
  912. accepted, # dwControlsAccepted,
  913. win32ExitCode, # dwWin32ExitCode;
  914. svcExitCode, # dwServiceSpecificExitCode;
  915. checkPoint, # dwCheckPoint;
  916. waitHint,
  917. )
  918. win32service.SetServiceStatus(self.ssh, status)
  919. def SvcInterrogate(self):
  920. # Assume we are running, and everyone is happy.
  921. self.ReportServiceStatus(win32service.SERVICE_RUNNING)
  922. def SvcOther(self, control):
  923. try:
  924. print("Unknown control status - %d" % control)
  925. except IOError:
  926. # services may not have a valid stdout!
  927. pass
  928. def ServiceCtrlHandler(self, control):
  929. return self.ServiceCtrlHandlerEx(control, 0, None)
  930. # The 'Ex' functions, which take additional params
  931. def SvcOtherEx(self, control, event_type, data):
  932. # The default here is to call self.SvcOther as that is the old behaviour.
  933. # If you want to take advantage of the extra data, override this method
  934. return self.SvcOther(control)
  935. def ServiceCtrlHandlerEx(self, control, event_type, data):
  936. if control == win32service.SERVICE_CONTROL_STOP:
  937. return self.SvcStop()
  938. elif control == win32service.SERVICE_CONTROL_PAUSE:
  939. return self.SvcPause()
  940. elif control == win32service.SERVICE_CONTROL_CONTINUE:
  941. return self.SvcContinue()
  942. elif control == win32service.SERVICE_CONTROL_INTERROGATE:
  943. return self.SvcInterrogate()
  944. elif control == win32service.SERVICE_CONTROL_SHUTDOWN:
  945. return self.SvcShutdown()
  946. else:
  947. return self.SvcOtherEx(control, event_type, data)
  948. def SvcRun(self):
  949. # This is the entry point the C framework calls when the Service is
  950. # started. Your Service class should implement SvcDoRun().
  951. # Or you can override this method for more control over the Service
  952. # statuses reported to the SCM.
  953. # If this method raises an exception, the C framework will detect this
  954. # and report a SERVICE_STOPPED status with a non-zero error code.
  955. self.ReportServiceStatus(win32service.SERVICE_RUNNING)
  956. self.SvcDoRun()
  957. # Once SvcDoRun terminates, the service has stopped.
  958. # We tell the SCM the service is still stopping - the C framework
  959. # will automatically tell the SCM it has stopped when this returns.
  960. self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)