I recently had to install a Python application as a Windows Service and lost a few hours to a seemingly obvious problem involving arguments.
And of course I solved it rather quickly by using an old “trick”; walking away and then coming back when I was refreshed.
I used the Python for Windows extensions, pywin32, library to create the service which inherited from win32serviceutil.ServiceFramework
The __init__ method receives two arguments; ‘self’ and ‘args’ but you can also always access sys.argv as normal.
But it turns out that sys.argv only contains the name of the PythonService, for example, [‘C:\\Python27\\lib\\site-packages\\win32\\PythonService.exe’] which is used to actually call your main program. It does not contain any start parameters that you may have added to the Windows Service Manager when starting your service. But you can find these start parameters in the ‘args’ parameter.
The python application uses argparse.ArgumentParser from the argparse library. The application was failing with argparse complaining
File "C:\Python27\lib\argparse.py", line 1937, in _parse_known_args
self.error(_('too few arguments'))
I tried every combination of args before I hit upon the answer:
The ‘args’ parameter is a tuple!, Not a list as expected by a call to parser.parse_args().
Also sys.arvg has insufficient elements as it is received by __init__
So the solution is to change sys.argv to be the list that parser.parse_args() expects but using the elements in args.
sys.argv = [arg for arg in args]
This works when passing the arguments using the Windows Service Manager “start parameters” field. But this value is not persisted (Why Microsoft, why?) so using the registry may be good place to store values needed for subsequent runs.
Another workaround is to edit the registry value for your service.
In the Registry Editor window, navigate to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<ServiceName> and open the key ImagePath.
After the path to the PythonService you may add arguments.
Now you will NOT need to mess with sys.argv and it can be left as is since it now has the arguments in a list as expected.