How do you run a Python script as a service in Windows?

后端 未结 13 1633
你的背包
你的背包 2020-11-22 02:18

I am sketching the architecture for a set of programs that share various interrelated objects stored in a database. I want one of the programs to act as a service which prov

相关标签:
13条回答
  • 2020-11-22 02:38

    nssm in python 3+

    (I converted my .py file to .exe with pyinstaller)

    nssm: as said before

    • run nssm install {ServiceName}
    • On NSSM´s console:

      path: path\to\your\program.exe

      Startup directory: path\to\your\ #same as the path but without your program.exe

      Arguments: empty

    If you don't want to convert your project to .exe

    • create a .bat file with python {{your python.py file name}}
    • and set the path to the .bat file
    0 讨论(0)
  • 2020-11-22 02:38

    The accepted answer using win32serviceutil works but is complicated and makes debugging and changes harder. It is far easier to use NSSM (the Non-Sucking Service Manager). You write and comfortably debug a normal python program and when it finally works you use NSSM to install it as a service in less than a minute:

    From an elevated (admin) command prompt you run nssm.exe install NameOfYourService and you fill-in these options:

    • path: (the path to python.exe e.g. C:\Python27\Python.exe)
    • Arguments: (the path to your python script, e.g. c:\path\to\program.py)

    By the way, if your program prints useful messages that you want to keep in a log file NSSM can also handle this and a lot more for you.

    0 讨论(0)
  • 2020-11-22 02:39

    A complete pywin32 example using loop or subthread

    After working on this on and off for a few days, here is the answer I would have wished to find, using pywin32 to keep it nice and self contained.

    This is complete working code for one loop-based and one thread-based solution. It may work on both python 2 and 3, although I've only tested the latest version on 2.7 and Win7. The loop should be good for polling code, and the tread should work with more server-like code. It seems to work nicely with the waitress wsgi server that does not have a standard way to shut down gracefully.

    I would also like to note that there seems to be loads of examples out there, like this that are almost useful, but in reality misleading, because they have cut and pasted other examples blindly. I could be wrong. but why create an event if you never wait for it?

    That said I still feel I'm on somewhat shaky ground here, especially with regards to how clean the exit from the thread version is, but at least I believe there are nothing misleading here.

    To run simply copy the code to a file and follow the instructions.

    update:

    Use a simple flag to terminate thread. The important bit is that "thread done" prints.
    For a more elaborate example exiting from an uncooperative server thread see my post about the waitress wsgi server.

    # uncomment mainthread() or mainloop() call below
    # run without parameters to see HandleCommandLine options
    # install service with "install" and remove with "remove"
    # run with "debug" to see print statements
    # with "start" and "stop" watch for files to appear
    # check Windows EventViever for log messages
    
    import socket
    import sys
    import threading
    import time
    from random import randint
    from os import path
    
    import servicemanager
    import win32event
    import win32service
    import win32serviceutil
    # see http://timgolden.me.uk/pywin32-docs/contents.html for details
    
    
    def dummytask_once(msg='once'):
        fn = path.join(path.dirname(__file__),
                    '%s_%s.txt' % (msg, randint(1, 10000)))
        with open(fn, 'w') as fh:
            print(fn)
            fh.write('')
    
    
    def dummytask_loop():
        global do_run
        while do_run:
            dummytask_once(msg='loop')
            time.sleep(3)
    
    
    class MyThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            global do_run
            do_run = True
            print('thread start\n')
            dummytask_loop()
            print('thread done\n')
    
        def exit(self):
            global do_run
            do_run = False
    
    
    class SMWinservice(win32serviceutil.ServiceFramework):
        _svc_name_ = 'PyWinSvc'
        _svc_display_name_ = 'Python Windows Service'
        _svc_description_ = 'An example of a windows service in Python'
    
        @classmethod
        def parse_command_line(cls):
            win32serviceutil.HandleCommandLine(cls)
    
        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.stopEvt = win32event.CreateEvent(None, 0, 0, None)  # create generic event
            socket.setdefaulttimeout(60)
    
        def SvcStop(self):
            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                                servicemanager.PYS_SERVICE_STOPPED,
                                (self._svc_name_, ''))
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            win32event.SetEvent(self.stopEvt)  # raise event
    
        def SvcDoRun(self):
            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                                servicemanager.PYS_SERVICE_STARTED,
                                (self._svc_name_, ''))
            # UNCOMMENT ONE OF THESE
            # self.mainthread()
            # self.mainloop()
    
        # Wait for stopEvt indefinitely after starting thread.
        def mainthread(self):
            print('main start')
            self.server = MyThread()
            self.server.start()
            print('wait for win32event')
            win32event.WaitForSingleObject(self.stopEvt, win32event.INFINITE)
            self.server.exit()
            print('wait for thread')
            self.server.join()
            print('main done')
    
        # Wait for stopEvt event in loop.
        def mainloop(self):
            print('loop start')
            rc = None
            while rc != win32event.WAIT_OBJECT_0:
                dummytask_once()
                rc = win32event.WaitForSingleObject(self.stopEvt, 3000)
            print('loop done')
    
    
    if __name__ == '__main__':
        SMWinservice.parse_command_line()
    
    0 讨论(0)
  • 2020-11-22 02:43

    The simplest way is to use the: NSSM - the Non-Sucking Service Manager. Just download and unzip to a location of your choosing. It's a self-contained utility, around 300KB (much less than installing the entire pywin32 suite just for this purpose) and no "installation" is needed. The zip contains a 64-bit and a 32-bit version of the utility. Either should work well on current systems (you can use the 32-bit version to manage services on 64-bit systems).

    GUI approach

    1 - install the python program as a service. Open a Win prompt as admin

    c:\>nssm.exe install WinService
    

    2 - On NSSM´s GUI console:

    path: C:\Python27\Python27.exe

    Startup directory: C:\Python27

    Arguments: c:\WinService.py

    3 - check the created services on services.msc

    Scripting approach (no GUI)

    This is handy if your service should be part of an automated, non-interactive procedure, that may be beyond your control, such as a batch or installer script. It is assumed that the commands are executed with administrative privileges.

    For convenience the commands are described here by simply referring to the utility as nssm.exe. It is advisable, however, to refer to it more explicitly in scripting with its full path c:\path\to\nssm.exe, since it's a self-contained executable that may be located in a private path that the system is not aware of.

    1. Install the service

    You must specify a name for the service, the path to the proper Python executable, and the path to the script:

    nssm.exe install ProjectService "c:\path\to\python.exe" "c:\path\to\project\app\main.py"
    

    More explicitly:

    nssm.exe install ProjectService 
    nssm.exe set ProjectService Application "c:\path\to\python.exe"
    nssm.exe set ProjectService AppParameters "c:\path\to\project\app\main.py"
    

    Alternatively you may want your Python app to be started as a Python module. One easy approach is to tell nssm that it needs to change to the proper starting directory, as you would do yourself when launching from a command shell:

    nssm.exe install ProjectService "c:\path\to\python.exe" "-m app.main"
    nssm.exe set ProjectService AppDirectory "c:\path\to\project"
    

    This approach works well with virtual environments and self-contained (embedded) Python installs. Just make sure to have properly resolved any path issues in those environments with the usual methods. nssm has a way to set environment variables (e.g. PYTHONPATH) if needed, and can also launch batch scripts.

    2. To start the service

    nssm.exe start ProjectService 
    

    3. To stop the service

    nssm.exe stop ProjectService
    

    4. To remove the service, specify the confirm parameter to skip the interactive confirmation.

    nssm.exe remove ProjectService confirm
    
    0 讨论(0)
  • 2020-11-22 02:47

    https://www.chrisumbel.com/article/windows_services_in_python

    1. Follow up the PySvc.py

    2. changing the dll folder

    I know this is old but I was stuck on this forever. For me, this specific problem was solved by copying this file - pywintypes36.dll

    From -> Python36\Lib\site-packages\pywin32_system32

    To -> Python36\Lib\site-packages\win32

    setx /M PATH "%PATH%;C:\Users\user\AppData\Local\Programs\Python\Python38-32;C:\Users\user\AppData\Local\Programs\Python\Python38-32\Scripts;C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\pywin32_system32;C:\Users\user\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\win32
    
    1. changing the path to python folder by

    cd C:\Users\user\AppData\Local\Programs\Python\Python38-32

    1. NET START PySvc
    2. NET STOP PySvc
    0 讨论(0)
  • 2020-11-22 02:53

    This answer is plagiarizer from several sources on StackOverflow - most of them above, but I've forgotten the others - sorry. It's simple and scripts run "as is". For releases you test you script, then copy it to the server and Stop/Start the associated service. And it should work for all scripting languages (Python, Perl, node.js), plus batch scripts such as GitBash, PowerShell, even old DOS bat scripts. pyGlue is the glue that sits between Windows Services and your script.

    '''
    A script to create a Windows Service, which, when started, will run an executable with the specified parameters.
    Optionally, you can also specify a startup directory
    
    To use this script you MUST define (in class Service)
    1. A name for your service (short - preferably no spaces)
    2. A display name for your service (the name visibile in Windows Services)
    3. A description for your service (long details visible when you inspect the service in Windows Services)
    4. The full path of the executable (usually C:/Python38/python.exe or C:WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe
    5. The script which Python or PowerShell will run(or specify None if your executable is standalone - in which case you don't need pyGlue)
    6. The startup directory (or specify None)
    7. Any parameters for your script (or for your executable if you have no script)
    
    NOTE: This does not make a portable script.
    The associated '_svc_name.exe' in the dist folder will only work if the executable,
    (and any optional startup directory) actually exist in those locations on the target system
    
    Usage: 'pyGlue.exe [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'
    Options for 'install' and 'update' commands only:
            --username domain\\username : The Username the service is to run under
            --password password : The password for the username
            --startup [manual|auto|disabled|delayed] : How the service starts, default = manual
            --interactive : Allow the service to interact with the desktop.
            --perfmonini file: .ini file to use for registering performance monitor data
            --perfmondll file: .dll file to use when querying the service for performance data, default = perfmondata.dll
    Options for 'start' and 'stop' commands only:
            --wait seconds: Wait for the service to actually start or stop.
                    If you specify --wait with the 'stop' option, the service and all dependent services will be stopped,
                    each waiting the specified period.
    '''
    
    # Import all the modules that make life easy
    import servicemanager
    import socket
    import sys
    import win32event
    import win32service
    import win32serviceutil
    import win32evtlogutil
    import os
    from logging import Formatter, Handler
    import logging
    import subprocess
    
    
    # Define the win32api class
    class Service (win32serviceutil.ServiceFramework):
            # The following variable are edited by the build.sh script
            _svc_name_ = "TestService"
            _svc_display_name_ = "Test Service"
            _svc_description_ = "Test Running Python Scripts as a Service"
            service_exe = 'c:/Python27/python.exe'
            service_script = None
            service_params = []
            service_startDir = None
    
            # Initialize the service
            def __init__(self, args):
                    win32serviceutil.ServiceFramework.__init__(self, args)
                    self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
                    self.configure_logging()
                    socket.setdefaulttimeout(60)
    
            # Configure logging to the WINDOWS Event logs
            def configure_logging(self):
                    self.formatter = Formatter('%(message)s')
                    self.handler = logHandler()
                    self.handler.setFormatter(self.formatter)
                    self.logger = logging.getLogger()
                    self.logger.addHandler(self.handler)
                    self.logger.setLevel(logging.INFO)
    
            # Stop the service
            def SvcStop(self):
                    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
                    win32event.SetEvent(self.hWaitStop)
    
            # Run the service
            def SvcDoRun(self):
                    self.main()
    
            # This is the service
            def main(self):
    
                    # Log that we are starting
                    servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED,
                                                              (self._svc_name_, ''))
    
                    # Fire off the real process that does the real work
                    logging.info('%s - about to call Popen() to run %s %s %s', self._svc_name_, self.service_exe, self.service_script, self.service_params)
                    self.process = subprocess.Popen([self.service_exe, self.service_script] + self.service_params, shell=False, cwd=self.service_startDir)
                    logging.info('%s - started process %d', self._svc_name_, self.process.pid)
    
                    # Wait until WINDOWS kills us - retrigger the wait for stop every 60 seconds
                    rc = None
                    while rc != win32event.WAIT_OBJECT_0:
                            rc = win32event.WaitForSingleObject(self.hWaitStop, (1 * 60 * 1000))
    
                    # Shut down the real process and exit
                    logging.info('%s - is terminating process %d', self._svc_name_, self.process.pid)
                    self.process.terminate()
                    logging.info('%s - is exiting', self._svc_name_)
    
    
    class logHandler(Handler):
            '''
    Emit a log record to the WINDOWS Event log
            '''
    
            def emit(self, record):
                    servicemanager.LogInfoMsg(record.getMessage())
    
    
    # The main code
    if __name__ == '__main__':
            '''
    Create a Windows Service, which, when started, will run an executable with the specified parameters.
            '''
    
            # Check that configuration contains valid values just in case this service has accidentally
            # been moved to a server where things are in different places
            if not os.path.isfile(Service.service_exe):
                    print('Executable file({!s}) does not exist'.format(Service.service_exe), file=sys.stderr)
                    sys.exit(0)
            if not os.access(Service.service_exe, os.X_OK):
                    print('Executable file({!s}) is not executable'.format(Service.service_exe), file=sys.stderr)
                    sys.exit(0)
            # Check that any optional startup directory exists
            if (Service.service_startDir is not None) and (not os.path.isdir(Service.service_startDir)):
                    print('Start up directory({!s}) does not exist'.format(Service.service_startDir), file=sys.stderr)
                    sys.exit(0)
    
            if len(sys.argv) == 1:
                    servicemanager.Initialize()
                    servicemanager.PrepareToHostSingle(Service)
                    servicemanager.StartServiceCtrlDispatcher()
            else:
                    # install/update/remove/start/stop/restart or debug the service
                    # One of those command line options must be specified
                    win32serviceutil.HandleCommandLine(Service)
    

    Now there's a bit of editing and you don't want all your services called 'pyGlue'. So there's a script (build.sh) to plug in the bits and create a customized 'pyGlue' and create an '.exe'. It is this '.exe' which gets installed as a Windows Service. Once installed you can set it to run automatically.

    #!/bin/sh
    # This script build a Windows Service that will install/start/stop/remove a service that runs a script
    # That is, executes Python to run a Python script, or PowerShell to run a PowerShell script, etc
    
    if [ $# -lt 6 ]; then
            echo "Usage: build.sh Name Display Description Executable Script StartupDir [Params]..."
            exit 0
    fi
    
    name=$1
    display=$2
    desc=$3
    exe=$4
    script=$5
    startDir=$6
    shift; shift; shift; shift; shift; shift
    params=
    while [ $# -gt 0 ]; do
            if [ "${params}" != "" ]; then
                    params="${params}, "
            fi
            params="${params}'$1'"
            shift
    done
    
    cat pyGlue.py | sed -e "s/pyGlue/${name}/g" | \
            sed -e "/_svc_name_ =/s?=.*?= '${name}'?" | \
            sed -e "/_svc_display_name_ =/s?=.*?= '${display}'?" | \
            sed -e "/_svc_description_ =/s?=.*?= '${desc}'?" | \
            sed -e "/service_exe =/s?=.*?= '$exe'?" | \
            sed -e "/service_script =/s?=.*?= '$script'?" | \
            sed -e "/service_params =/s?=.*?= [${params}]?" | \
            sed -e "/service_startDir =/s?=.*?= '${startDir}'?" > ${name}.py
    
    cxfreeze ${name}.py --include-modules=win32timezone
    

    Installation - copy the '.exe' the server and the script to the specified folder. Run the '.exe', as Administrator, with the 'install' option. Open Windows Services, as Adminstrator, and start you service. For upgrade, just copy the new version of the script and Stop/Start the service.

    Now every server is different - different installations of Python, different folder structures. I maintain a folder for every server, with a copy of pyGlue.py and build.sh. And I create a 'serverBuild.sh' script for rebuilding all the service on that server.

    # A script to build all the script based Services on this PC
    sh build.sh AutoCode 'AutoCode Medical Documents' 'Autocode Medical Documents to SNOMED_CT and AIHW codes' C:/Python38/python.exe autocode.py C:/Users/russell/Documents/autocoding -S -T
    
    0 讨论(0)
提交回复
热议问题