PyInstaller生成exe后,Shell脚本传递Windows服务环境参数失败
Windows服务启动时--env参数路径找不到的解决方法
问题概述
使用PyInstaller将service_file.py打包为service_file.exe后,通过PowerShell脚本安装并启动Windows服务:
& 'C:\\files\\service_file.exe' install # 启动服务时传递环境参数报错 & 'C:\\file\\service_file.exe' start --env "D:\\full_project\\device\\config\\windows.env"
单独执行start命令正常,但传递--env参数时提示“路径未找到”错误。
核心原因
- 服务参数传递逻辑错误:Windows服务的启动参数不能直接在
start命令后追加,必须通过install或update命令的--startupargs参数预先配置——服务由Windows服务控制管理器(SCM)启动,不会直接接收start命令后的参数。 - 代码参数解析时机错误:原代码在
MyService.run()中调用argparse.parse_args(),但服务运行时的sys.argv是SCM启动服务的参数,并非执行start命令时传入的--env参数。
解决步骤
1. 修正服务启动脚本
通过--startupargs在安装/更新服务时传递环境参数,而非在start命令后追加:
# 首次安装服务并指定启动参数 & 'C:\\files\\service_file.exe' install --startupargs "--env D:\\full_project\\device\\config\\windows.env" # 若服务已安装,更新启动参数(替换上面的install命令) & 'C:\\files\\service_file.exe' update --startupargs "--env D:\\full_project\\device\\config\\windows.env" # 启动服务 & 'C:\\files\\service_file.exe' start
2. 修改Python代码适配参数传递
调整代码逻辑,让服务框架正确获取并传递启动参数:
- 新增
sys导入(原代码缺失) - 在服务框架类中解析启动参数,传递给业务逻辑类
- 移除
MyService.run()中的argparse解析逻辑
修改后的完整代码:
import os import sys import win32serviceutil import win32service import servicemanager import argparse class MyService: def __init__(self, env_path): self.running = True self.env_path = env_path def stop(self): self.running = False def run(self): application_path = os.path.dirname(sys.executable) os.chdir(application_path) def load_env(envFile): # 提前校验路径存在性,输出明确错误信息 if not os.path.exists(envFile): raise FileNotFoundError(f"环境文件未找到: {envFile}") env_obj = Environment(envFile) return env_obj env_obj = load_env(self.env_path) db_agent(env_obj) db_agent_download(env_obj) client_agent(env_obj) init_scheduler() app = rest_api(env_obj) app.run(port=env_obj.rest_api_port, host="0.0.0.0") class MyServiceFramework(win32serviceutil.ServiceFramework): _svc_name_ = 'DeviceAgent' _svc_display_name_ = 'Device Agent Service' def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) # 解析从--startupargs传递来的服务启动参数 parser = argparse.ArgumentParser() parser.add_argument("--env") parser.add_argument("--log") self.service_args = parser.parse_args(args[1:]) def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) self.service_impl.stop() self.ReportServiceStatus(win32service.SERVICE_STOPPED) def SvcDoRun(self): self.service_impl = MyService(self.service_args.env) self.ReportServiceStatus(win32service.SERVICE_START_PENDING) self.ReportServiceStatus(win32service.SERVICE_RUNNING) self.service_impl.run() def Main(): if len(sys.argv) == 1: servicemanager.Initialize() servicemanager.PrepareToHostSingle(MyServiceFramework) servicemanager.StartServiceCtrlDispatcher() else: win32serviceutil.HandleCommandLine(MyServiceFramework) if __name__ == '__main__': Main()
3. 额外注意事项
- 权限检查:确保服务运行账户(默认是Local System)对环境文件路径有读取权限,若路径在用户私有目录,需手动添加权限。
- 路径校验:在
load_env函数中提前校验文件存在性,能输出更明确的错误信息,便于排查。 - PyInstaller打包:打包时确保所有依赖(如
win32serviceutil、argparse等)都被正确包含,可使用pyinstaller --onefile service_file.py命令打包。
内容的提问来源于stack exchange,提问作者Titan
相关产品推荐
相关产品推荐

