I hit this problem recently with the embeddable interpreter and came up with these instructions that work for Python 3.11 on both the embeddable interpreter and a normal virtual environment.
This solution differs from the current most popular answer in that it doesn't rely on any shared DLL's in C:\windows\system32
.
Lastly, a Python script demonstrating the required environment setup is provided.
Virtual Environment
PS D:\dev\python_winsvc> C:\Python\Python311\python.exe -m venv venv_311
PS D:\dev\python_winsvc> . .\venv_311\Scripts\activate
(venv_311) PS D:\dev\python_winsvc> pip install pywin32
Collecting pywin32
Using cached pywin32-306-cp311-cp311-win_amd64.whl (9.2 MB)
Installing collected packages: pywin32
Successfully installed pywin32-306
(venv_311) PS D:\dev\python_winsvc> deactivate
Make .\venv_311\Scripts
look like this:
python.exe # already there
python3.dll # copy/link from venv source interpreter
python311.dll # copy/link from venv source interpreter
pythoncom311.dll # copy/link from .\venv_311\Lib\site-packages\pywin32_system32
pywintypes311.dll # copy/link from .\venv_311\Lib\site-packages\pywin32_system32
pythonservice.exe # copy/link from .\venv_311\Lib\site-packages\win32
servicemanager.pyd # copy/link from .\venv_311\Lib\site-packages\win32
...
Perform the service install without activating the venv. Otherwise the wrong pythonservice.exe is used as service executable and it has to be the one in .\venv_311\Scripts
as it needs to be in the same folder as the required DLL's.
PS D:\dev\python_winsvc> .\venv_311\Scripts\python .\winsvc.py install
Installing service python-winsvc
Service installed
PS D:\dev\python_winsvc> .\venv_311\Scripts\python .\winsvc.py start
Starting service python-winsvc
Embedded Interpreter
Note that we stage the pip packages for the embeddable interpreter by using a regularly installed one as the embeddable interpreter lacks the pip module.
PS D:\dev\python_winsvc> C:\Python\Python311\python.exe -m pip install --target embed_311\lib\site-packages pywin32
Collecting pywin32
Using cached pywin32-306-cp311-cp311-win_amd64.whl (9.2 MB)
Installing collected packages: pywin32
Successfully installed pywin32-306
Make .\embed_311
look like this:
python.exe # already there
python3.dll # already there
python311.dll # already there
pythoncom311.dll # copy/link from .\embed_311\Lib\site-packages\pywin32_system32
pywintypes311.dll # copy/link from .\embed_311\Lib\site-packages\pywin32_system32
pythonservice.exe # copy/link from .\embed_311\Lib\site-packages\win32
servicemanager.pyd # copy/link from .\embed_311\Lib\site-packages\win32
...
PS D:\dev\python_winsvc> .\embed_311\python.exe .\winsvc.py install
Installing service python-winsvc
Service installed
PS D:\dev\python_winsvc> .\embed_311\python.exe .\winsvc.py start
Starting service python-winsvc
Example Service
# winsvc.py
import sys
import pathlib
PYTHON_PATH = pathlib.Path(sys.executable).parent
import site
site.addsitedir(PYTHON_PATH.joinpath("lib/site-packages")) # Only required when using the embedded interpreter
from typing import *
import logging, logging.handlers
import threading
import time
import win32event
import win32evtlogutil
import win32service
import win32serviceutil
import servicemanager
def configure_logger(filename: str) -> logging.Logger:
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(levelname)5.5s: %(message)s")
handlers = [
logging.handlers.RotatingFileHandler(pathlib.Path(__file__).parent.joinpath(filename), maxBytes=1024*1024, backupCount=0),
logging.StreamHandler()
]
for handler in handlers:
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
logger = configure_logger("winsvc.log")
class ApplicationThread(threading.Thread):
def __init__(self) -> None:
super().__init__()
self._exit = False
def stop(self) -> None:
self._exit = True
def run(self) -> None:
logger.debug("service is running")
while not self._exit:
time.sleep(1)
class Win32ServiceWrapper(win32serviceutil.ServiceFramework):
_exe_name_ = str(PYTHON_PATH.joinpath("pythonservice.exe"))
_svc_name_ = "python-winsvc"
_svc_display_name_ = "Python WinSvc"
def __init__(self, args: Iterable[str]) -> None:
super().__init__(args)
self._stop_event = win32event.CreateEvent(None, 0, 0, None)
self._thread = ApplicationThread()
def SvcStop(self) -> None:
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self._stop_event)
def SvcDoRun(self):
win32evtlogutil.ReportEvent(self._svc_display_name_, servicemanager.PYS_SERVICE_STARTED, 0, servicemanager.EVENTLOG_INFORMATION_TYPE, (self._svc_name_, ''))
self._thread.start()
win32event.WaitForSingleObject(self._stop_event, win32event.INFINITE)
self._thread.stop()
self._thread.join()
win32evtlogutil.ReportEvent(self._svc_display_name_, servicemanager.PYS_SERVICE_STOPPED, 0, servicemanager.EVENTLOG_INFORMATION_TYPE, (self._svc_name_, ''))
if __name__ == "__main__":
win32serviceutil.HandleCommandLine(Win32ServiceWrapper)