ARTICLE DETAIL

资讯详情

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

NSSM 2.10:把普通exe变成可靠Windows服务的服务包装器

NSSM 2.10:把普通exe变成可靠Windows服务的服务包装器 简介NSSMNon-Sucking Service Manager2.10 版压缩包面向需要将普通可执行程序注册为 Windows 服务的开发与运维人员目标是简化自定义程序的后台运行管理。它能把任意 exe 配置成开机自启的服务并支持启动参数、依赖项、异常处理与错误日志记录弥补系统自带服务管理工具对非标准程序配置繁琐的不足适用于后台数据同步、日志监控、自动化脚本等持续运行场景。该安装包共 24 个文件、158KB包含 7 个头文件、6 个 C 源文件便于阅读服务注册、进程控制与配置写入等核心逻辑同时提供 win32 与 win64 两个平台的可执行文件以及工程文件与说明文档使用户既能直接运行工具也能基于源码进行二次开发理解服务创建与权限设置的细节。已有 223 人学习下载适合需要快速实现程序后台化、无人值守运行的场景尤其对 Windows 10 环境做了兼容验证。1. NSSM 2.10 是什么win10 上把普通 exe 变成可靠后台服务的“服务包装器”很多在 win10 上跑私有工具的人都经历过这种尴尬写好的程序直接双击运行没问题一关命令行窗口进程就没了把 bat 丢进“启动”文件夹开机弹黑色窗口还经常不执行服务管理器里永远看不到它。NSSM 2.10 就是专门治这个问题的工具一个几百 KB 的 exe不需要安装就能把任意普通程序包装成 Windows 服务让它在系统启动时自动运行、崩溃后自己重启、日志重定向到文件。适合的场景很具体Nginx、MySQL 5.7、内网穿透工具、定时任务脚本凡是“得常驻但不想老开个窗口”的程序都可以交出去。我最初接触它是因为要给一台 win10 机器部署内网应用用过之后就把“服务包装”这件事从玄学变成可重复执行的方案。2. 为什么选 NSSM 而不是 sc.exe、winsw、srvany服务包装器的选型对比2.1 sc.exe 装不了普通程序Windows 服务接口与包装器的存在理由不少人第一次接触 Windows 服务时都想过用 sc.exe 直接把 exe 路径塞进去不就行了标准答案是不行。Windows 服务不是“开机自动运行的程序”这么简单它必须向服务控制管理器SCMService Control Manager注册一个服务主循环实现 ServiceMain 回调并响应启动、暂停、停止这几类控制事件。普通命令行程序没有这套接口你用 sc.exe 创建一个指向 mydaemon.exe 的服务启动时大概率看到的是“错误 1053服务没有及时响应启动或控制请求”或者服务进程刚起来就退出西南 CM 里留下一个“已停止”的列表项。包装器Wrapper就是来补这层接口的。NSSM 的做法很直接把 nssm.exe 本身注册成服务宿主SCM 启动的是 nssm.exe然后 nssm.exe 再创建子进程跑你的程序。这样 SCM 这边看到的是一个合格的服务进程而你的程序不需要关心任何服务接口细节。任务管理器里你会看到两个进程——nssm.exe 和你的 app.exe当 app.exe 崩溃时nssm.exe 还在只有它才知道下一步该重启还是退出。理解这个进程模型很重要。很多人在“服务启动后立刻停止”的坑里转了半天就是因为没搞明白 NSSM 负责监控子进程而不是把自己伪装成那个应用。你手动跑 exe 一切正常和它作为子进程被拉起来是两个完全不同的环境工作目录、环境变量、登录会话都不一样。后面章节里的参数几乎都是在给这个“子进程环境”做修正。2.2 NSSM、winsw、srvany 的对比从 Nginx 注册服务的常见争议说起社区里很长一段时间有个高频问题Nginx 服务用 winsw 还是 nssm。我一般直接回答 nssm原因往下看。除了这两者还有一个更老的 srvany来自 Sysinternals 的资源套件现在基本只在老教程里出现。对比项NSSM 2.10winswsrvany配置方式命令行 GUI 并存手写 XML注册表参数进程守护与自动重启内置按退出码动作部分版本支持策略较弱只负责拉起不守护日志重定向与轮转内置支持按大小轮转需另配重定向工具不支持服务依赖管理支持 DependOnService支持不支持环境变量注入AppEnvironmentExtraXML 里配置不直接支持对 win10 的适配好x64 原生依赖 .NET 运行时需要折腾 x64 兼容winsw 的问题不是不能用而是它通过 XML 声明服务行为每台机器要维护一份配置文件NSSM 的服务参数直接写到服务注册表键里命令行一套GUI 一套对“快速部署、少维护”的 win10 个人机器来说更轻。srvany 在这三个里最弱它只管把进程拉起来进程退出后 SCM 会把服务标记为停止没有自动重启、没有日志、没有依赖关系遇到崩溃只能干瞪眼。我做选型时的判断标准很简单如果程序本身没有守护机制就选带守护的如果我要在服务器上维护很多个服务实例就选命令行配置的。NSSM 两点都占。Nginx、MySQL 5.7、Python 脚本、Java 命令行程序只要它是控制台程序NSSM 都能包。反过来带 GUI 界面的程序不适合做成服务因为服务跑在 Session 0用户桌面看不到窗口这不是 NSSM 能解决的是选型上就错了。3. 在 win10 上第一次用 NSSM 2.10 注册服务从解压到开机自启3.1 解压与选型win32 与 win64 目录、版本确认从官网下载 nssm-2.10.zip解压后你会看到 win32 和 win64 两个目录里面各有一个 nssm.exe。win10 基本上直接用 win64除非你的程序是 32 位且宿主机存在兼容性要求。把整个 nssm-2.10 目录放到一个固定位置比如 C:\nssm-2.10然后把 nssm.exe 所在目录加进系统 PATH或者每次调用都写全路径。# 进入解压目录确认版本可用 C:\nssm-2.10\win64\nssm.exe version这条命令会打印 NSSM 的版本信息。如果你用的是 2.10输出里能看到对应版本号如果提示缺少 DLL 或无法运行先检查是否下载了错误的压缩包再看系统是不是 64 位。注意nssm.exe 的位置在注册服务后就不能随便移动SCM 记录的是服务镜像路径的绝对地址你把 nssm.exe 挪走下次开机服务会直接启动失败。我一般会把 nssm.exe 单独复制到一个专用目录比如 C:\tools\nssm64\nssm.exe这样即使以后更新版本也能用新路径重新注册服务不会和别的工具混在一起。3.2 最小命令行注册nssm install 与 start 两条命令注册服务的最小命令是 install它有两种用法只给服务名会弹出 GUI 窗口直接在后面跟程序路径则完全命令行完成。我倾向于直接走命令行方便留痕、方便写脚本。# 以管理员身份打开 cmd 或 PowerShell # 注册一个名为 MyDaemon 的服务程序路径是 C:\app\mydaemon.exe C:\nssm-2.10\win64\nssm.exe install MyDaemon C:\app\mydaemon.exe # 启动服务 C:\nssm-2.10\win64\nssm.exe start MyDaemon # 查看服务状态 C:\nssm-2.10\win64\nssm.exe status MyDaemoninstall 命令里第三个位置是程序的完整路径如果程序还需要启动参数可以继续往后面跟比如nssm.exe install MyDaemon C:\app\mydaemon.exe --port 8080这些参数会被保存为 AppParameters。服务名建议不要带空格虽然 Windows 允许但后续 sc.exe、服务管理器的操作里带空格名字总是多一层转义麻烦。我在 win10 上第一次执行时会盯着 status 的输出。如果显示 SERVICE_RUNNING说明包装成功如果显示 SERVICE_STOPPED别慌先去看 5.1 的排查思路大概率是工作目录或路径问题。这里的核心逻辑是NSSM 已经把服务注册好并尝试拉起子进程服务状态反映的是整个生命周期不是程序本身。3.3 用 GUI 补全细节五个选项卡在配置什么命令行注册完服务后运行nssm edit MyDaemon可以重新打开 GUI。这个界面值得花十分钟过一遍因为它把 NSSM 对服务生命周期管到什么程度都摆出来了。第一个选项卡 Application负责设置程序路径、启动目录和参数。启动目录就是前面反复强调的 AppDirectory这里可以看到它的默认值。第二个选项卡 Shutdown配置停止服务时 NSSM 怎么对待子进程先尝试发送 CtrlC 或关闭消息超时后强制终止进程树超时时间可以自行调整。第三个选项卡 Exit actions按退出码决定动作。比如程序退出码为 0 时服务正常停止退出码为 1 时自动重启。第四个选项卡 I/O把 stdout 和 stderr 重定向到文件还能设置按大小轮转。第五个选项卡 Log on设置服务以哪个身份运行默认是 LocalSystem。GUI 里点“Install service”或者改完点“Save”都会写注册表之后用命令行再看也能读到同一组参数。这种 GUI 和命令行共存的设计是这个工具口碑好的重要原因新手可以看着界面理解每一项老手全部用脚本批量处理。我在实际部署中GUI 只用来第一次观察参数结果最终都会把参数固化成脚本避免“改过就忘”。4. 服务调稳的 6 个 NSSM 参数启动目录、崩溃自愈与日志轮转4.1 AppDirectory启动目录错了服务起来也等于白起很多程序都假设“当前目录等于程序目录”于是用相对路径读配置文件、读模型文件、写日志。双击运行时这个假设成立因为 shell 会把当前目录切到可执行文件所在位置通过 NSSM 拉起时默认工作目录是 C:\Windows\System32相对路径全部指错。结果就是服务进程在但程序实际是带着错误配置在跑或者直接初始化失败退出。# 把服务的工作目录显式指向程序所在目录 nssm.exe set MyDaemon AppDirectory C:\appAppDirectory 是整个 NSSM 配置里第一优先级的参数。哪怕程序用的是绝对路径我也建议显式设置它因为很多程序会在运行中动态生成相对路径的临时文件你不给一个明确的起点它就把临时文件写在系统目录里权限不足时又是一堆莫名奇妙的问题。你在 GUI 里填 Application 路径时NSSM 不会自动帮你填充启动目录这个设计有点反直觉也是新手最容易漏掉的一步。4.2 AppExit 与 AppRestartDelay让崩溃进程自己爬回来程序崩溃后自动重启是 NSSM 相对 sc.exe 最大的价值。它按退出码来决定动作而不是简单地“进程没了就拉起来”。动作有三个Restart 重启、Ignore 忽略、Exit 让服务结束。默认情况下退出码 0 视为正常退出服务随之停止非 0 退出码默认动作是 Restart。很多时候程序崩溃退出码是不确定的所以我会用一个 Default 规则覆盖所有未显式列出的退出码。# 除 0 之外的任何退出码都交给重启逻辑 nssm.exe set MyDaemon AppExit Default Restart # 重启动作前等待 5 秒避免频繁拉起 nssm.exe set MyDaemon AppRestartDelay 5000AppRestartDelay 单位是毫秒5000 意味着程序退出后等 5 秒再重启。这个延迟很重要如果程序是因为抢占端口失败或者依赖还没就绪而退出立刻重启只会以同样的原因再次退出形成“崩溃-重启-崩溃”死循环。我一般给数据库类程序设 5000 到 10000 毫秒给轻量脚本设 2000 毫秒左右。还有一个经验如果程序需要初始化外部资源放弃“退出码精确配置”的思想直接用 Default Restart 最省心。4.3 AppStdout、AppStderr 与 AppRotateBytes日志重定向与大小轮转控制台程序的输出默认写到标准输出和标准错误做成服务后这些流没人接不重定向就等于丢日志。NSSM 允许分别设置 stdout 和 stderr 的输出文件这是排查问题的关键手段。我在线上服务上基本都配置日志文件否则程序崩了连一句报错都看不见。# 标准输出写文件 nssm.exe set MyDaemon AppStdout C:\app\logs\out.log # 标准错误写文件 nssm.exe set MyDaemon AppStderr C:\app\logs\err.log # 单个日志文件超过 10MB 时轮转 nssm.exe set MyDaemon AppRotateBytes 10485760 # 最多保留 20 份轮转文件 nssm.exe set MyDaemon AppRotateFiles 20AppRotateBytes 按字节计算10485760 就是 10MB超过这个值后 NSSM 会把当前文件改名为带时间戳的历史文件然后重新写新文件。默认是不轮转的也就是日志会无限增长总有一天把磁盘占满。这里有个经常翻车的细节NSSM 不会帮你创建日志目录路径里的目录必须提前存在否则输出文件根本不会生成日志被静默丢弃表面看服务是好的实际黑匣子一只。我在部署脚本里一定会先New-Item -Force建目录。4.4 DependOnService 与 AppEnvironmentExtra启动顺序与环境变量服务之间如果有先后依赖Windows 的 SCM 支持声明依赖关系NSSM 只是把这个能力暴露成参数。典型场景是应用依赖 MySQL 5.7 或依赖网络服务不让 SCM 知道开机时应用可能先启动等数据库好了它已经失败退出。# 声明 MyDaemon 依赖 MySQL 服务 nssm.exe set MyDaemon DependOnService MySQL # 注入额外的环境变量多个变量用空格分开 nssm.exe set MyDaemon AppEnvironmentExtra JAVA_HOMEC:\jdk11 TZAsia/ShanghaiDependOnService 后面填的是服务名不是显示名用sc.exe query看到的 Name 字段为准。一个服务可以依赖多个服务多个依赖按空格分隔。SCM 在启动 MyDaemon 之前会尝试把 MySQL 先拉起来这个顺序是系统保证的。AppEnvironmentExtra 解决的是“程序需要某个环境变量但系统里没设”的问题。我之前部署 Java 命令行程序时直接在服务参数里注入 JAVA_HOME比改全局系统环境变量干净得多一台机器可以同时存在用不同 JDK 版本的服务互不污染。5. NSSM 在 win10 上的避坑清单5 个常见的服务翻车现场5.1 服务启动后立刻停止但手动运行 exe 一切正常现象服务注册成功手动运行程序没问题但nssm start后状态马上变回 SERVICE_STOPPED事件查看器里也没有明确报错。原因绝大多数是启动目录问题。服务默认工作目录是 C:\Windows\System32程序里所有相对路径的配置文件、数据文件都找不到初始化失败退出。另一个高频原因是程序依赖的端口或文件被别的进程占着。解决先设置 AppDirectory 指向程序目录再检查程序的启动参数。然后用nssm edit打开 GUI把 stdout/stderr 重定向到文件重新启动后去看错误日志这比猜原因快得多。注意这个坑在 win10 22H2 和 LTSC 2021 上表现一模一样跟系统版本基本无关。5.2 32 位 nssm.exe 启动 64 位程序System32 路径被悄悄改写现象服务能启动但程序日志里出现“找不到 C:\Windows\System32\xxx”的错误可这个文件明明存在或者程序加载的 DLL 全是 32 位版本的。原因32 位进程在 64 位 win10 上访问 System32 时会被文件系统重定向到 SysWOW64。如果你用了 win32 目录下的 nssm.exe 去启动 64 位程序程序的工作目录和系统路径看起来被“魔改”了实际上是从 System32 被重定向到了 SysWOW64。解决检查任务管理器里 nssm.exe 的位数顺手看下它是否带 *32。win10 上用 win64 目录的 nssm.exe 启动程序是唯一推荐做法。这个坑特别隐蔽因为它不报错只是文件错位。5.3 日志文件一个都没生成AppStdout 配置不生效现象AppStdout 和 AppStderr 都设置了服务也运行正常但日志目录里空空如也。原因NSSM 的日志重定向不会自动创建目录。目录不存在时NSSM 打开文件失败但服务本身不会因此报错相当于日志被静默丢弃所有诊断信息全丢进黑洞。解决在配置日志路径前先把目录建好。部署脚本里加一句New-Item -ItemType Directory -Force -Path C:\app\logs。另外确认路径里没有引号残留NSSM 对路径参数的处理比较直接路径里带空格倒是没问题但多余的引号会被当成文件名的一部分。5.4 remove 服务时提示拒绝访问服务卸载不干净现象执行nssm.exe remove MyDaemon时提示 access denied服务在服务管理器里手工删除也报错。原因创建服务时用的管理员权限但当前 PowerShell 或 cmd 没有提升到管理员权限。Windows 的服务控制管理器对删除操作的权限要求很严格普通权限只能读状态不能写配置、不能删服务。解决右键“以管理员身份运行”终端再执行。如果已经用管理员权限还是删不掉先确认服务处于停止状态nssm stop之后再 remove。还有一个细节remove 命令默认要交互确认批量脚本里记得带 confirm 参数避免卡住。5.5 开机时程序依赖网络驱动器映射还没就绪服务已经失败现象程序需要访问某个网络共享手动启动服务正常重启机器后服务启动失败进程反复退出。原因服务在登录用户之前启动网络驱动器映射发生在用户登录会话里。SCM 启动服务时网络驱动器还没挂载程序打开共享路径失败。解决不要把网络驱动器字母比如 Z:写死在程序配置里改用 UNC 路径\server\share。同时在 NSSM 的 Log on 选项卡里给服务指定一个有权限访问共享的账户让服务以指定身份运行避免 LocalSystem 的凭据问题。如果程序必须使用盘符可以用开机延迟执行脚本在服务启动前完成映射。6. 把 NSSM 服务变成一键部署脚本验收方法与交付习惯6.1 一键部署脚本与重启验收把一个服务配置说清楚最好的方式是一段可重复执行的脚本。GUI 一次性的方便解决不了“三个月后换机器重新部署”的场景我一般把 NSSM 操作固化成 PowerShell 脚本连同 nssm.exe 一起放进工具目录。# deploy-svc.ps1管理员身份运行 $ErrorActionPreference Stop $nssm C:\nssm-2.10\win64\nssm.exe $svc MyDaemon $app C:\app\mydaemon.exe # 服务已存在则先停止再删除 $nssm status $svc 2$null if ($LASTEXITCODE -eq 0) { $nssm stop $svc $nssm remove $svc confirm } $nssm install $svc $app $nssm set $svc AppDirectory C:\app $nssm set $svc AppExit Default Restart $nssm set $svc AppRestartDelay 5000 $nssm set $svc AppStdout C:\app\logs\out.log $nssm set $svc AppStderr C:\app\logs\err.log $nssm set $svc AppRotateBytes 10485760 $nssm start $svc # 输出验证信息 $nssm status $svc sc.exe query $svc这段脚本里有个值得注意的点PowerShell 判断原生命令是否成功不是看布尔值而是看$LASTEXITCODE。nssm status在服务存在时返回 0在服务不存在时返回非 0所以if ($LASTEXITCODE -eq 0)判断的是“服务存在”而不是服务正在运行。这个写法绕开了 PowerShell 对退出码的真假判断习惯建议在你的脚本里也这么写不要直接用if ($nssm status $svc)否则会得到反直觉的结果。脚本执行完真正的验收是三个动作第一nssm status和sc.exe query都能看到 RUNNING 状态第二重启一次机器登录之前就在服务管理器里看到服务已经自动运行而不是等你打开终端才发现它没起来第三故意结束 app.exe 的进程等 5 秒左右再看任务管理器新的 app.exe 进程应该出现PID 已经变化。第三条是最容易被省掉的但它验证的是 NSSM 最核心的守护能力。我在 win10 上处理过的 NSSM 服务几十个最深的教训是不要依赖 GUI 改配置而不留记录。GUI 点一下保存很方便但一个月后没人记得当初设置过什么也没人知道为什么 AppExit 配了 Restart。把参数写进脚本随服务一起交付才是对“服务”二字的尊重。希望这些参数和踩坑记录帮到你下次在 win10 上把任意程序变成 Windows 服务时能少走我走过的弯路。本文还有配套的精品资源点击获取
返回列表