Python for windows is pretty powerful. Largely by its ability to complete extensive tasks in very few lines of code. Legibility is maybe tougher, but it is a great tool when scripting or tool building.
Python gets even better with its support to run as a Windows Service. The main problem though with it is the shortage of documentation. Hopefully this post will change that.
Note the following tutorial was carried out on Windows 10 Pro x64.
mborus was also able to carry out the following tutorial on Windows 7 x86 with only some minor path changes!
Installing Python 3 on Windows
First up is installing python 3 for windows. This is pretty easy - just download from https://www.python.org/downloads/. For this demo I used specifically Python 3.6.1 (https://www.python.org/downloads/release/python-361/)
Next, Python does lack some native library support needed to create services in windows - so you also need to install the python windows extension library here: https://sourceforge.net/projects/pywin32/files/. This process is quite a pain to sift through, as you need to dig through the version numbers and match your python version and the python bit version. If you downloaded python 3.6.1 as I did, it has likely installed an x86/32-bit version of python 3.6.1. The corresponding extension I downloaded was in the Build 221 folder here: https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/. From here I downloaded pywin32-221.win32-py3.6.exe. Note anything with arm64 in the name is x64/64-bit.
UPDATE - April 28/18: I've been building some more recent tools with newer versions of python and pywin32, and additionaly been working with 64 bit python rather than 32 bit. The 32bit version I found has memory limitations when working with multi-processing or multi-threading. For my latest setup I used Python 3.6.5 and pywin32 release 223. To download these I downloaded the 64-bit amd64 version of python 3.6.5: https://www.python.org/downloads/release/python-365/ and then downloaded pywin32-223.win-amd64-py3.6.exe for release 332 from pywin32's new github page in their releases: https://github.com/mhammond/pywin32/releases. The previous sourceforge location is still kept for older versions as an archive but is no longer maintained.
Up to this point everything is auto-magically installed into place. For your service to run though, the python installation is missing required
PATH variable information, so to do this you need to include a number of routes in your system and user
Open your windows environment variables and add the following to the system (note it must be the system path as windows services don't run under your current user by default) and user
PATH variables. You will need to tweak them to your computer as Python is automatically installed in the AppData of the logged in user:
C:\Users\<username>\AppData\Local\Programs\Python\Python36-32 C:\Program Files\Python 3.6 C:\Users\<username>\AppData\Local\Programs\Python\Python36-32\Scripts C:\Users\<username>\AppData\Local\Programs\Python\Python36-32\DLLs
The DLL path is specifically needed so that Python can generate and run the Windows Service.
mborus, for Windows 7 x86, only the following paths are needed in order to get the python service to run:
Note that if you have installed Python on your computer differently, or it is not located in the AppData folder, you may need to do some more searching in order to correctly find the location of the
Starting Your Service Project
Now that you have everything setup with Python 3, you can build your service. Documentation here is sparse, but I was able to find another person blog and a stack overflow post to find everything needed to get started. The original posts are as follows:
From these two I was able to come up with this for a starting template
import win32serviceutil import win32service import win32event import servicemanager import configparser import os import inspect import logging from logging.handlers import RotatingFileHandler class AppServerSvc (win32serviceutil.ServiceFramework): _svc_name_ = "MyService" _svc_display_name_ = "My Service" _config = configparser.ConfigParser() def __init__(self,args): win32serviceutil.ServiceFramework.__init__(self,args) self.hWaitStop = win32event.CreateEvent(None,0,0,None) self._config.read(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + '/config.ini') print(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) print(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + '/teconfig.ini') print(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) print(self._config.sections()) logDir = self._config["DEFAULT"]["loggingDir"] logPath = logDir + "/service-log.log" self._logger = logging.getLogger("MyService") self._logger.setLevel(logging.DEBUG) handler = RotatingFileHandler(logPath, maxBytes=4096, backupCount=10) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) self._logger.addHandler(handler) def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hWaitStop) def SvcDoRun(self): self.ReportServiceStatus(win32service.SERVICE_RUNNING) servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_,'')) self._logger.info("Service Is Starting") self.main() def main(self): # your code could go here rc = None while rc != win32event.WAIT_OBJECT_0: # your code here... # hang for 1 minute or until service is stopped - whichever comes first rc = win32event.WaitForSingleObject(self.hWaitStop, (1 * 60 * 1000)) # your code also here ... if __name__ == '__main__': win32serviceutil.HandleCommandLine(AppServerSvc)
The above code is pretty straight forward. the
SvcStop method is called when your service is stopped in windows. The
SvcDoRun is called when your method is started. To keep the code more sane there is also a
main method which is called at start. Within this method all of your services logic should be called. Ontop of these basic components I also added a logger and configuration loader. The configuration loader looks for a
config.ini located within the same directory as the service script is above. From within this config the logging directory can be found - writing to the folder location specified in the config to the file
_svc_display_name are both required attributes needed for the service installation. These will work as your display names and calling names from terminal.
Install Your Service
Installing your service in python is extremely easy. Open a terminal with Administrator privileges and cd to the location of your service script. Type the following:
python <nameOfFile>.py install
This will install your service. For additional options simply run your script with no parameters. You can update or remove your service at any time using the appropriate
remove commands, or even run in debug with
One of the most common errors from windows when starting your service is
Error 1053: The service did not respond to the start or control request in a timely fashion. This can occur for multiple reasons but there are a couple things to check when you do get them:
- Make sure your service is actually stopping:Note the `main` method has an infinite loop. The template above with break the loop if the stop even occurs, but that will only happen if you call `win32event.WaitForSingleObject` somewhere within that loop; setting `rc` to the updated value
- Make sure your service actually starts: Same as the first one, if your service starts and does not get stuck in the infinite loop, it will exit. Terminating the service
- Check your system and user PATH contains the necessary routes: The DLL path is extremely important for your python service as its how the script interfaces with windows to operate as a service. Additionally if the service is unable to load python - you are also hooped. Check by typing `echo %PATH%` in both a regular console and a console running with Administrator priveleges to ensure all of your paths have been loaded
- Give the service another restart: Changes to your `PATH` variable may not kick in immediately - its a windows thing