How to create a Windows Service in Python

by Davide Mastromatteo
6 minute read

teaser

Hi guys, today’s post is just for the ones of you that work with the “OS of the misoriented slashes”: Microsoft Windows. :)

Have you ever had the need of writing a Python script that could run in background as a Windows Service? In this post, you will learn how to do it in less than 10 minutes, no jokes.

I will skip all the introduction about Windows Services, how convenient they could be, how much could be appreciated the fact that they can be run in background even when the user is logged off etc… I mean, if you can code in Python and you use Windows I bet you already know what a Windows Service is, don’t you?

So, first of all, let’s start by installing the Python for Windows extensions:

c:test> pip install pywin32

Once you have done it, let’s write this base class, your Windows service will be a subclass of this base class.

 1'''
 2SMWinservice
 3by Davide Mastromatteo
 4
 5Base class to create winservice in Python
 6-----------------------------------------
 7
 8Instructions:
 9
101. Just create a new class that inherits from this base class
112. Define into the new class the variables
12   _svc_name_ = "nameOfWinservice"
13   _svc_display_name_ = "name of the Winservice that will be displayed in scm"
14   _svc_description_ = "description of the Winservice that will be displayed in scm"
153. Override the three main methods:
16    def start(self) : if you need to do something at the service initialization.
17                      A good idea is to put here the inizialization of the running condition
18    def stop(self)  : if you need to do something just before the service is stopped.
19                      A good idea is to put here the invalidation of the running condition
20    def main(self)  : your actual run loop. Just create a loop based on your running condition
214. Define the entry point of your module calling the method "parse_command_line" of the new class
225. Enjoy
23'''
24
25import socket
26
27import win32serviceutil
28
29import servicemanager
30import win32event
31import win32service
32
33
34class SMWinservice(win32serviceutil.ServiceFramework):
35    '''Base class to create winservice in Python'''
36
37    _svc_name_ = 'pythonService'
38    _svc_display_name_ = 'Python Service'
39    _svc_description_ = 'Python Service Description'
40
41    @classmethod
42    def parse_command_line(cls):
43        '''
44        ClassMethod to parse the command line
45        '''
46        win32serviceutil.HandleCommandLine(cls)
47
48    def __init__(self, args):
49        '''
50        Constructor of the winservice
51        '''
52        win32serviceutil.ServiceFramework.__init__(self, args)
53        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
54        socket.setdefaulttimeout(60)
55
56    def SvcStop(self):
57        '''
58        Called when the service is asked to stop
59        '''
60        self.stop()
61        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
62        win32event.SetEvent(self.hWaitStop)
63
64    def SvcDoRun(self):
65        '''
66        Called when the service is asked to start
67        '''
68        self.start()
69        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
70                              servicemanager.PYS_SERVICE_STARTED,
71                              (self._svc_name_, ''))
72        self.main()
73
74    def start(self):
75        '''
76        Override to add logic before the start
77        eg. running condition
78        '''
79        pass
80
81    def stop(self):
82        '''
83        Override to add logic before the stop
84        eg. invalidating running condition
85        '''
86        pass
87
88    def main(self):
89        '''
90        Main class to be ovverridden to add logic
91        '''
92        pass
93
94# entry point of the module: copy and paste into the new module
95# ensuring you are calling the "parse_command_line" of the new created class
96if __name__ == '__main__':
97    SMWinservice.parse_command_line()

Let’s examine the class we have just introduced a little.

def SvcDoRun(self): it’s the method that will be called when the service is requested to start.

def SvcStop(self): it’s the method that will be called when the service is requested to stop.

def start(self): it’s a method that you will be asked to override if you need to do something when the service is starting (before started)

def stop(self): it’s the method that you will be asked to override if you need to do something when the service is stopping (before stopped)

def main(self): it’s the method that will contain the logic of your script, usually in a loop that keeps it alive until the service is stopped.

