win32serviceutil issue with starting a custom service

391 Views Asked by At

I'm trying to create a windows service by using the code below. The global variables are defined, and the imports are properly imported. The main bulk of the code is:

class MyHandler(FileSystemEventHandler):
    def __init__(self):
        self.changed_files = {}
    
    def on_any_event(self, event):
        if event.is_directory or event.event_type == 'modified':
            root, dirs, files = next(os.walk(folder_to_monitor))
            for file_name in files:
                file_path = os.path.join(root, file_name)
                if event.is_directory or file_name in self.changed_files.get(root, set()):
                    self.changed_files[root] = {file_name}
            for dir_path in dirs:
                self.changed_files[os.path.join(root, dir_path)] = set()
        elif event.event_type == 'deleted' or event.event_type == 'created':
            root, file_name = os.path.split(event.src_path)
            self.changed_files[root].add(file_name)
    
    def should_upload_files(self):
        return len(self.changed_files) > 0
    
    def get_changed_files_dict(self):
        return self.changed_files


class CloudService(win32serviceutil.ServiceFramework):
    _svc_name_ = global_service_name
    _svc_display_name_ = global_service_name
    _svc_account_ = global_service_account

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.stop_event = win32event.CreateEvent(None, 0, 0, None)
        self.is_running = False
        self.svc_account = _svc_account_

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.stop_event)
        self.is_running = False

    def SvcDoRun(self):
        self.is_running = True
        self.ReportServiceStatus(win32service.SERVICE_RUNNING)

        while self.is_running:
            event_handler = MyHandler()
            observer = Observer()
            observer.schedule(event_handler, folder_to_monitor, recursive=True)
            observer.start()
            while self.is_running:
                if event_handler.should_upload_files():
                    changed_files = event_handler.get_changed_files_dict()
                    # upload_files_to_server(changed_files)
                    with open("log.txt", "w") as f:
                        f.write(str(changed_files))
                    event_handler.changed_files.clear()
                
                time.sleep(1)


if __name__ == '__main__':
    # Delete the service
    subprocess.call(["sc", "delete", global_service_name])
    
    # Create the service
    python_path = sys.executable
    service_path = os.path.abspath(__file__)

    # print(python_path)

    subprocess.call(
            [
                'sc', 
                'create', 
                global_service_name,
                f'binPath="{python_path} {service_path}"', 
                'start=auto',
            ]
        )

    print(f'\nService "{global_service_name}" created.\n')

    # Set up the service
    win32serviceutil.HandleCommandLine(CloudService)

The goals are:

  1. automatically delete the service (reset for testing), then re-create it, with a specific name/description and to have it be in status of "Running".

  2. When monitoring a folder, any modification or change will be logged in a .txt file on the desktop (for testing)

At the moment the service is being created in the services.msc list, but the status is empty, and manually starting it produces errors:

Error 2: The system cannot find the file specified.

or

Error 1053: The service did not respond to the start or control request in a timely fashion.

Solutions attempted:

  • Tried looking in the forum and saw some answers as to copying the python.dll to site-packages folder but that didn't work.

  • using admin terminal to manually install the .py file, generates same output...

  • in depth conversation with chat-gpt3.5 about possible solutions :) didn't help in the end..


EDIT

After browsing again through some tutorials such as:

https://thepythoncorner.com/posts/2018-08-01-how-to-create-a-windows-service-in-python/

and looking at posts here such as

Python win32 service

I am still stuck. My modified, hopefully simpler code now is:

class CloudService(win32serviceutil.ServiceFramework):
    _svc_name_ = global_service_name
    _svc_display_name_ = global_service_name
    _svc_description_ = global_service_description

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

    def SvcStop(self):
        self.stop()
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self):
        self.start()
        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                              servicemanager.PYS_SERVICE_STARTED,
                              (self._svc_name_, ''))
        self.main()
            
    def main(self):
        # do some stuff


if __name__ == '__main__':
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(CloudService)
        servicemanager.StartServiceCtrlDispatcher()

    else:
        win32serviceutil.HandleCommandLine(CloudService)

If I use terminal with admin to python CloudService.py install then the service appears, which also happened before, but when I try to Start it, I still get the error again:

Error 1053: The service did not respond to the start or control request in a timely fashion.

Any ideas on what this could be...? I'm guessing it's something with user permissions but not clear on what exactly is happening.

1

There are 1 best solutions below

5
On

I think you're missing a space after binPath=. Try:

    subprocess.call(
            [
                'sc', 
                'create', 
                global_service_name,
                'binPath=',
                f'{python_path} {service_path}',
                'start=auto',
            ]
        )

From the documentation:

A space is required between an option and its value (for example, type= own. If the space is omitted, the operation fails.

Another possible issue is that this script is deleting the service as it's being created. That might cause an exception which means win32serviceutil.HandleCommandLine(CloudService) will never be reached. Maybe add another argument to the script and when it's specified don't delete+create the service?

if len(sys.argv) > 1 and sys.argv[1] == 'start':
    win32serviceutil.HandleCommandLine(CloudService)
else:
    # Delete the service
    subprocess.call(["sc", "delete", global_service_name])
    
    # Create the service
    python_path = sys.executable
    service_path = os.path.abspath(__file__)

    subprocess.call(
            [
                'sc', 
                'create', 
                global_service_name,
                'binPath=',
                f'{python_path} {service_path} start',
                'start=auto',
            ]
        )