
简介WinSW是一款开源的Windows服务包装工具这份资源提供了64位与32位两个可执行文件并附带一个典型的XML配置示例面向开发人员和系统管理员用于将Java程序、.NET应用或自定义脚本快速封装为Windows系统服务从而获得开机自启、崩溃恢复、统一服务管理等能力。资源包共3个文件其中两个exe分别适配不同系统架构xml展示了服务名称、启动命令、日志记录等关键参数的写法压缩包整体仅11.2MB结构简单、便于直接上手。目前已有548人学习下载在后台服务部署场景中具备一定参考价值。借助这份工具读者可省去从零编写配置的繁琐步骤根据实际程序修改示例XML并选用对应版本的可执行文件即可将常驻进程或定时任务纳入Windows服务管理体系同时还能利用日志功能辅助排查问题适合开发环境与生产环境的快速落地。1. WinSW把任意 exe 变成 Windows 服务的包装器运维的后悔药如果你手头有个没人维护的 exe 或 bat 脚本却被要求把它做成开机自启、崩溃自动重启的 Windows 服务WinSW 就是为此存在的。它是一个服务包装器你下载 WinSW-x64.exe写一个像 sample-minimal.xml 那样的最小 XML把真实进程的路径和参数填进去跑一遍 install、start这个进程就被 Windows 服务管理器接管了。没有界面、没有额外运行时一条命令装好一条命令卸载。适合做工具交付、无人值守部署、或者临时把某个脚本扶正成后台长任务的场景。初学者照着模板十分钟能跑通老手也能在 XML 里调出重启策略、进程树杀除这些细节。我把这套东西拆开讲清楚读完你就能拿它去处理实际交付任务。2. 先搞懂 WinSW 的工作方式wrapper 进程、XML 模板与 Windows 服务管理器的关系2.1 WinSW 为什么能“不碰业务代码”就管住进程WinSW 本身不是业务程序也不托管任何业务代码。它做的是一件很纯粹的事在 Windows 服务管理器和你真实的可执行文件之间垫一层包装进程。注册服务时服务管理器登记的是 WinSW-x64.exe 这个包装器本身而不是你的 run.exe。服务启动时包装器读取同目录下的 XML 配置文件解析出executable和arguments然后以子进程的方式把你的真实程序拉起来。服务停止时包装器按照 XML 里的stoptimeout、stopexecutable这些配置决定是优雅地通知子进程退出还是强制结束。子进程意外挂掉时包装器看到非零退出码再依据onfailure里的规则决定是否重新拉起、延迟多久拉起。这层包装的价值在于你不需要在业务代码里写任何服务协议相关的逻辑只需要提供一个能被命令行启动的进程。换句话说哪怕你手里只有一堆老脚本、一个早期版本的内部工具也能在不动代码的情况下让它获得服务级别的生命周期管理。我第一次接触 WinSW 是在交付一个无人值守的批处理任务时当时负责的脚本团队早就解散了靠的就是这层包装把脚本扶正成了 Windows 服务从那以后我就养成了习惯凡是需要开机自启的后台程序先看它能不能用 WinSW 包一层。wrapper 进程的另一个作用是吞掉标准输出和标准错误。XML 里通过logpath和logmode控制日志写到哪、怎么滚动。这件事看起来简单实际运维里很多程序自己没有日志能力又不能在明面上加代码wrapper 帮你接管 stdout 就是最省事的方案。安装完成后的基本验证方式是这样的# 先确认服务已经被登记到系统里 Get-Service -Name myservice # 查看服务的详细配置信息确认启动类型和执行路径 Get-CimInstance Win32_Service -Filter Namemyservice | Select-Object Name, State, StartMode, PathName这里Get-Service只能看到运行状态Get-CimInstance才能看到服务对应的可执行文件路径。如果你发现 PathName 指向的不是 WinSW 所在目录说明注册时配置读取有问题需要回头检查 XML 文件是不是和 exe 在同一目录、文件名是否匹配。2.2 sample-minimal.xml 逐字段拆解sample-minimal.xml 是官方包里带的最小模板核心逻辑就五个字段。很多文章把它说得像黑匣子其实拆开看非常直观service idsampleService/id nameSample Service/name description这是一个最小可运行的服务配置/description executableD:\app\demo.exe/executable arguments--config config.yaml/arguments /serviceid是服务在系统里的唯一标识对应服务管理器里的 ServiceName。这个字段有硬性要求不能包含空格最好只用字母、数字和连字符。name是显示名称会出现在服务列表里可以带空格。description是服务描述写在服务属性的“描述”栏里方便后来的人看懂这个服务是干什么的。executable是你要包装的真实程序的绝对路径这里建议写绝对路径不要赌相对路径在各种启动方式下都能正确解析。arguments是传给真实程序的命令行参数没有参数时可以留空也可以干脆不写这个标签。这份模板能跑通安装和启动但离生产可用还差得远。它没有配置开机自启也没有配置进程崩溃后的行为更没有定义日志输出位置。如果你直接拿它去部署结果大概率是服务启动后手动能用、重启服务器后它就躺平了。还有一个容易翻车的细节WinSW 在安装时默认查找与 exe 同名的 XML 文件。也就是说你放在目录里的是 WinSW-x64.exe它会找 WinSW-x64.xml。你下载回来的模板叫 sample-minimal.xml直接执行 install 时它根本不会去读这个文件。所以你第一步要做的就是把 sample-minimal.xml 改名成 WinSW-x64.xml或者安装时用-c参数手动指定配置文件路径。2.3 为什么选 WinSW 而不是 sc create 或 NSSM很多人第一次做服务包装时第一反应是用系统自带的 sc.exe。sc create 确实能注册一个服务但它有两个硬伤一是启动命令行不支持复杂的参数组合二是它只管启动和停止进程一旦崩溃服务管理器只是标记一个失败状态没有任何自动恢复策略。如果进程需要接收特定参数、需要在崩溃后延迟重启sc.exe 基本帮不上忙。NSSM 是另一个常见的包装器功能覆盖面和 WinSW 差不多。区别在于NSSM 的配置方式偏向交互界面虽然也支持命令行参数但落到批量交付、代码评审、版本管理这些场景时你会希望服务的配置是一个能被 diff 的文件。WinSW 把所有配置收敛到一个 XML 里这个 XML 可以进 Git可以走配置管理工具也可以让同事在提交流程里直接 review。方式复杂参数崩溃自动重启日志捕获配置形态sc.exe受限不支持不支持命令行参数NSSM支持支持支持对话框为主WinSW支持支持支持单个 XML 文件我的选择习惯是如果只需要临时把一个程序注册成服务NSSM 也可以但如果要交付到多台机器、后续可能要反复调整重启策略和日志策略WinSW 的单一 XML 形态更利于用一套模板去批量部署。你只要把 XML 当作配置文件来管理服务本身反而是副产品。3. 实战把 WinSW-x64.exe 和 sample-minimal.xml 变成一个能用的 Windows 服务3.1 目录规划与文件摆放下载回来的资源包里有三个核心文件WinSW-x64.exe、WinSW-x86.exe、sample-minimal.xml。首先明确一个选型原则x64 还是 x86看的是操作系统位数不是被包装程序的位数。64 位系统上WinSW-x64.exe 完全可以启动一个 32 位的子进程。如果你的服务器是 32 位系统才需要用到 WinSW-x86.exe。这个选择搞反的话常见的报错是安装成功后服务无法启动事件查看器里留下一句“服务进程无法连接到服务控制器”。推荐的目录结构是这样的D:\tools\my-service\ WinSW-x64.exe WinSW-x64.xml run.bat logs\用 bash 展示目录树是想让大家看清文件之间的位置关系。实际在 Windows 环境里重点就一句话所有文件放在同一个目录XML 文件名与 exe 文件名保持一致。如果你不愿意改 XML 文件名也可以用WinSW-x64.exe install -c sample-minimal.xml的方式显式指定配置但这样每次操作都要多带一个参数不如直接改名省心。那个logs目录可以先建好后面配置logpath时指向这里。注意这个目录必须存在WinSW 不会自动创建深层级目录路径不存在时服务启动会失败。3.2 安装、启动、停止、卸载的完整命令序列安装服务前强烈建议先跑一遍test指令。这个指令会校验 XML 里的配置是否能被正确解析同时尝试启动一次被包装的进程然后立刻停止它。如果配置里 executable 路径写错了或者 XML 编码有问题test阶段就能暴露出来不用等到安装成服务后反复翻日志。# 在管理员权限的 PowerShell 里执行 cd D:\tools\my-service # 第一步验证配置是否合法 .\WinSW-x64.exe test # 第二步安装服务成功后会提示 WMI operation succeeded .\WinSW-x64.exe install # 第三步启动服务 .\WinSW-x64.exe start # 第四步确认服务状态 Get-Service -Name myservice每一步都有对应的检查重点。test的输出如果出现配置解析失败先回头检查 XML 编码和路径。install这一步需要最高权限普通用户执行会直接报“拒绝访问”。start之后不要只看服务状态变成 Running 就放行还要确认你的业务进程真的被拉起来了去任务管理器里找一下 run.bat 对应的进程树。停止和卸载同样有顺序要求# 停止服务 .\WinSW-x64.exe stop # 卸载服务如果不先停止uninstall 也会尝试自动停掉 .\WinSW-x64.exe uninstall卸载之后可以用Get-Service -Name myservice确认服务已经从系统里消失。如果提示服务不存在说明卸载干净了。有人为了图快直接把 WinSW 目录删掉这在注册表里会留下一堆服务项残骸后面重新安装时会遇到各种奇怪冲突。3.3 一份能直接用于生产的完整 XMLsample-minimal.xml 只是起步模板我实际部署时通常会在它基础上补上启动模式、重启策略和日志滚动成型后的配置大约长这样service idmy-service/id nameMy Service/name description后台任务包装服务负责拉起 run.bat/description executableD:\tools\my-service\run.bat/executable arguments/arguments startmodeAutomatic/startmode delayedAutoStarttrue/delayedAutoStart logmoderoll-by-time/logmode logpathD:\tools\my-service\logs/logpath onfailure actionrestart delay10 sec/ onfailure actionrestart delay30 sec/ resetFailureAfter3600/resetFailureAfter /servicestartmode设为 Automatic 表示开机自启delayedAutoStart设为 true 会让服务在系统启动后才延迟启动避开开机时的高负载阶段。logmode用 roll-by-time日志按时间滚动logpath指向你提前建好的日志目录。onfailure可以写多条按出现顺序依次生效第一次失败等 10 秒重启第二次失败等 30 秒重启。resetFailureAfter的单位是秒3600 表示一小时。这个字段很关键如果只写 onfailure 而不写 resetFailureAfter服务会以非常短的时间间隔不停重启形成所谓的“重启风暴”把系统资源全部吃光。注意arguments留空是可以的但是尖括号仍然要写完整。如果把整个标签删掉解读为没有参数也没问题。我一般保留空标签明确表示这是有意的留空后面要加参数时直接在标签对里补充即可。4. WinSW 避坑与常见问题排查五个血泪踩坑记录4.1 服务安装成功了一启动就秒退现象install 提示成功执行 start 后服务状态很快从 Running 变回 Stopped事件查看器里只有一条 Service Control Manager 的报错。原因最常见的是executable路径写错写到不存在的目录其次是 XML 文件编码问题导致中文路径解析失败还有一种是服务器是 64 位系统但用了 x86 的 wrapper。路径带空格本身一般没问题WinSW 对引号的处理还算完善但如果路径里混合了中文和空格编码问题会被放大。解决先执行.\WinSW-x64.exe test它能明确告诉你 executable 是否可启动。如果 test 启动失败优先检查路径是否真实存在。如果 test 成功但服务还是秒退改用绝对路径并确保 XML 保存为 UTF-8 with BOM。最后把服务账户从默认的 LocalSystem 换成有权限启动目标程序的账户这在目标程序依赖网络共享或特定数据库时尤其重要。4.2 服务反复重启形成“重启风暴”现象服务每隔几秒到十几秒自动重启一次系统事件日志里刷满了错误记录CPU 的占用也莫名其妙升高。原因onfailure写了一条永远重启的规则又没有配resetFailureAfter。WinSW 会把每一次异常退出都视为新的失败于是无限地按最短间隔拉起进程。如果业务程序缺少依赖导致启动即崩这个循环会一直持续到有人手动停止服务。解决在 XML 里加上resetFailureAfter3600/resetFailureAfter让失败计数在一小时内重置。同时把onfailure的 delay 从秒级提高到至少 30 秒以上给系统留出喘息空间。onfailure actionrestart delay30 sec/ onfailure actionrestart delay60 sec/ resetFailureAfter3600/resetFailureAfter延迟值可以按业务容忍度调整。一个需要快速恢复的业务30 秒已经是比较保守的下限如果是批处理类任务delay 设到 2 到 5 分钟都合理。这里有个血泪教训我有一回把 delay 设成 5 秒结果目标程序因为缺少一个中间件启动一次要 20 多秒才能确认失败整个上午服务都在反复拉起、反复退出数据库连接池被打爆了。注意 的值是秒不是分钟写错了会以为设置了重置时间却没生效。4.3 XML 里中文内容乱码导致启动失败或描述显示异常现象服务能安装但启动失败或者服务描述在服务管理面板里显示成乱码。原因用记事本保存 XML 时默认是 UTF-8 无 BOMWinSW 的配置解析器在 Windows 环境下可能会把 UTF-8 的中文字节误读为 ANSI。路径里的中文一乱码executable 就解析不到了。解决把 XML 另存为 UTF-8 with BOM 编码。VSCode 里可以点右下角编码按钮选择“Save with Encoding”后选UTF-8 with BOM记事本保存时选“UTF-8带 BOM”也行。在那之后我给人交付配置时都会强调一句XML 保存后用十六进制编辑器看一眼文件头有没有EF BB BF三个字节没有就说明编码不对。4.4 停止服务后子进程还占着端口现象服务已经显示为 Stopped但业务进程还挂在任务管理器里端口也被占着重启服务时报“地址已被占用”。原因WinSW 只管理它直接拉起的那个进程。你的 run.bat 可能又启动了另一个子程序这个子进程不在 wrapper 的进程树监控范围内。停止服务时 wrapper 杀掉了 run.bat 的宿主进程但孙进程成了孤儿继续霸占资源。解决在 XML 里开启父进程优先停止策略或者指定一个专门的停止命令stopparentprocessfirsttrue/stopparentprocessfirst stopexecutabletaskkill /IM run_child.exe /T /F/stopexecutablestopparentprocessfirst设 true 表示先停掉父进程树stopexecutable支持一个外部命令来负责清扫残留进程。我一般更倾向于前者它让 wrapper 按进程树关系逐层结束子进程。如果业务程序有自己的停止脚本也可以在stopexecutable里指向那个脚本。另一个关联参数是stoptimeout默认是 15 秒还是更短我没仔细记过但当进程需要时间做优雅退出时建议把它调到 30 秒以上。4.5 日志总是写不出或者文件越来越大现象配置了logpath但日志目录里什么都没生成或者日志文件无限增长把磁盘写满了。原因日志写不出大多是因为logpath目录不存在WinSW 不会帮你创建文件无限增长则是logmode设成了 append 或 reset这两个模式都不滚动文件。解决先确认目录建好再根据场景选日志模式。append 是往同一个文件持续追加reset 是服务每次重启时清空日志文件roll 是按文件大小滚动比如roll-size后配一个 thresholdroll-by-time 是按时间滚动适合日志量稳定的业务。模式行为适用场景append持续追加到同一个文件临时调试reset重启时清空日志快速排错roll按大小滚动日志量大的业务roll-by-time按时间滚动长期运行的服务我实际部署时都用 roll-by-time配合logpattern可以定义文件名带日期格式方便后续归档。如果某些程序自己打了大量日志注意同时控制 wrapper 捕获 stdout 的节奏别把所有输出都引到同一个文件里。5. 让服务不“假死”用探针脚本检查业务层健康度而不是只看服务状态Windows 服务管理器能告诉你的是 wrapper 进程还活着它无法感知业务层是否真的可用。我遇到过的情况是服务状态是 Running端口还在监听但内部的调度队列已经堵死业务侧报了故障服务管理器却像个没事人一样。这就是服务的“假死”。治本的办法是在业务代码里做健康检查但大部分被 WinSW 包装的程序没有这个能力。退而求其次的做法是写一个探针脚本定期检查业务端口或心跳文件发现异常就把服务拉起来。$tcp Test-NetConnection -ComputerName 127.0.0.1 -Port 8080 -WarningAction SilentlyContinue if (-not $tcp.TcpTestSucceeded) { Restart-Service -Name my-service -Force }Test-NetConnection检测端口是否可连接Restart-Service -Force在服务无响应时也能强制重启。这个脚本本身可以注册成计划任务每两分钟跑一次。注意探针的检查目标应该是对外暴露的业务端口而不是 wrapper 的进程端口。如果业务进程有自己的健康检查接口用 HTTP 探活比 TCP 探活更准确比如检查一个特定 URL 的返回码。从那以后我每次用 WinSW 交付服务都强制走一遍四项检查路径、账户、日志、探针。路径错了服务起不来账户错了起起来没权限日志配错了出事没证据探针不配假死没人发现。这四关过完再复杂的交付也能睡得着觉。希望帮到你。本文还有配套的精品资源点击获取