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

后端 未结 13 1645
你的背包
你的背包 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: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
    

提交回复
热议问题