Pywin32 Windows服务无法调用主函数,启动超时卡住问题排查
Windows服务启动超时问题排查与修复
核心问题分析
- 函数调用错误:
svc_manager.py中从scamwatch_main.py导入的是scamwatch_main函数而非模块,调用scamwatch_main.scamwatch_main()会触发AttributeError,但因日志捕获逻辑问题未显示该错误。 - 服务环境提权失效:
scamwatch_main中的管理员权限检查在Windows服务环境下无法正常工作——服务进程无法弹出UAC对话框,run_as_admin()直接失败,且sys.exit()会导致服务进程异常终止,使服务卡在"正在启动"状态。 - 重复执行逻辑冲突:
scamwatch_main启动守护线程后立即返回,导致svc_manager的循环每隔3秒重复调用该函数,造成端口阻塞和监控线程重复初始化,引发资源浪费。
解决方案
1. 修复函数调用错误
在svc_manager.py的main方法中,将错误调用:
scamwatch_main.scamwatch_main()
改为直接调用导入的函数:
scamwatch_main()
2. 适配服务环境的权限处理
Windows服务需提前配置管理员账户运行,无需动态提权。修改scamwatch_main.py,拆分命令行与服务环境的权限逻辑:
- 命令行运行时保留原提权逻辑
- 服务环境下跳过提权,避免进程异常终止
3. 修改服务运行逻辑,避免重复执行
svc_manager.py的main方法只需调用一次scamwatch_main,无需循环调用,让主函数内部保持运行状态以等待服务停止信号。
4. 配置服务运行账户
安装服务后,通过services.msc找到ScamWatch Service,在属性→登录选项卡中选择管理员账户,保存后重启服务。
修改后的完整代码示例
svc_manager.py
# this file handles the pywin32 config for using ScamWatch as a service import win32serviceutil import win32service import win32event import servicemanager import socket from scamwatch_main import scamwatch_main import logging import os class SWWinService(win32serviceutil.ServiceFramework): _svc_name_ = "ScamWatchService" _svc_display_name_ = "ScamWatch Service" def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) socket.setdefaulttimeout(180) log_dir = 'Log Directory Path' # 替换为实际日志目录 os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, 'ScamWatch.log') logging.basicConfig( filename=log_file, level=logging.DEBUG, format='%(asctime)s %(levelname)s: %(message)s' ) logging.info("ScamWatch initialized Successfully") def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hWaitStop) logging.info('ScamWatch service stopping') def SvcRun(self): try: servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, '')) self.main() except Exception as e: logging.error(f"Exception in SvcRun: {e}") servicemanager.LogErrorMsg(f"Exception in SvcRun: {e}") self.SvcStop() def main(self): logging.info('ScamWatch service running') try: logging.info("calling main from scamwatch_main.py") scamwatch_main() except Exception as e: logging.error(f"Exception in main loop: {e}") # 无限等待停止信号 win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) logging.info('Service stopped by exiting loop in svc_manager!') if __name__ == '__main__': servicemanager.Initialize() servicemanager.PrepareToHostSingle(SWWinService) servicemanager.StartServiceCtrlDispatcher()
scamwatch_main.py
# This is the main executable for ScamWatch. import threading import time import sys import logging import os from process_blocking import monitor_process, one_time_connection from elevate import is_admin, run_as_admin from port_blocking import block_all_ports def scamwatch_main(): logging.info("ScamWatch is running!") try: # Check and block common remote connection ports logging.info("Blocking remote connection ports if needed...") block_all_ports() # Begin monitoring processes for common RCA App exes logging.info("Now monitoring for RCA applications. Don't worry, you're safe :)") monitor_thread = threading.Thread(target=monitor_process) monitor_thread.daemon = True monitor_thread.start() # 保持主进程运行,直到服务停止 while True: time.sleep(1) except KeyboardInterrupt: logging.info("ScamWatch has been stopped by user!") except Exception as e: logging.error(f"Something went wrong! Here's the error info: {e}") # Run this script as an executable, not as a file to import if __name__ == "__main__": # 命令行运行时保留管理员权限检查 if not is_admin() and "--elevated" not in sys.argv: logging.info("Attempting to restart with administrative privileges...") if run_as_admin(): logging.info("Restarted with administrative privileges.") sys.exit(0) else: logging.error("Failed to restart with administrative privileges.") sys.exit(1) scamwatch_main()
内容的提问来源于stack exchange,提问作者alilprogrammerappears
相关产品推荐
相关产品推荐

