# kdwms-print-client-updater **Repository Path**: johnconner/kdwms-print-client-updater ## Basic Information - **Project Name**: kdwms-print-client-updater - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-26 - **Last Updated**: 2026-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # KDWMS 打印客户端更新器 KDWMS 打印客户端的独立更新器:下载安装包 → 校验 → 备份 → 静默安装 → 启动自检 → 失败回滚。 ## 为什么单独做一个程序 主程序是 JavaFX + jpackage 打包的: 1. 运行中 `app\lib\*.jar` 被锁定,客户端自己无法替换自己 2. jpackage 的 launcher 把 JVM 放在一个「Job 关闭即杀死全部子孙进程」的 Job Object 里,客户端一退出,它启动的子进程全部被带走 3. 更新器不能依赖被它更新的东西——用 JRE 的话,runtime 损坏或升级时自己也起不来 所以更新器是独立的原生 exe,不依赖 JRE。 ## 技术栈 | 项 | 值 | |---|---| | 语言 | C# | | 运行时 | **.NET Framework 4.8** | | UI | WinForms(进度窗口) | | 目标系统 | Windows 7 SP1 / 10 / 11(x64) | | 编译 | Windows 自带的 `csc.exe` + .NET 4.8 引用程序集,不需要 Visual Studio | ## 编译 ``` build.bat ``` 前提:安装 **.NET Framework 4.8 Developer Pack**(提供 `C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8`)。 脚本用 `csc.exe /nostdlib+ /lib: /r:mscorlib.dll ...` 显式引用 4.8 程序集, 因为系统自带编译器默认按 4.0 编译。产物:`build\KdwmsUpdater.exe`。 > `.NET 3.5` 不作为目标:Win7 自带 3.5,但 Win10/11 默认不启用 NetFx3,反而更麻烦。 > 4.8 由安装包负责安装(见下文),一次装好长期受益。 ## 命令行参数 | 参数 | 必填 | 说明 | |---|---|---| | `--url` | ✅ | 安装包下载地址(支持 http/https,自签证书已放行) | | `--sha256` | | 安装包 SHA-256,不匹配则删除重下(最多 3 次) | | `--version` | | 目标版本号,仅用于窗口标题 | | `--installer` | | 安装包保存路径,默认 `%LOCALAPPDATA%\KDWMS-Print-Client\updates\KDWMS-Print-Client-Setup.exe` | | `--install-dir` | | 安装目录,默认按注册表等规则自动解析 | | `--main-exe` | | 主程序路径(值可带引号,解析时剥离) | | `--main-pid` | | 主程序进程号,用于等待其退出(30 秒超时后强杀) | | `--backup-dir` | | 备份目录,默认 `%LOCALAPPDATA%\KDWMS-Print-Client\backup\last` | | `--detached` | | 内部使用:已由调用方创建于 Job 之外,跳过自 detach | | `--dry-run` | | 只下载 + 校验,不安装(排障用) | ## 更新流程 ``` 1. 体检 磁盘 ≥ 500MB、安装目录可写、Mutex 防重复运行 2. 下载 先写 .part,完整后改名;失败重试 3 次 3. 校验 SHA-256(未提供则跳过) 4. 等退出 按 PID 等待 30 秒,超时强杀;再按 exe 名兜底 5. 备份 app\ 全量 + 安装根目录文件(不含 runtime\) 6. 安装 setup.exe /SILENT /SUPPRESSMSGBOXES /NORESTART /CLOSEAPPLICATIONS /LOG=... 7. 校验 返回码 0 或 3010,且安装目录下主程序存在 8. 启动 拉起主程序 9. 等进程 20 秒内进程存活则记录成功;未启动只提示,不回滚 ``` ## 关键设计 ### 进程脱离(重要) 客户端(jpackage 产物)调用本程序时,普通 `ProcessBuilder` / `ShellExecute` / `cmd start` 创建的进程都会留在客户端的 Job Object 里,客户端退出即被杀死。 **解决办法**:客户端通过 **WMI** 创建进程: ```powershell Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine='...'} ``` 这样新进程的父级是 `WmiPrvSE.exe`,不在客户端的 Job 内。客户端侧启动后会 `waitFor` WMI 返回再退出。本程序在 `--detached=true` 时不重复脱离。 ### 安装目录解析顺序 ``` --install-dir → 注册表 InstallLocation(Inno 写入) → --main-exe 的父目录 → 当前目录 ``` 不直接用 `--main-exe` 的目录,因为开发环境(IDE 直接跑源码)时它指向项目目录而非安装目录。 ### 安装结果判定 **不能用「文件时间是否变新」判断**:Inno Setup 复制文件时保留源文件时间戳, 同版本重装时间戳不变,会被误判为「没装上」。 正确判据:**返回码 0/3010 + 安装目录下主程序存在**。 ### 回滚 | 触发条件 | 动作 | |---|---| | 安装返回码非 0/3010 | 从备份还原 `app\` + 根目录文件,启动旧版本 | | 安装后主程序不存在 | 同上 | | 还原自身失败 | 窗口显示备份路径,人工恢复 | 还原时跳过 `KdwmsUpdater.exe`(正在运行,无法覆盖)。 备份目录默认 `%LOCALAPPDATA%\KDWMS-Print-Client\backup\last`,成功后保留(下次覆盖)。 ### .NET 运行时 安装包内置 .NET 4.8 离线安装包,安装时检测注册表: ``` HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\Release >= 528040 ``` 不满足则先静默安装 4.8,再继续。已装 4.8 的机器完全跳过该步骤。 > Win7 SP1 安装 .NET 4.8 需要先装 **KB4019990**(D3DCompiler_47 依赖),否则 4.8 安装程序会报错退出。 ## 日志 | 文件 | 内容 | |---|---| | `%LOCALAPPDATA%\KDWMS-Print-Client\logs\updater.log` | 本程序全流程日志 | | `%LOCALAPPDATA%\KDWMS-Print-Client\logs\setup-install.log` | Inno Setup 自己的安装日志(每个文件的处理结果) | ## 已知限制 - `runtime\` 不参与自动更新(换 JDK 属于大版本,需要重新安装) - 安装包未做数字签名,若现场有 SmartScreen / 杀软拦截需加白名单 - 静默安装到 `Program Files` 需要管理员权限,会弹 UAC