引用本文(DOI: 10.5281/zenodo.21615518)
本文保存于 Zenodo。以下同时提供始终指向最新版本的 DOI,以及固定于您正在阅读版本的 DOI。
小村 豪(2026)。《Windows 应用发布方式怎么选 - MSI / MSIX / ClickOnce / xcopy / 自定义 updater 判断指南》。小村软件有限公司。https://doi.org/10.5281/zenodo.21615518 https://comcomponent.com/zh-CN/blog/2026/03/20/000-windows-app-deployment-msi-msix-clickonce-xcopy-custom-updater/
- DOI(最新版本)
- 10.5281/zenodo.21615518
- DOI(此版本)
- 10.5281/zenodo.22282039
在决定 Windows 应用的发布方式时,人们往往容易从“哪个更新颖”“哪个更简单”开始讨论。 但在实务中真正起作用的,是另一套判断轴线。
- 是想以使用者单位安装,还是安装到整台机器
- 更新是想交给发布平台负责,还是想自己掌握
- 是否涉及服务 / 驱动程序 / shell extension / COM 注册这类 OS 集成
- 是否需要承受封闭网络、离线、USB 分发的场景
- 是否需要 package identity,还是想以纯 Win32 的方式 unrestricted 运行
发布方式的选择,不是 安装程序格式的偏好,而是 要触及 OS 到什么程度 与 由谁承担更新责任 的选择。
flowchart TB
accTitle: 决定发布方式的两条轴线
accDescr: 说明发布方式的选择不是哪个更新颖、哪个更简单这种安装程序格式的偏好,而是要触及OS到什么程度和由谁承担更新责任这两条轴线的选择的图。
a0["“哪个更新颖、哪个更简单”"] -.-> a1["这条轴线上定不下来"]
a2["要触及OS到什么程度"] --> a4["发布方式就定下来了"]
a3["由谁承担更新责任"] --> a4
图1:发布方式不靠偏好决定,而是由 OS 集成的深浅和更新责任这两条轴线决定。
这篇文章面向的是 正在开发 Windows 桌面应用、接下来要决定发布方式,或者想重新审视现有方式 的开发者,以及接手其运维工作的信息系统负责人。文章不以特定的语言或框架为前提。这个领域有很多术语是直接以英语形式流通的,因此把它们整理在了 1.1。开头那份工作表的内容,摘要在 1.2。
1. 先下结论
说得比较粗略,但在实务上很好用的说法是这样:
- 要安装到整台机器、涉及服务或 COM 注册、需要安装前置组件,那就先以 MSI 为起点考虑
- 以 Windows 10/11 为前提,想要 clean install / clean uninstall、频繁更新、package identity,MSIX 更有力
- 想把 .NET 公司内部桌面应用以使用者单位简单分发并自动更新,ClickOnce 到现在依然相当强
- 优先考虑放着就能跑的工具、封闭网络、USB 分发、无需管理员权限,xcopy 最直接
- 想自己掌握更新 UX、渠道、分阶段发布、telemetry、恢复策略,那就是 自定义 updater
- 需要驱动程序,最好不要一开始就以 MSIX 为中心考虑
- 需要 Explorer 的 in-process shell extension,要先确认 MSIX 能支持的范围和 OS 版本条件
粗略总结起来是这样:
- 对 OS 的注册程度很深 → 偏向 MSI
- 想要 package identity 和 modern packaging → MSIX
- 想要 per-user 的简单分发和内置更新 → ClickOnce
- 放置即可运行的分发最优先 → xcopy
- 已经做好自行设计和运维更新基础设施的准备 → 自定义 updater
flowchart TB
accTitle: 5种方式的粗略总结
accDescr: 说明对OS的注册程度很深就偏向MSI,想要package identity和modern packaging就选MSIX,想要per-user的简单分发和内置更新就选ClickOnce,放置即可运行的分发最优先就选xcopy,做好自行设计和运维更新基础设施的准备就选自定义updater的对应关系的图。
b1{"对OS的注册程度是否很深"} -->|"是"| b2["偏向MSI"]
b1 -->|"否"| b3{"是否想要package identity"}
b3 -->|"是"| b4["MSIX"]
b3 -->|"否"| b5{"是否要per-user简单分发+自动更新"}
b5 -->|"是"| b6["ClickOnce"]
b5 -->|"放置即用最优先"| b7["xcopy"]
b7 -.-> b8["做好自行掌握更新基础设施的准备就选自定义updater"]
图2:拿不准时,按注册程度、identity、分发难易的顺序来拆分。
1.1 正文中出现的术语
发布相关的领域有很多术语是直接以英语形式流通的,先在这里列出来。
| 术语 | 中文说法 | 含义 |
|---|---|---|
| package identity | 包标识 | OS 赋予已打包应用的唯一标识。有些 Windows 功能没有它就用不了 |
| authoring | 编写安装程序 | 定义 MSI 内容的工作。含义接近“写一个 MSI” |
| custom action | 自定义动作 | 安装程序的标准动作不够用时,用自己的代码插入处理的机制 |
| ARP | 应用列表 | Add / Remove Programs 的缩写。指“设置”里的“已安装的应用”,或控制面板“程序和功能”中显示的那份列表 |
| clean install / clean uninstall | 干净的安装 / 卸载 | 该装的都装上,删除时不留残余的状态 |
| repair | 修复 | 用安装程序的机制把损坏的安装状态还原回去 |
| telemetry | 使用情况度量 | 收集并掌握更新成败和使用情况的机制 |
| per-user / per-machine | 使用者单位 / 机器单位 | 只装给该用户,还是所有用户共用地安装 |
| shell extension | 资源管理器扩展 | 右键菜单、图标显示等嵌入资源管理器的组件 |
| unrestricted | 无限制 | 不受包的约束,以纯 Win32 的身份自由访问文件和注册表的状态 |
| side-by-side | 并存 | 让多个版本共存于同一台机器 |
| staged rollout | 分阶段发布 | 新版本不一次性发给所有人,而是按比例逐步扩大范围 |
1.2 开头的工作表里有什么
放在开头的那份 Excel 工作表,是为了 把这篇文章的判断套用到自己的项目上并记录下来 而准备的。它有日语版和英语版,各自由 2 个工作表构成。
Planner 表 分成 3 个区块。
- 输入表:填完下面这 7 个项目,就能把首选方案和理由留存下来
- 分发范围 … per-user / per-machine / 两者都要 / 未定
- OS 集成要素 … 无 / 服务 / 驱动程序 / shell extension / COM 注册 / 多项
- package identity … 需要 / 不需要 / 无法判断
- 标准用户安装要求 … 必须 / 不需要 / 视条件而定
- 更新频率 … 手动或极少 / 月度 / 周度 / 更高
- 目标环境 … 封闭网络与离线 / 受管理的较新 Windows / 新旧混杂的 Windows / USB 与现场
- 应用类型备注 … 公司内部 .NET 桌面应用 / 商用产品 / 实用工具 / 混合
- 候选的甄别方法:5 种方式各自的“首先要重点关注的条件”和“容易先排除掉的条件”
- 判断流程:按什么顺序看上面这些项目,以及由此容易靠向哪种方式
Reference 表 原样收录了这篇文章第 3 章的判断表和第 4 章的比较表。
也就是说,它把文章的第 3 章、第 4 章、第 7 章做成了可以按项目逐个填写的形式。如果光靠阅读就能得出结论,那就不必下载。想比较多个项目、想在公司内部留下判断依据时,再用它。
flowchart TB
accTitle: 工作表的用法
accDescr: 说明在Planner表里填完7个项目,用候选的甄别方法和判断流程靠向某种方式,再把首选方案和理由留存下来,从而把判断变成按项目逐个记录的工作表流程的图。
c1["填写输入表的7个项目"] --> c2["用候选的甄别方法缩小范围"]
c2 --> c3["按判断流程靠向某种方式"]
c3 --> c4["写下首选方案和理由"]
c4 -.-> c5["Reference表是第3章和第4章的表格"]
图3:工作表的作用,是把文章里的判断变成按项目留存的记录。
图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 17 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle
2. 这 5 个并不是同一个擂台
这一点相当重要。
MSI / MSIX / ClickOnce / xcopy 主要讨论的是 怎么安装进去。 而 自定义 updater 主要讨论的是 如何承担更新责任。
也就是说,在实务中把它分成两层来考虑会更容易理清思路。
| 层级 | 主要候选 | 要决定的事 |
|---|---|---|
| 首次安装 | MSI / MSIX / ClickOnce / xcopy | 部署到哪里、注册什么、权限、卸载 |
| 持续更新 | MSIX App Installer / ClickOnce / 手动替换 / 自定义 updater | 更新检查、分发源、签名验证、rollback、渠道、UI |
画成图是这样。虚线表示“选了那种安装方式,就能顺理成章接上的更新手段”。
flowchart TB
APP["要分发的 Windows 应用"] --> Q1["首先:怎么安装<br/>首次安装层"]
APP --> Q2["其次:由谁承担更新责任<br/>持续更新层"]
Q1 --> MSI["MSI"]
Q1 --> MSIX["MSIX"]
Q1 --> CO["ClickOnce"]
Q1 --> XC["xcopy"]
Q2 --> AI["MSIX App Installer"]
Q2 --> COU["ClickOnce 的内置更新"]
Q2 --> MAN["手动替换"]
Q2 --> OWN["自定义 updater"]
MSIX -.-> AI
CO -.-> COU
MSI -.-> MAN
XC -.-> MAN
MSI -.-> OWN
XC -.-> OWN
图4:首次安装层与持续更新层的两层模型。虚线是能顺理成章接上的更新手段。
ClickOnce 和 MSIX 只要定了上层,下层也基本随之确定。反过来,MSI 和 xcopy 需要另行决定下层。“更新怎么做”最容易悬而未决的,正是选了这两个的时候。
所以,自定义 updater 不是最先选的方案,而是在现有发布方式无法满足更新需求时才追加选择的方案,这样理解不容易跑偏。
flowchart TB
accTitle: 更新悬而未决的是MSI和xcopy
accDescr: 说明ClickOnce和MSIX只要定了安装方式更新手段也基本随之确定,而MSI和xcopy需要另行决定持续更新层,自定义updater是在更新需求不足时才追加选择的方案的图。
d1["选择ClickOnce / MSIX"] --> d2["更新手段也基本随之确定"]
d3["选择MSI / xcopy"] --> d4["更新怎么做悬而未决"]
d4 --> d5["另行决定持续更新层"]
d5 -.-> d6["不够用时再追加自定义updater"]
图5:自定义 updater 不是最先的选项,而是填补更新层空缺的追加选择。
3. 一张表看懂的判断表
先放一张最实用的判断表。
| 情况 | 首先选择 | 理由 |
|---|---|---|
| 面向全体用户,涉及服务、COM 注册、machine-wide 设置 | MSI | 顺着 Windows Installer 这套体系走,出事的概率更低 |
| 以 Windows 10/11 为前提,想要 clean install / uninstall、频繁更新、package identity | MSIX | 更容易贴合 modern packaging 和更新模型 |
| 想把 .NET 公司内部业务应用以 per-user 方式简单分发 | ClickOnce | 内置的更新模型好用 |
| 放着就能跑的工具、封闭网络、USB、无管理员权限 | xcopy | 尽量不引入“安装”这个概念 |
| 商用产品,想自己掌握更新 UX 和渠道 | 自定义 updater | 比内置更新的自由度更高 |
| 需要驱动程序 | 偏向 MSI 或专用 installer | driver package 是另一个问题,MSIX 不适合 |
| 需要 in-process shell extension | 偏向 MSI 或专用 installer | Windows 11 21H2 及以后可以用 MSIX 注册 legacy context menu handler 等,但需要先确认条件 |
这张表里最重要的一点是:不要仅凭“有更新需求”就直接跳到自定义 updater。
4. 按视角分类的比较
| 视角 | MSI | MSIX | ClickOnce | xcopy | 自定义 updater |
|---|---|---|---|---|---|
| per-user 安装的便利性 | ○ | ○ | ◎ | ◎ | ○ |
| per-machine 安装的便利性 | ◎ | ○ | △ | × | ○ |
| 内置更新 | △ | ◎ | ◎ | × | ◎ |
| package identity | × | ◎ | × | × | × |
| 与服务的适配性 | ◎ | △ | × | × | ○ |
| 与驱动程序的适配性 | △ | × | × | × | ○ |
| 与 shell extension 的适配性 | ◎ | △ | × | × | ○ |
| 封闭网络 / 离线分发 | ◎ | ○ | ○ | ◎ | ◎ |
| 实现与运维成本 | ○ | ○ | ◎ | ◎ | × |
| 更新 UX 的自由度 | △ | ○ | △ | × | ◎ |
这张表要看的重点不是 哪个最强,而是 哪个摩擦最少。
5. 各自适合哪种项目
5.1 MSI
MSI 是想把 Windows 传统桌面应用做到规范的 install / uninstall / repair 时的基准方案。
特别适合的是这类项目:
- 面向全体用户的业务应用
- 包含 Windows 服务的应用
- 涉及 COM 注册、文件关联、machine-wide 设置的应用
- 已经有既有 installer 运维体系的产品
MSI 的优势在于,能用 Windows 的方式清楚表达“应用是如何安装进 OS 的”。
另一方面,它的弱点也很明显:
- authoring 本身其实相当麻烦
- 如果 upgrade / patch 设计得草率,后面很容易受苦
- custom action 越多越容易出问题
- 对高频更新的产品来说,更新 UX 容易变得笨重
flowchart TB
accTitle: MSI的优势与代价
accDescr: 说明MSI能用Windows的方式表达应用是如何安装进OS的,另一方面authoring很麻烦、custom action越多越容易出问题、高频更新时更新UX容易变得笨重的图。
e1["MSI"] --> e2["用Windows的方式表达如何安装进OS"]
e2 --> e3["install / uninstall / repair齐备"]
e1 -.-> e4["authoring其实相当麻烦"]
e4 -.-> e5["custom action越多越容易出问题"]
图6:MSI 用 OS 集成的表达力,换来了 authoring 的复杂度。
5.2 MSIX
MSIX 是想要 modern packaging 以及干净的更新 / 卸载 时的选择。如果想使用依赖 package identity 的 Windows 功能,MSIX 的意义会更大。
适合的场景大致是:
- 可以以 Windows 10/11 为前提的桌面应用
- 更新频率较高的业务应用
- 想使用依赖 package identity 的 Windows 功能的应用
- 想向 Intune 或 App Installer 靠拢的项目
MSIX 的优势在于 更新和卸载的干净程度。
不过,它并不是什么都能装进去。以下 4 点最好一开始就先确认:
- in-process shell extension(Windows 11 21H2 及以后的 MSIX 可以注册 legacy context menu handler 等,但需要在 manifest 中声明并确认目标 OS)
- 驱动程序
- unrestricted 的旧版 Win32 前提
- 不想使用 package identity 的架构
这 4 点各自要查的资料并不相同。不要停在“MSIX 好像不行”,而是把“查哪份资料才能定论”也一并定下来,效率会更高。
| 想确认的事 | 要看的资料 | 能弄清什么 |
|---|---|---|
| 从哪个 Windows 版本开始可用 | Microsoft Learn 的《MSIX features and supported platforms》 | 各项功能所支持的 OS 版本以表格形式给出 |
| 能否注册 in-process shell extension | Microsoft Learn 的《Support legacy context menus for packaged apps》 | 支持的 OS 版本,以及在 manifest 中的声明方法 |
| 自己的安装程序能否 MSIX 化 | Microsoft Learn 的《Prepare to package a desktop application》和《Know your installer》 | 无法打包的架构清单,以及需要事先确认的要点 |
| 包含服务的架构会怎样 | Microsoft Learn 的《Convert an installer that includes services》 | 包含服务的转换条件和限制 |
这些资料的链接都放在第 9 章的参考资料里。做判断时,要看的不是 “是否支持”,而是“从哪个版本开始、用什么样的声明才能支持”。如果不把这里连同 OS 版本条件一起弄清楚,就会出现验证机上能跑、现场的旧版 Windows 却装不上的问题,而且是事后才暴露出来。
flowchart TB
accTitle: MSIX的限制要连确认出处一起定下来
accDescr: 说明shell extension和驱动程序等MSIX上拿不准的4点各自要查的资料不同,不要停在好像不行,而要看清从哪个版本开始用什么样的声明才能支持,从而避免验证机上能跑、现场却装不上的问题的图。
f1["MSIX上拿不准的4点"] --> f2["不要停在“好像不行”"]
f2 --> f3["定下用哪份资料来定论"]
f3 --> f4["看清从哪个版本、用什么声明才支持"]
f4 -.-> f5["避免验证机OK、现场NG"]
图7:MSIX 的限制,要查到 OS 版本条件和声明方法才算定论。
5.3 ClickOnce
ClickOnce 在想把 .NET 公司内部桌面应用以 per-user 方式,快速地连同更新一起运转 的场景下,至今仍然相当强。
适合的场景是这样:
- 面向公司内部的业务应用
- 想以标准用户权限安装
- 以使用者单位分发就足够
- 不想在更新 UX 上投入太多精力
反过来,对于需要深度接触 OS 的产品,或者需要像 installer 那样打包多个前置组件的角色,不要对 ClickOnce 抱太大期待,这样更安全。
5.4 xcopy
xcopy 是 deploy 而不是 install。它没有注册表注册,没有修复功能,也没有 package identity。但只要 放着就能用,它就是最强等级的简单方案。
能发挥优势的是这类工具:
- 诊断工具
- 设备配置工具
- 日志收集工具
- 拿 USB 带到现场使用的实用工具
- 想要 side-by-side 共存多个版本的场景
xcopy 的优势是 失败的方式很容易理解。整个文件夹替换、想恢复就换回旧版本,这种运维方式很好操作。
flowchart TB
accTitle: xcopy是deploy而不是install
accDescr: 说明xcopy不具备注册表注册、修复功能和package identity,代价换来的是放着就能用,可以整个文件夹替换、想回退就换回上一版这种失败方式很容易理解的运维的图。
g1["整个文件夹放过去"] --> g2["直接就能跑"]
g2 --> g3["更新就是整个文件夹替换"]
g3 --> g4["回退就是换回上一版"]
g1 -.-> g5["不具备注册、修复、identity"]
图8:xcopy 的价值在于安装、更新和回退都很简单。
当然,它也有明显的弱点:
- Start menu / ARP / repair
- 文件关联 / 服务 / shell extension / 驱动程序
- 内置更新
5.5 自定义 updater
自定义 updater 与其说是 自由度的选择,更应该说是 责任的选择。
值得考虑的场景是有以下需求时:
- 更新频率高
- 想拥有 stable / beta / preview 这样的渠道
- 想控制分阶段发布或 rollout 比例
- 想细致地控制后台下载、通知、维护时间段
- 想自己掌握更新 telemetry 和 crash recovery
优势很大,但要付出的代价也很大:
- 签名验证
- 分发 manifest
- 重试 / resume
- proxy / firewall / 封闭网络支持
- rollback
- 损坏更新的恢复
- updater 自身的更新
也就是说,增加的不是自由度,而是责任。
flowchart TB
accTitle: 自定义updater是责任的选择
accDescr: 说明自定义updater换来渠道、分阶段发布和telemetry这样的自由度,代价是签名验证、分发manifest、rollback、损坏更新的恢复直到updater自身的更新都要自己设计和运维的责任的图。
h1["得到的:渠道、分阶段发布、telemetry"] --> h3["自定义updater"]
h2["付出的:签名验证、rollback、恢复"] --> h3
h3 --> h4["增加的不是自由度而是责任"]
h2 -.-> h5["updater自身的更新也是自己的活"]
图9:自定义 updater 增加的不是自由度,而是更新基础设施的运维责任。
5.6 定下方式之后,接下来要查什么
即使定下了“就用 MSI”,如果不知道接下来该查什么,工作照样会停下来。这里列出几个有代表性的入口。它们都不是“必须用这个”,而是 选了那种方式之后,先知道名字会有好处的东西。
| 定下的方式 | 接下来要查的 | 补充 |
|---|---|---|
| MSI | WiX Toolset | 用 XML 编写 MSI 的开源工具集。是 MSI authoring 的标准入口 |
| MSI | Advanced Installer、InstallShield | 以 GUI 为主的商用 MSI 制作工具。适合想用 GUI 搭建 custom action 和 upgrade 设计的场合 |
| 不用 MSI、EXE 形式即可 | Inno Setup、NSIS | 制作的不是 MSI,而是自有格式的 EXE 安装程序。不在 Windows Installer 这套体系里 |
| MSIX | MSIX Packaging Tool、makeappx.exe、signtool.exe |
从既有安装程序转换的工具,以及 Windows SDK 的打包和签名工具 |
| MSIX | Windows Application Packaging Project | 在 Visual Studio 里把既有项目 MSIX 化的项目类型 |
| ClickOnce | Visual Studio 的发布向导、mage.exe |
发布和 manifest 的生成。更新的行为由发布时的选项决定 |
| 自定义 updater | Squirrel.Windows、Velopack、WinSparkle、NetSparkle | 提供更新骨架的库。在决定是否采用之前,请先按 如何处理签名验证和 rollback 来比较 |
这里要注意的是,选工具和选方式是两回事。比如 Inno Setup 很好上手,但做出来的不是 MSI,因此以 Windows Installer 为前提的运维,例如用组策略分发软件、用 msiexec 做修复,都用不上。如果 5.1 中选择 MSI 的理由正在于此,那工具也必须与之匹配。
flowchart TB
accTitle: 选工具和选方式是两回事
accDescr: 说明Inno Setup很好上手但做出来的不是MSI,因此用不了组策略分发软件和msiexec修复,需要让工具匹配选择该方式的理由的图。
i1["定下方式"] --> i2["定下工具"]
i2 -.-> i3["Inno Setup不生成MSI"]
i3 -.-> i4["用不了GPO分发和msiexec修复"]
i4 -.-> i5["让工具匹配选择该方式的理由"]
图10:好用的工具,未必站在你选定的那套体系上。
6. 容易犹豫的问题
6.1 是否需要 package identity
如果需要的是依赖 package identity 的 Windows 功能,MSIX 的价值会一下子提升。
反过来,如果需要的是:
- unrestricted 的文件系统访问
- unrestricted 的注册表访问
- elevation / process model 上的自由度
- 想原样保留旧版 Win32 前提
那么偏向 unpackaged 的方式会更自然。
flowchart TB
accTitle: 按package identity的必要性区分
accDescr: 说明如果想要依赖package identity的Windows功能MSIX的价值会一下子提升,如果想要unrestricted的文件和注册表访问、想原样保留旧版Win32前提则偏向unpackaged的方式更自然的图。
j1{"是否想要依赖package identity的功能"} -->|"是"| j2["MSIX的价值一下子提升"]
j1 -->|"想unrestricted地运行"| j3["偏向unpackaged的方式更自然"]
图11:identity 的必要与否,是 packaged 与 unpackaged 的分水岭。
6.2 是否有服务 / 驱动程序 / shell extension
这 3 项会一下子让发布方式变重:
- 驱动程序:MSIX 不适合
- in-process shell extension:MSIX 不适合
- Windows 服务:MSI 很自然,MSIX 在满足条件时也可以作为比较对象
与 OS 结合越深的要素越多,主题就不再是 表面上简单的分发,而是 能否正确地安装、更新、删除。
6.3 per-user 还是 per-machine
如果在这一点上含糊不清地推进,之后一定会出问题。
- 想偏向 per-user
- ClickOnce
- xcopy
- 部分 MSIX
- 想偏向 per-machine
- MSI
- 条件符合时的 MSIX
“想在没有管理员权限的情况下安装”和“想让所有用户从同一个位置使用”并不是同一件事。
flowchart TB
accTitle: 不要混淆per-user和per-machine
accDescr: 说明想偏向per-user就是ClickOnce、xcopy和部分MSIX,想偏向per-machine就是MSI和条件符合时的MSIX,而想在没有管理员权限下安装与想让所有用户从同一位置使用并不是同一件事的图。
k1["想偏向per-user"] --> k2["ClickOnce / xcopy / 部分MSIX"]
k3["想偏向per-machine"] --> k4["MSI / 条件符合时的MSIX"]
k1 -.-> k5["无权限安装与全员共用同一位置是两回事"]
图12:安装范围含糊不清地往前推进,之后一定会出问题。
6.4 更新频率与运维责任
从更新频率的感觉来看,大致是这样:
- 季度到月度更新:MSI 也完全够用
- 月度到周度更新:MSIX / ClickOnce 相当轻松
- 周度到日度更新:出现了考虑自定义 updater 的理由
- 更新可以手动进行,或由部署方控制:xcopy 也足够
发布方式,既是 技术选型,同时也是 运维设计。
flowchart TB
accTitle: 更新频率的台阶
accDescr: 说明季度到月度更新用MSI也完全够用,月度到周度用MSIX或ClickOnce相当轻松,周度到日度会出现考虑自定义updater的理由,更新可以手动进行时xcopy也足够的更新频率参考的图。
m1["季度~月度:MSI也够用"] --> m2["月度~周度:MSIX / ClickOnce轻松"]
m2 --> m3["周度~日度:出现考虑自定义updater的理由"]
m1 -.-> m4["可以手动更新的话xcopy也够用"]
图13:更新频率越高,内置更新和自建基础设施的价值就越大。
6.5 封闭网络 / 离线分发
在封闭网络中,简单 往往比漂亮的 auto-update 更占优势。
- xcopy 很强
- MSI 也很强
- ClickOnce 也可以通过文件共享或 removable media 使用
- MSIX 也可以根据 App Installer 的用法运转
不过,如果要在封闭网络中频繁更新,就必须决定“谁、把新版本放到哪里、旧版本如何保留”,否则即使选好了方式,运维也很容易崩掉。
flowchart TB
accTitle: 封闭网络中简单占优势
accDescr: 说明在封闭网络中简单往往比漂亮的auto-update更占优势,如果要频繁更新就必须决定谁把新版本放到哪里、旧版本如何保留,否则即使选好方式运维也很容易崩掉的图。
n1["封闭网络与离线环境"] --> n2["简单胜过漂亮的auto-update"]
n2 --> n3["xcopy和MSI很强"]
n2 -.-> n4["连谁、把新版放到哪里都要定下来"]
n4 -.-> n5["旧版如何保留也要定下来"]
图14:在封闭网络里,比选方式更先要定的是新版和旧版的存放运维。
7. 犹豫时最后要看的 6 个问题
- 这个应用只需要 current user 就够了,还是需要 machine-wide 安装
- 是否涉及服务 / 驱动程序 / shell extension / COM 注册
- 是否使用依赖 package identity 的 Windows 功能
- 是否只想以标准用户权限安装
- 更新频率是月度、周度,还是更高
- 目标环境是否为封闭网络,OS 版本是否统一
只要回答完这 6 个问题,大致就能看到落脚点。
- 第 2 项为“是” → 先从 MSI 方向考虑
- 第 3 项为“是” → 优先考虑 MSIX
- 第 1 项为 current user、第 4 项为“是”,且是 .NET desktop app → ClickOnce 更有力
- 第 4 项为“是”、第 2 项为“否”,放置即可运维 → xcopy 更有力
- 第 5 项较高,并且想把更新 UX 当作产品价值来掌握 → 把自定义 updater 也纳入比较对象
flowchart TB
accTitle: 从6个问题得出的落脚点
accDescr: 说明有服务和驱动程序等OS集成就先从MSI方向考虑,需要package identity就优先考虑MSIX,current user且标准用户安装的.NET桌面应用就选ClickOnce,放置即可运维就选xcopy,更新频率高且想掌握更新UX就把自定义updater纳入比较对象的落脚点的图。
p1{"是否有OS集成(服务等)"} -->|"是"| p2["先从MSI方向考虑"]
p1 -->|"否"| p3{"是否需要package identity"}
p3 -->|"是"| p4["优先考虑MSIX"]
p3 -->|"否"| p5{"是否per-user+标准用户安装"}
p5 -->|"若是.NET桌面应用"| p6["ClickOnce更有力"]
p5 -->|"若放置即可运维"| p7["xcopy更有力"]
p6 -.-> p8["想掌握更新UX就把自定义updater也纳入比较"]
图15:按这个顺序追着 6 个问题的答案走,落脚点大致就看得见了。
8. 总结
Windows 应用的发布方式,可以相当程度地浓缩成这一句话:
把 如何完成首次安装 与 由谁负责持续更新 分开决定。
在此基础上,大致的实务判断是这样:
- MSI:深度进入 OS 的传统 desktop app
- MSIX:想要 package identity 与 modern packaging / update 的 app
- ClickOnce:想把 per-user 的 .NET 业务应用简单分发并更新
- xcopy:放着就能用的自包含工具
- 自定义 updater:已经做好自行设计和运维更新本身的产品
而最重要的是这一点:
- 如果涉及 驱动程序 / shell extension / 服务,发布方式不是由最后的外观决定,而是由 OS 集成的方式决定
- 如果需要 package identity,MSIX 的意义就很大
- 自定义 updater 是最后的王牌,而不是最先的选项
- 在 封闭网络 中,简单往往比精巧更占优势
如果还在犹豫,先把 是 per-user 还是 per-machine、要向 OS 注册什么、更新频率大概是多少 这 3 点先固定下来,事情就能往前推进很多。
flowchart TB
accTitle: 先固定下来的3点
accDescr: 说明如果还在犹豫,先把是per-user还是per-machine、要向OS注册什么、更新频率大概是多少这3点固定下来,发布方式的讨论就能往前推进很多的图。
q1["是per-user还是per-machine"] --> q4["先固定下来"]
q2["要向OS注册什么"] --> q4
q3["更新频率大概是多少"] --> q4
q4 --> q5["讨论能往前推进很多"]
图16:为发布方式犹豫时,先把这 3 点固定下来。
9. 参考资料
- Microsoft Learn, Windows Installer
- Microsoft Learn, What is MSIX?
- Microsoft Learn, Packaging overview for Windows apps
- Microsoft Learn, MSIX features and supported platforms
- Microsoft Learn, Support legacy context menus for packaged apps
- Microsoft Learn, App Installer file overview
- Microsoft Learn, Prepare to package a desktop application
- Microsoft Learn, Know your installer
- Microsoft Learn, Convert an installer that includes services
- Microsoft Learn, ClickOnce deployment and security
- Microsoft Learn, Manage updates for a ClickOnce application
- Microsoft Learn, Choosing a ClickOnce deployment strategy
- Microsoft Learn, ClickOnce cache overview
- Microsoft Learn, Windows App SDK deployment guide for self-contained apps
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows出现“Windows 已保护你的电脑”提示的原因
整理Windows应用分发时出现SmartScreen警告的原因,从代码签名、EV/OV证书、Azure Artifact Signing、MSIX、Microsoft Store、ClickOnce、企业内部分发到App Control,以实务角度梳理应对方法。
ClickOnce 是什么 - 从实务角度整理其原理、更新机制以及适用与不适用的场景
本文围绕用于 .NET Windows 桌面应用发布的 ClickOnce 技术,结合 Mermaid 图整理其清单(manifest)、更新、缓存、签名机制,以及适用与不适用的项目类型。
自动更新的安全设计——为什么仅靠 HTTPS 还不够
把自动更新当作信任边界来处理,从实务角度整理带签名的 metadata、客户端验证、密钥分离、防回滚措施与 fail-closed 策略。
今天的 Windows 外壳集成——右键菜单、文件关联与 Windows 11 的变化
解读 Windows 11 中右键菜单被藏进“显示更多选项”的原因,并梳理扩展名→ProgID→verb 的关联基础、传统外壳扩展的注意事项,以及 IExplorerCommand 与 MSIX/sparse package 这套新方式。
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
Windows 应用的发布需要把服务、驱动程序、WebView2、WinUI 以及企业内部运维一并纳入方式选择,在动手实现之前先梳理清楚很有效果。
技术咨询 & 设计评审
MSI / MSIX / ClickOnce / xcopy / 自定义 updater 不是 installer 的偏好问题,而是更新责任与 OS 集成的设计,从需求的拆解开始重新审视会更容易做判断。
常见问题
汇总了咨询这一主题时常见的问题。
- MSI 和 MSIX 有什么区别?
- MSI 是 Windows 传统的安装程序格式,适合 per-machine 部署、Windows 服务、COM 注册、shell extension 这类需要深度接触 OS 的安装场景,能够用 Windows 的方式表达 install / uninstall / repair。MSIX 是以 Windows 10/11 为前提的 modern packaging,优势在于 clean install / clean uninstall、频繁更新和 package identity。但另一方面,MSIX 不适合驱动程序,in-process shell extension 只有在 Windows 11 21H2 及以后才有条件支持,也不适合 unrestricted 的旧版 Win32 前提。如果需要深度注册到 OS,就以 MSI 为起点;如果想要 package identity 和更干净的更新体验,则以 MSIX 为起点。
- MSIX 和 ClickOnce 应该怎么选?
- 如果想把 per-user 的 .NET 公司内部业务应用简单地分发出去并实现自动更新,ClickOnce 至今仍是相当有力的选择。它内置的更新模型好用,可以在标准用户权限下安装,也不需要额外打造更新 UX。另一方面,如果需要使用依赖 package identity 的 Windows 功能、重视 clean install / uninstall,或者想向 Intune、App Installer 靠拢,那么 MSIX 更合适。这两者都不适合需要深度接触 OS 的产品,如果涉及服务或驱动程序,就应该从 MSI 的方向考虑。
- ClickOnce 现在还能用吗?不是过时的技术吗?
- 现在依然可以使用,而且在适合的场景中是相当强的选择。适用场景是:想把面向公司内部的 .NET 桌面应用,以使用者单位(per-user)、在标准用户权限下快速分发,并用内置的更新模型来运转。反过来,对于 Windows 服务、驱动程序、shell extension 这类需要深度接触 OS 的产品,或者需要像 installer 那样打包多个前置组件的角色,就不应该期待 ClickOnce 能胜任。从更新频率的角度看,月度到周度左右的更新节奏,用 ClickOnce 或 MSIX 都能相当轻松地运转。
- 什么时候应该考虑自定义 updater?
- 自定义 updater 不是最先该选的方案,而是在现有发布方式无法满足更新需求时才追加考虑的选项。值得考虑的场景是:更新频率高、想要拥有 stable / beta / preview 这样的渠道、想控制分阶段发布或 rollout 比例、想自行掌握更新 telemetry 和 crash recovery。不过,这也意味着签名验证、发布 manifest、重试、rollback、损坏更新的恢复,以及 updater 自身的更新,都需要自己设计和运维,因此应该把它看作是责任增加而不是自由度增加的选择。