pyenv+venv 環境でpywin32によるサービス開始ができない


# 質問内容

後述する環境・設定・ソースにおいて、サービス登録後に開始・成功するにはどうすればよいでしょうか？

# 環境

Windows 10 Pro  
Python 3.9.10 (pyenv+venv)  
pip 23.0.1  
pywin32 305

# 現象

venvでの仮想環境で、以下のサービス登録は成功します。管理ツールｰ>サービスを起動して、一覧にも登録されていることを確認しました。

[code]

    > python app.py install
    Installing service test_pywin32_service
    copying host exe 'C:\...\ENV\lib\site-packages\win32\pythonservice.exe' -> 'C:\...\ENV\pythonservice.exe'
    Service installed
    
[/code]

サービスを実行しようとすると、失敗します。

[code]

    > python app.py start
    Starting service test_pywin32_service
    Error starting service: そのサービスは指定時間内に開始要求または制御要求に応答しませんでした。
    
[/code]

# やってみたこと

## システム環境変数のPathに追加

[code]

    C:\...\ENV\Scripts\  ... python.exeがあります。
    C:\...\ENV\Lib\site-packages\pywin32_system32
    C:\...\ENV\Lib\site-packages\win32
    
[/code]

以下のコマンドでPathに正しく登録されたことを確認しました。

[code]

    echo %Path:;=&echo.%
    
[/code]

この環境変数への適用により、pyenvよりも優先して、venv環境のPythonが実行されることを確認しました。  
具体的には、以下のコマンドにより、インストールされているパッケージリストの違いにより判断しました。

[code]

    python -m pip list 
    
[/code]

# pip インストールパッケージ

[code]

    autopep8==2.0.2
      - pycodestyle [required: >=2.10.0, installed: 2.10.0]
      - tomli [required: Any, installed: 2.0.1]
    flake8==6.0.0
      - mccabe [required: >=0.7.0,<0.8.0, installed: 0.7.0]
      - pycodestyle [required: >=2.10.0,<2.11.0, installed: 2.10.0]
      - pyflakes [required: >=3.0.0,<3.1.0, installed: 3.0.1]
    pip-licenses==4.1.0
      - prettytable [required: >=2.3.0, installed: 3.6.0]
        - wcwidth [required: Any, installed: 0.2.6]
    pip-review==1.3.0
      - packaging [required: Any, installed: 23.0]
      - pip [required: Any, installed: 23.0.1]
    pipdeptree==2.5.2
    pywin32==305
    setuptools==58.1.0
    wheel==0.38.4
    
[/code]

# ソースコード

app.py

[code]

    import win32serviceutil
    import win32service
    import win32event
    import servicemanager
    import logging
    import os
    import pathlib
    import time
    
    log_path = os.getenv("TEMP") + "\\test_pywin32_service\\test-service.log"
    if not os.path.exists(log_path):
        ret_path = os.path.split(log_path)
        pathlib.Path(ret_path[0]).mkdir(
            parents=True,
            exist_ok=True
        )
    
    logging.basicConfig(
        filename=log_path,
        level=logging.DEBUG,
        format="%(asctime)s:LINE[%(lineno)s] %(levelname)s %(message)s"
    )
    
    
    class test_pywin32_service(win32serviceutil.ServiceFramework):
        _svc_name_ = "test_pywin32_service"
        _svc_display_name_ = "TEST Service"
        _svc_description_ = '一定周期でログを出力する'
        _timeout_Milliseconds = 10000
    
        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
    
        def SvcStop(self):
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            win32event.SetEvent(self.hWaitStop)
    
        def SvcDoRun(self):
            servicemanager.LogMsg(
                servicemanager.EVENTLOG_INFORMATION_TYPE,
                servicemanager.PYS_SERVICE_STARTED,
                (self._svc_name_, '')
            )
    
            while True:
                ret = win32event.WaitForSingleObject(
                    self.hWaitStop,
                    self._timeout_Milliseconds
                )
                if ret == win32event.WAIT_OBJECT_0:
                    break
    
                time.sleep(5)
    
                logging.info(__class__.__name__)
    
    
    if __name__ == '__main__':
        logging.debug('start')
        win32serviceutil.HandleCommandLine(test_pywin32_service)
    
