Nathan Sanders
Software / Web / Data
© 2022 All rights reserved.
Developing a python based Windows Service
I was tasked with building something that would phone home collected data regularly. So I investigated many options but concluded that python + pyinstaller was my best bet. Below are the steps I did and the components I used to craft this solution. I hope this helps someone in the future!
Contents
- The Components
- Code Snippet of Service Application
- Code Snippet of Service Installer
- Python base requirements
The Components
The essential items I used to make this work beyond the Python code of the application are the following:
- Pyinstaller – Package the application up into a windows application.
- Inno Setup – Installer creation, managed all the installation tasks of deploying and updating the app.
- pywin32 – Python package for Win32 (pywin32) extensions, which provides access to many Windows APIs from Python. This includes the ability to run as a service.
Code Snippet of Service Application
import socket
import time
import schedule # https://pypi.python.org/pypi/schedule
import servicemanager
import win32event
import win32service
import win32serviceutil
"""
python.exe XXXX.py install | remove | start | stop | help
XXXX.exe install | remove | start | stop | update | help
"""
.. Add you logic for basic app variables and logging functions here
class WinService(win32serviceutil.ServiceFramework):
_svc_name_ = "XXXX Service"
_svc_display_name_ = "XXXX"
_svc_description_ = "XXXX" \
"XXXX" \
"XXXX"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.stop_event = win32event.CreateEvent(None, 0, 0, None)
socket.setdefaulttimeout(60)
self.stop_requested = False
# stop command service
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.stop_event)
mylogger.info("*** STOPPING SERVICE ***\n")
self.stop_requested = True
self.ReportServiceStatus(win32service.SERVICE_STOPPED)
# start command service
def SvcDoRun(self):
try:
servicemanager.LogMsg(
servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, '')
)
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
try:
# *** STARTING SERVICE ***
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ''))
# run main process
self.main()
except Exception as e:
s = str(e)
mylogger.error('Exception :' + s)
self.SvcStop()
except Exception as e:
pass
# main process
def main(self):
#... STARTING SCHEDULE PROCESS ...
# Service Start Logic goes here
schedule.every(1).minutes.do(YYYY) # Call your Service function you imported, this function should check if actions are needed
count = 0
while not self.stop_requested:
# execute task on schedule
schedule.run_pending()
if debug:
count += 1
print(f"Sleeping... {count} seconds")
time.sleep(.5)
if count == 60 and debug:
count = 0
return
if __name__ == '__main__':
if len(sys.argv) == 1:
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(WinService)
servicemanager.StartServiceCtrlDispatcher()
else:
win32serviceutil.HandleCommandLine(WinService)
Code Snippet of Service Installer
REM Script to install/update XXXX
echo off
cls
sc query "XXXX Service" > nul
IF ERRORLEVEL 1060 (
echo "Service is not installed"
XXXX.exe --startup delayed install
sc failure "XXXX Service" actions= restart/180000/restart/180000/restart/180000 reset= 86400
XXXX.exe start
) else (
XXXX.exe stop
XXXX.exe --startup delayed update
sc failure "XXXX Service" actions= restart/180000/restart/180000/restart/180000 reset= 86400
XXXX.exe start
)
Python base requirements
pyinstaller
pyinstaller-hooks-contrib
pywin32
pywin32-ctypes
schedule