As noted by Chip (and which I've missed while trying to figure this out), 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 may now fail instead due to the difference in environment variables. An instant timeout 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 like me and trying to be extra careful to not alter the environment, that's going to be a problem. I was running something like .\.pyenv37\Scripts\python.exe service.py start, gets Python37.dll from the PATH, not the venv like I assumed, so it's no longer known when pythonservice.exe starts running using a different PATH, which causes it to immediately fail Windows reports it as an instant timeout.
The same goes for pywintypesXX.dll, 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. It will also cause your service to immediately timeout if it if pythonservice.exe can't find it.
Figuring this out without any sort of logs is an absolute nightmare, let me tell you!
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.