5

I am trying to run a python program, written using Anaconda, as a Windows Service. The complexity is that I'd like to run the Windows Service from a specific conda virtual environment. The idea being that in the future, we may develop more python based windows services that may have different module dependencies, thus keeping each within its own virtual environment would be ideal.

I've found several excellent articles about how to write a python program as a windows service and they work fine. I created a very simple test program that simply writes some messages to a text file after the service starts. I can successfully install this test python program as a windows service and I see the various text messages in my file. However, when I try to import modules like Numpy or TensorFlow into my simple test python program, the service won't start and I get failure messages that their respective DLL's can't be found.

I am sure the problem is because the required conda virtual environment hasn't been activated. At the same time, I've tried replicating the various conda environment variables at the system level; tried adding all of the required python library paths from the virtual environment to the system path and system-wide python path, but to no avail.

I suspect that if I could activate the conda virtual environment as part of my python code, that would solve the problem. (I also suspect that installing all of the required modules into my base configuration would solve the problem, but i'd like to avoid that).

Here is the little test program I've written. This program works just fine with the basic Python modules like sys, os and so forth. It fails with the following error message when I try to run it and include Numpy or TensorFlow: (This is from the Windows Event Viewer after I try and start my service - which does install correctly):

Python could not import the service's module Traceback (most recent call last): File "D:\TFS\Projects\DEV\AEPEnrollmentForms\src\aepenrl\Windows_Service_Example.py", line 35, in import numpy as np File "C:\Users\pboerner\AppData\Local\conda\conda\envs\aepenr\lib\site-packages\numpy__init__.py", line 140, in from . import _distributor_init File "C:\Users\pboerner\AppData\Local\conda\conda\envs\aepenr\lib\site-packages\numpy_distributor_init.py", line 34, in from . import _mklinit ImportError: DLL load failed: The specified module could not be found. %2: %3

Here is the code for the simple test program. (Most of the Windows Service integration work I took from an excellent article provided by Davide Mastromatteo)

import numpy as np

import socket
import sys
import time

import win32serviceutil

import servicemanager
import win32event
import win32service


class SimpleService(win32serviceutil.ServiceFramework):
    '''Base class to create winservice in Python'''

    _svc_name_ = 'TestPythonSrvc'
    _svc_display_name_ = 'Test Python Service'
    _svc_description_ = 'Test to see how to create a windows service with python'

    @classmethod
    def parse_command_line(cls):
        '''
        ClassMethod to parse the command line
        '''
        win32serviceutil.HandleCommandLine(cls)

    def __init__(self, args):
        '''
        Constructor of the winservice
        '''
        self.isrunning=True
        self.fid = open("D:\\temp\\simple_service.txt", "w")
        self.fid.write("Initialize\n")
        self.fid.flush()

        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        socket.setdefaulttimeout(60)

    def SvcStop(self):
        '''
        Called when the service is asked to stop
        '''
        self.stop()
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self):
        '''
        Called when the service is asked to start
        '''
        self.start()
        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                              servicemanager.PYS_SERVICE_STARTED,
                              (self._svc_name_, ''))
        self.main()

    def start(self):
        '''
        Override to add logic before the start
        eg. running condition
        '''
        self.isrunning = True
        self.fid.write("Start method called\n")
        self.fid.flush()

    def stop(self):
        '''
        Override to add logic before the stop
        eg. invalidating running condition
        '''
        self.isrunning = False
        self.fid.write("STOP method called. Setting stop flag\n")
        self.fid.flush()

    def main(self):
        '''
        Main class to be ovverridden to add logic
        '''
        a = np.zeros((100,1))
        while True:
            if self.isrunning:
                self.fid.write(f"Tick. Numpy array shape {a.shape}\n")
                self.fid.flush()
                time.sleep(1)
            else:
                self.fid.write("Breaking out of main loop\n")
                self.fid.flush()
                break;

        self.fid.write("Closing the log file\n")
        self.fid.flush()
        self.fid.close()