def parse_command_line(cls): it’s the method that handles the command line interface that you can use to install and update your windows service

Can you see how easy it is with pywin32 to interface with the system to create a Windows Service? The last mention is for the following variables:

1svc_name = "PythonCornerExample"
2svc_display_name = "Python Corner's Winservice Example"
3svc_description = "That's a great winservice! :)"

These are just three variables that contain the name of the service, the “friendly name” that will be used by Windows to display the name on the mmc console and a short description of your service.

As always, enough talk, let’s code something useful!


Let’s pretend that we want to create a Winservice that, when started, creates a random file on our C: drive every 5 seconds.

What? Do you think it is stupid? Well, install it on your boss PC, set the destination folder as its user’s desktop and you will change your mind. :)

However, how can you achieve this result? Super easy.

  • Subclass the SMWinservice class we have just met.
  • On the new class, override the three variables svc_name, svc_display_name and svc_description.
  • Override the “start” method to set the running condition. Setting a boolean variable will be enough for that.
  • Override the “stop” method to invalidate the running condition when the service is requested to be stopped.
  • Override the “main” method to add the logic of creating a random file every 5 seconds
  • Add the call at the “parse_command_line” function to handle the command line interface.

The result should be something like this:

 1import time
 2import random
 3from pathlib import Path
 4from SMWinservice import SMWinservice
 5
 6class PythonCornerExample(SMWinservice):
 7    _svc_name_ = "PythonCornerExample"
 8    _svc_display_name_ = "Python Corner's Winservice Example"
 9    _svc_description_ = "That's a great winservice! :)"
10
11    def start(self):
12        self.isrunning = True
13
14    def stop(self):
15        self.isrunning = False
16
17    def main(self):
18        i = 0
19        while self.isrunning:
20            random.seed()
21            x = random.randint(1, 1000000)
22            Path(f'c:{x}.txt').touch()
23            time.sleep(5)
24
25if __name__ == '__main__':
26    PythonCornerExample.parse_command_line()

That’s it! Now it’s time to install our newly created winservice. Just open a command prompt, navigate to your script directory and install the service with the command:

C:test> python PythonCornerExample.py install
Installing service PythonCornerExample
Service installed

In the future, if you want to change the code of your service, just modify it and reinstall the service with

C:test> python PythonCornerExample.py update
Changing service configuration
Service updated

Now, open the “Services” msc snap in

C:test> mmc Services.msc

locate your new PythonCornerExample winservice, and right click and choose properties. Here you can start your service and configure it at your will.

Now try to start your service and go to see your C: folder contents.

Can you see all these files being created to your C: folder? Yeah, that’s working!

But now it’s time to stop it! :) You can do it from the previous windows or just by using the command line

C:test> net stop PythonCornerExample

Il servizio Python Corner's Winservice Example sta per essere arrestato.. 
Servizio Python Corner's Winservice Example arrestato.

If something goes wrong…

There are a couple of known problems that can happen writing Windows Services in Python. If you have successfully installed the service but starting it you get an error, follow this iter to troubleshoot your service:

  • Check if Python is in your PATH variable. It MUST be there. To check this, just open a command prompt and try starting the python interpreter by typing “python”. If it starts, you are ok.
  • Be sure to have the file C:\Program Files\Python36\Lib\site-packages\win32\pywintypes36.dll (please note that “36” is the version of your Python installation). If you don’t have this file, take it from C:\Program Files\Python36\Lib\site-packages\pywin32_system32\pywintypes36.dll and copy it into C:\Program Files\Python36\Lib\site-packages\win32

If you still have problems, try executing your Python script in debug mode. To try this with our previous example, open a terminal, navigate to the directory where the script resides and type

c:test> python PythonCornerExample.py debug

Ok, that’s all for today, this was just a brief introduction to developing Windows Services with Python. Try it by yourself and … happy coding!

D.


Did you find this article helpful?


Buy me a coffee! Buy me a coffee!