EDIT: As noted in my comment below, this shouldn't be needed anymore in pyWin32 305 and up, which automatically copies pythonservice.exe to sys.exec_prefix
(i.e: inside your venv if you have one) and runs it from there.
As hinted by dslosky and this answer to a linked question, pythonservice.exe is going to run as a system service, so it will have a different environment than you do as a user. Running python service.py debug
will run just fine because it's still running with your user environment, but if you run python service.py start
, it could very well now fail instead due to the difference in environment variables. Your service timing out instantly is most likely due to pythonservice.exe failing to execute, and it will fail to execute it it's missing either PythonXX.dll
or pywintypesXX.dll
.
PythonXX.dll
is likely to be in your system path already (depending on how Python was installed), but if you're juggling with several Python versions on the same system and trying to avoid altering the environment (like I unfortunately am), that's going to be a problem. I was hoping to get the service to use the venv by running something like .\.pyenv37\Scripts\python.exe service.py start
, which works for regular Python scripts running as scheduled tasks, but you have to remember that service.py start
only commands Windows to start the service. The actual service executable, pythonservice.exe
, will actually resolve Python37.dll from the PATH variable, not the venv I was using when running service.py start
. This means Python37.dll is no longer known when pythonservice.exe starts running as a system service, even though I have Python37.dll in my user PATH, as it is now running using the system PATH. Since pythonservice.exe can't run without Python37.dll, that causes an immediate linker error, and Windows reports it as an instant timeout.
The same goes for pywintypesXX.dll
(pythonservice.exe will immediately timeout if it can't be found), except instead of installing it somewhere in your search path, the more portable solution is to drop it in the same directory as pythonservice.exe
since the deafult DLL search path includes it.
EDIT: Here's what I'm using to verify all of that on script installation/update:
# customOptionHandler will only run after service install/update
if __name__=='__main__':
win32serviceutil.HandleCommandLine(AppServerSvc, customOptionHandler=post_service_update)
.
def post_service_update(*args):
import win32api, win32con, win32profile, pywintypes
from contextlib import closing
env_reg_key = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
hkey = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, env_reg_key, 0, win32con.KEY_ALL_ACCESS)
with closing(hkey):
system_path = win32api.RegQueryValueEx(hkey, 'PATH')[0]
# PATH may contain %SYSTEM_ROOT% or other env variables that must be expanded
# ExpandEnvironmentStringsForUser(None) only expands System variables
system_path = win32profile.ExpandEnvironmentStringsForUser(None, system_path)
system_path_list = system_path.split(os.pathsep)
core_dll_file = win32api.GetModuleFileName(sys.dllhandle)
core_dll_name = os.path.basename(core_dll_file)
for search_path_dir in system_path_list:
try:
dll_path = win32api.SearchPath(search_path_dir, core_dll_name)[0]
print(f"System python DLL: {dll_path}")
break
except pywintypes.error as ex:
if ex.args[1] != 'SearchPath': raise
continue
else:
print("*** WARNING ***")
print(f"Your current Python DLL ({core_dll_name}) is not in your SYSTEM PATH")
print("The service is likely to not launch correctly.")
from win32serviceutil import LocatePythonServiceExe
pythonservice_exe = LocatePythonServiceExe()
pywintypes_dll_file = pywintypes.__spec__.origin
pythonservice_path = os.path.dirname(pythonservice_exe)
pywintypes_dll_name = os.path.basename(pywintypes_dll_file)
try:
return win32api.SearchPath(pythonservice_path, pywintypes_dll_name)[0]
except pywintypes.error as ex:
if ex.args[1] != 'SearchPath': raise
print("*** WARNING ***")
print(f"{pywintypes_dll_name} is not is the same directory as pythonservice.exe")
print(f'Copy "{pywintypes_dll_file}" to "{pythonservice_path}"')
print("The service is likely to not launch correctly.")
It may seem like a lot, but it will at leastkeep you from forgetting to do those steps when deploying the service on a new machine/virtual environment or when updating python.