if __name__ == '__main__':
    # This code block was required to get this simple service example to run
    # on a Windows 10 laptop with Admin privs.  Only calling the 
    # HandleCommandLine method alone didn'd seem to work. Not sure why but this
    # code was provided as a solution on the Web.
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(SimpleService)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(SimpleService)
Peer
  • 181
  • 2
  • 5
  • One more point: Many thanks in advance for any help or guidance! – Peer Sep 12 '19 at 17:46
  • Did you try building on a virtual environment as a python script file, then converting to a executable file, and so then with all libraries and dependencies with this executable file, then running as a windows service – Dulangi_Kanchana Nov 19 '19 at 09:10
  • I have a similar problem. I also think it is due to lack of activating conda. Strangely, I used to be able to launch python code by calling a conda env's python.exe with its absolute path and without having to activate the env beforehand. Later this changed (4.6?) and I worked around the issue by creating a batch script that first activates the env, then calls python. But that doesn't work when you want to run your python code as a service. – dcatteeu May 22 '20 at 12:48
  • BTW, I use NSSM to quickly get python code set up as a Windows service. Works pretty good. The problem is the same though, since conda (4.6) you have to activate your conda env before running python or it won't find the necessary DLLs. If you don't need to run any code when the service is stopped (that is, getting killed is just fine) the work around mentioned above works fine. – dcatteeu May 22 '20 at 12:55
  • Any luck on this one? I am facing same issue and I think you are right about activation being the root cause of this. Would be glad to hear some update. Thanks – Seb T. Oct 21 '20 at 08:01

2 Answers2

2

Unfortunately the service manager of Windows is not as fexible as, let's say, systemd. The only method I found was to do the following:

  • make a batch file holding all your logic, e.g.
    call C:\ProgramData\Anaconda3\Scripts\activate.bat C:\ProgramData\Anaconda3
    call activate yourenv
    cd C:/path/to/your/wd
    python yourservice.py and your args
    
    NB: your activate.bat file could be under your home folder: ~\AppData\local\Continuum\anaconda3\Scripts
  • Use the NSSM (which is aptly named): http://nssm.cc/download ; see also Run batch file as a Windows service . You need to start the command prompt or powershell as administrator before calling nssm.

This works to serve a bokeh server really well (using bokeh serveinstead of python). I guess it works for python script of any complexity then.

You can run a command upon stopping or exiting within the "hooks" tab.

In my case the logic within the batch file depends on the machine, so I need to make an additional python script that is called upon setting up to write the batch file.

hyamanieu
  • 1,055
  • 9
  • 25
1

I was stuck in the exact same situation when I came across a blog (link below) and followed the steps in it

  1. Create a run.bat file in your working directory with the following lines.

    call conda activate env  
    call python app.py
    

    So that when you call your batch file it will first activate the virtual environment and then run the python script.

  2. Download NSSM, no need to install it. Once downloaded, go to the NSSM -> win32/win64 (as per your computer architecture) and run the following command in the command prompt as administrator:

    C:\nssm-2.24\win64\nssm install <service name> "C:\path\to\your\run.bat"
    
  3. Your service is installed at this point but not yet started.

  4. You might need to configure it

    C:\nssm-2.24\win64\nssm edit <service name>
    
  5. NSSM service editor will open after executing the above command where you can set Display names, description, log files, etc.

  6. Once done you can start the service as

    C:\nssm-2.24\win64\nssm start <service name>
    

Link for reference:- Run Windows Service in its own Python virtual environment

I simply followed these steps and it worked just fine for me. I hope it will work for you as well.

Ketan Sahu
  • 127
  • 1
  • 11
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/29062543) – coreuter May 27 '21 at 08:43
  • 1
    @coreuter Thanks for guiding. Added the essential parts above ^ – Ketan Sahu May 27 '21 at 12:05
  • This was the route I went. One caveat stomped me, tho: if your environment exists within your user's home directory (such as c:\Users\youruser\.conda\envs\yourenv), you'll need to specify the user to run the service in NSSM tab 'Log On' – Benoit Dufresne May 19 '22 at 21:21