ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

WinSW 把 exe 包装成 Windows 服务:三个文件配置与避坑指南

WinSW 把 exe 包装成 Windows 服务:三个文件配置与避坑指南 简介WinSW 是一款开源轻量级的 Windows 服务包装工具主要面向开发人员与系统管理员用于将 .NET、Java 或自定义可执行程序注册为 Windows 系统服务从而获得自动启动、后台常驻与统一服务管理的能力。本资源包内含 64 位与 32 位两个可执行版本分别适配不同架构的 Windows 系统另附一份最小化 XML 配置示例可据此指定服务名称、启动命令、运行参数与日志记录方式适合需要让后台进程或定时任务长期稳定运行的场景。压缩包为 rar 格式共 3 个文件由 2 个 exe 与 1 个 xml 组成整体约 11.2MB体积小巧、开箱即用。目前已有 544 人学习下载。借助该工具读者可快速完成服务注册与配置并像管理普通 Windows 服务一样控制其启动、停止与恢复同时结合日志排查问题提升后台应用的稳定性与可维护性。1. 把 exe 当服务跑WinSW 三个文件到底怎么配合很多 Windows 上的后台程序比如自研的采集脚本、Java 小工具、Node 写的定时任务开发阶段都是双击一个 exe 或者敲一行命令跑起来的。一旦要交付到客户机器或者生产服务器上问题就来了机器重启后程序不会自己起来用户误关黑窗口进程就没了日志散落一地没法排查。这时候 WinSW 就派上用场了它把任意一个可执行程序包装成标准的 Windows 服务交给系统的服务管理器托管开机自启、崩溃重启、日志重定向这些事全部由系统层面兜住。标题里的三个文件其实是一套最小组合WinSW-x64.exe和WinSW-x86.exe是 WinSW 本体分别对应 64 位和 32 位系统sample-minimal.xml是官方给的最小配置模板。核心玩法就一句话——把 WinSW 的可执行文件改名成你服务的名字旁边放一个同名的 xml 配置文件然后执行安装命令。听起来简单但实际落地时位数选错、路径写错、xml 编码不对都会让你在服务管理器里看到一个起不来的红叉。这篇就把这套组合从选型到排错完整走一遍适合需要把程序做成 Windows 服务但不想碰 sc 命令和注册表的人。2. 三个文件的选型与最小配置先搞清楚谁是谁2.1 x64 和 x86 到底该选哪个WinSW-x64.exe和WinSW-x86.exe的区别只有一个编译目标平台不同。x64 版本只能在 64 位 Windows 上运行x86 版本在 32 位和 64 位系统上都能跑。听起来 x86 兼容性更好是不是无脑选 x86 就行不是。WinSW 本身只是个服务包装器它启动的目标程序才是真正吃资源的主角。如果你的目标程序是 64 位的比如 64 位 JDK、64 位 Python 解释器那 WinSW 也必须用 x64 版本否则 32 位的包装器去拉起 64 位的子进程在某些场景下会出现句柄传递异常或者直接启动失败。判断方法很直接在目标机器上执行# 查看系统架构PROCESSOR_ARCHITECTURE 为 AMD64 表示 64 位 echo %PROCESSOR_ARCHITECTURE%输出AMD64就用WinSW-x64.exe输出x86就用WinSW-x86.exe。如果目标程序本身有位数要求以目标程序的位数为准包装器和被包装程序保持一致最省心。我一般会在交付文档里明确写死用哪个避免运维随手拿错。2.2 sample-minimal.xml 里真正必须改的字段sample-minimal.xml是官方提供的最小可用模板字段不多但每一个都有实际作用。下面是一个改好的版本我把它当成通用起点service !-- 服务在 Windows 服务管理器里显示的名称必须唯一 -- idmy-collector/id !-- 显示名称可以带空格和中文 -- nameMy Collector Service/name !-- 描述鼠标悬停在服务上时显示 -- description采集任务后台服务开机自启/description !-- 要启动的可执行程序建议写绝对路径 -- executableC:\app\collector\collector.exe/executable !-- 传给目标程序的参数多个参数用空格分隔 -- arguments--config C:\app\collector\config.yaml/arguments !-- 工作目录目标程序里的相对路径都基于这里 -- workingdirectoryC:\app\collector/workingdirectory !-- 日志模式append 追加rotate 按大小轮转none 不记录 -- logmoderotate/logmode !-- 日志文件路径不写默认在 exe 同目录 -- logpathC:\app\collector\logs/logpath !-- 开机自启 -- startmodeAutomatic/startmode !-- 崩溃后自动重启延迟 5 秒 -- onfailure actionrestart delay5 sec/ /service几个字段的取舍说明一下。id是服务的内部标识安装后不能随便改改了等于换了一个服务旧的要先卸载。executable强烈建议写绝对路径相对路径在服务上下文里会以C:\Windows\System32为基准这是新手最常翻车的地方。logmode选rotate时默认单文件超过 10MB 轮转保留 8 个历史文件这个默认值对大多数场景够用日志量大的程序可以配合log标签调整。onfailure的restart动作是 WinSW 自己实现的不依赖 Windows 服务恢复策略响应更快。2.3 改名、安装、启动的完整命令序列WinSW 的约定是可执行文件名和 xml 配置文件名必须一致扩展名不同。假设服务叫my-collector操作步骤如下# 1. 建目录把文件放进去 mkdir C:\app\winsw copy WinSW-x64.exe C:\app\winsw\my-collector.exe copy sample-minimal.xml C:\app\winsw\my-collector.xml # 2. 编辑 my-collector.xml填入实际路径和参数 # 3. 以管理员身份打开 cmd安装服务 cd /d C:\app\winsw my-collector.exe install # 4. 启动服务 my-collector.exe start # 5. 查看状态 my-collector.exe statusinstall命令会把服务注册到系统里start立即拉起。如果只想注册不立即启动用install后不执行start服务会在下次开机时按startmode决定是否自启。卸载用my-collector.exe uninstall卸载前会先停止服务。这几条命令建议写进部署脚本避免手工敲错。提示所有 install/uninstall/start/stop 命令都必须在管理员权限的终端里执行普通权限会报「拒绝访问」这个报错信息不直观很多人会误以为是 xml 配置问题。3. 参数调优与日志排查让服务真正稳定跑起来3.1 启动类型、失败重启和延迟启动的参数组合startmode有三个值Automatic、Manual、Disabled。后台常驻服务用Automatic需要人工触发的用Manual。但Automatic有个坑如果服务依赖网络或者数据库开机时这些依赖还没就绪服务会启动失败然后进入重试循环。WinSW 提供了delayedAutoStart来缓解startmodeAutomatic/startmode !-- 延迟自动启动等系统登录后再拉起适合依赖网络的服务 -- delayedAutoStarttrue/delayedAutoStart失败重启策略用onfailure配置可以配多条按顺序匹配!-- 第一次失败等 5 秒重启第二次等 10 秒之后每分钟重启一次 -- onfailure actionrestart delay5 sec/ onfailure actionrestart delay10 sec/ onfailure actionrestart delay1 min/这里的action除了restart还有none、reboot、reload。reboot是重启整台机器慎用。delay的最小粒度是秒写5 sec或5sec都行。实际调优时如果程序启动本身要 30 秒重启延迟设太短会导致反复拉起、端口占用冲突我一般会把首次延迟设成程序冷启动时间的 1.5 倍。3.2 日志重定向stdout 和 stderr 怎么落到文件WinSW 默认会把目标程序的 stdout 和 stderr 重定向到日志文件文件名格式是服务名.out.log和服务名.err.log。logmode控制写入方式logmode 值行为适用场景append追加写入不轮转调试期日志量小rotate按大小轮转默认 10MB/8 份生产环境推荐roll-by-time按时间轮转需要按天归档none不写日志目标程序自己管日志如果目标程序自己写日志文件WinSW 这层可以设none避免重复。但要注意设了none之后程序启动阶段的报错比如找不到配置文件就看不到了排查启动失败时反而麻烦。我的习惯是生产环境用rotate同时程序内部也写自己的业务日志两层日志各管各的。日志路径用logpath指定不指定的话默认在 WinSW exe 所在目录。如果服务以 LocalSystem 账户运行写C:\Program Files下面可能没权限建议单独给一个C:\app\logs这样的目录。3.3 用 status 和系统事件查看器定位启动失败服务装好了但起不来第一步永远是看状态my-collector.exe status输出Started表示在跑Stopped表示停了NonExistent表示服务没注册成功。如果是Stopped接着看日志文件# 看错误日志最后 50 行 powershell -Command Get-Content C:\app\collector\logs\my-collector.err.log -Tail 50日志里没有有用信息的话去 Windows 事件查看器的「Windows 日志 → 应用程序」里找来源为服务名的记录系统层面会记录服务启动失败的具体错误码。常见的错误码 1053 表示服务没有及时响应启动请求通常是目标程序启动太慢或者卡住了1067 表示进程意外终止去看 err.log 里的堆栈。还有一个容易被忽略的点服务账户。默认安装用 LocalSystem权限最大但网络访问走机器账户。如果目标程序需要访问网络共享或者特定用户目录要在 xml 里配serviceaccountserviceaccount usernameDOMAIN\user/username passwordyourpassword/password /serviceaccount密码改了之后要重新 install 一次否则服务会登录失败。这个坑在域环境里特别常见。4. 避坑与常见问题那些让服务起不来的细节4.1 现象install 报「服务已存在」但服务管理器里找不到原因通常是之前用同一个id装过卸载不彻底注册表里残留了记录。WinSW 的uninstall有时候因为服务正在运行或者文件被占用而失败但错误被吞掉了。解决先用sc query 服务id确认服务是否存在存在就sc delete 服务id强制删除然后重新 install。如果sc delete也报错去注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\下找到对应项手动删删之前先停掉服务。4.2 现象服务显示「正在启动」然后超时失败日志为空原因是目标程序启动后没有进入稳定运行状态或者 WinSW 等待启动完成的超时太短。WinSW 默认等待启动的时间有限程序初始化慢就会超时。解决在 xml 里加stoptimeout和启动等待相关配置或者把目标程序的初始化逻辑改成异步先让主进程起来再在后台加载。另一个办法是用waithint告诉 WinSW 多等一会儿!-- 启动后等待 30 秒再检查状态 -- waithint30 sec/waithint4.3 现象服务能启动但程序读不到配置文件原因是工作目录不对。服务上下文里的当前目录默认是C:\Windows\System32程序里用相对路径config.yaml就会去 System32 找。解决xml 里必须配workingdirectory指向程序所在目录。同时检查程序内部是否用了%CD%或者Directory.GetCurrentDirectory()这类依赖当前目录的写法有的话改成基于 exe 路径拼接。4.4 现象日志文件不生成或者生成了但一直是空的原因是logmode设成了none或者logpath指向的目录不存在、没权限。WinSW 不会自动创建多级目录logpath的父目录必须提前建好。解决确认logmode不是none手动mkdir出日志目录并给服务账户写权限。如果服务账户是 LocalSystem一般都有权限如果是自定义账户用icacls授权icacls C:\app\collector\logs /grant DOMAIN\user:(OI)(CI)F4.5 现象改了 xml 配置后重启服务不生效原因是 WinSW 在 install 时把配置读进了服务注册信息改 xml 后必须重新 install 才会生效单纯 restart 用的是旧配置。解决改完 xml 执行my-collector.exe refresh或者uninstall再install。refresh是 WinSW 提供的轻量重载命令比完整卸载安装快但不是所有版本都支持稳妥起见用 uninstall install。5. 进阶多服务共存与配置模板化一台机器上跑多个 WinSW 服务是常态每个服务一套 exe xml互不干扰。但手工维护十几个 xml 容易出错我的做法是抽一个公共模板用脚本生成具体配置。下面这个 PowerShell 片段把服务名、程序路径、参数作为变量批量生成 xml# 定义服务清单每行一个服务 $services ( { Idcollector-a; ExeC:\app\a\a.exe; Args--port 8081; DirC:\app\a }, { Idcollector-b; ExeC:\app\b\b.exe; Args--port 8082; DirC:\app\b } ) foreach ($svc in $services) { # 用 here-string 拼 xml变量直接插值 $xml service id$($svc.Id)/id name$($svc.Id)/name executable$($svc.Exe)/executable arguments$($svc.Args)/arguments workingdirectory$($svc.Dir)/workingdirectory logmoderotate/logmode logpath$($svc.Dir)\logs/logpath startmodeAutomatic/startmode onfailure actionrestart delay10 sec/ /service # 写到对应目录文件名和服务 id 一致 $xml | Out-File -FilePath $($svc.Dir)\$($svc.Id).xml -Encoding UTF8 Write-Host 生成 $($svc.Id).xml }这段脚本的关键点是Out-File -Encoding UTF8。WinSW 读 xml 时对编码敏感用默认的 UTF-16 或者带 BOM 的 UTF-8 在某些版本上会解析失败报「配置文件格式错误」。统一用无 BOM 的 UTF-8 最稳。生成之后把WinSW-x64.exe复制成对应的服务id.exe再逐个 install。验证服务是否真正稳定不能只看status返回 Started。我的习惯是装完后做三件事一是手动taskkill掉目标进程看 WinSW 是否在 10 秒内把它拉起来二是重启机器看服务是否自动启动三是连续跑 24 小时检查日志轮转是否正常、有没有内存泄漏导致的假死。这三步走完基本可以放心交付。最后说个血泪教训xml 里的路径千万别用中文和空格虽然 Windows 支持但 WinSW 在解析arguments时对空格的处理有玄学路径带空格会导致参数被截断。我现在的习惯是所有部署路径统一用英文短横线命名比如C:\app\my-collector省掉一堆转义和引号的麻烦。希望帮到你。本文还有配套的精品资源点击获取
返回列表