[/code]




ご紹介いただいた以下のサイトを参考にしたところ、サービス起動が成功しました。  
[How to Run Python Script as a Service (Windows &
Linux)](https://oxylabs.io/blog/python-script-service-guide)

最終的にPyInstallerでの提供を考えていたので、とても助かりました。

# ソースコード修正

[code]

    import win32serviceutil
    import win32service
    import win32event
    import servicemanager
    import logging
    import logging.handlers
    import os
    import pathlib
    import sys
    import time
    
    
    def _logger_():
        log_path = os.getenv("TEMP") + "\\test_pywin32_service\\test-service.log"
        if not os.path.exists(log_path):
            ret_path = os.path.split(log_path)
            pathlib.Path(ret_path[0]).mkdir(
                parents=True,
                exist_ok=True
            )
    
        logging_format = '%(asctime)s %(threadName)s %(levelname)s:%(message)s'
        formatter = logging.Formatter(logging_format)
    
        root = logging.getLogger()
    
        handler = logging.handlers.RotatingFileHandler(
            filename=log_path,
            mode="a",
            maxBytes=1*1024*1024,
            backupCount=2,
        )
        handler.setFormatter(formatter)
        root.addHandler(handler)
        root.setLevel('DEBUG')
    
        return root
    
    
    class test_pywin32_service(win32serviceutil.ServiceFramework):
        # サービス名
        _svc_name_ = "test_pywin32_service"
        # 表示名(サービス画面にはこれを表示)
        _svc_display_name_ = "TEST Service"
        # サービスの説明
        _svc_description_ = '一定周期でログを出力する'
        # シグナルまでの待ち用タイムアウト時間(今回は10秒 = 10,000ミリ秒)
        _timeout_Milliseconds = 10000
    
        def __init__(self, args):
            self.logger = _logger_()
            self.logger.info('init')
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
    
        def SvcStop(self):
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            win32event.SetEvent(self.hWaitStop)
    
        def SvcDoRun(self):
            self.logger.info('run')
            servicemanager.LogMsg(
                servicemanager.EVENTLOG_INFORMATION_TYPE,
                servicemanager.PYS_SERVICE_STARTED,
                (self._svc_name_, '')
            )
    
            self.logger.info('start')
    
            while True:
                self.logger.info('alive')
                # イベントのシグナル化を10秒待つ
                ret = win32event.WaitForSingleObject(
                    self.hWaitStop,
                    self._timeout_Milliseconds
                )
                # サービス停止(イベントがシグナル化)ならば、処理を中止
                if ret == win32event.WAIT_OBJECT_0:
                    break
    
                time.sleep(1)
    
            self.logger.info('finish')
    
    
    if __name__ == '__main__':
        if len(sys.argv) == 1:
            servicemanager.Initialize()
            servicemanager.PrepareToHostSingle(test_pywin32_service)
            servicemanager.StartServiceCtrlDispatcher()
        else:
            win32serviceutil.HandleCommandLine(test_pywin32_service)
    
    
[/code]

# サービス実行

PyInstallerでexe化

[code]

    PyInstaller --hiddenimport win32timezone -F .\source\app.py
    
[/code]

サービス登録

[code]

    .\dist\app.exe install
    
[/code]

サービスデバッグ起動

[code]

    .\dist\app.exe debug
    
[/code]

サービス開始

[code]

    .\dist\app.exe start
    
[/code]

サービス停止

[code]

    .\dist\app.exe stop
    
[/code]

サービス削除

[code]

    .\dist\app.exe remove
    
[/code]

# 新たな問題

debug起動では正常にログに出力できますが、start起動ではTempにフォルダ作成からできていないことが分かりました。この問題は、別の質問として投稿します。

