「换了新电脑之后,明明是同一台打印机,纸张和纸盒的选项却不一样」「保存好的打印设置恢复不回来」「标签打印机从列表里消失了」。为打印驱动程序的停止提供做准备,就是让这些变化发生时业务也不会停下来。
首先要把握的是,驱动程序的停止提供计划,和启用 Windows protected print mode 是两回事。并不是计划的日期一过,现有的打印就会一齐停止。另一方面,在驱动程序的选中方式发生变化的场面,以及启用 Windows protected print mode 的场面,应用程序原本依赖的设置和打印目标会丢失。12
本文面向维护 WinForms、WPF 等 Windows 业务应用程序的开发者,以及配备打印机的管理员,按 什么会变、要查什么、改哪里、怎么验证 的顺序梳理。打印 API 和 PDF 输出方式本身的选型,请参阅上一篇Windows 业务应用程序的打印与 PDF 输出。
前提环境是 Windows 11(WPP 的验证需要 24H2 以后)、PowerShell 5.1 以上(PrintManagement 模块)、C#(.NET 6 以上或 .NET Framework 4.x,System.Drawing.Printing / System.Printing)。难度为中级。
1. 先说结论
不要一律重写打印代码,而是先确认「打印目标是否还在」,之后再修改依赖驱动程序的地方。
判断的顺序分为下面 3 个阶段。
| 顺序 | 要确认的内容 | 下一步行动 |
|---|---|---|
| 1. 确认打印目标 | 队列在 WPP 下是否还在。物理打印机能否用 Windows Ready Print 重新注册 | 如果不在,就先准备另一条输出通道,或者先做出不使用 WPP 的判断 |
| 2. 确认应用程序的依赖 | 是否依赖驱动程序专有设置、队列名、虚拟打印机、RAW 发送 | 修改相应的地方。也要包括 SDK 和报表库内部的依赖 |
| 3. 用实际输出确认 | 即使驱动程序或队列变了,报表、PDF、标签能否正确输出 | 使用 WPP 的环境要启用后验证,不使用的环境按另一套步骤验证 |
如果只是用 PrintDocument 或 FixedDocument 绘制,基本上属于 验证对象而不是改造对象。不过,如果打印目标的队列本身在 WPP 下消失且无法重新注册,那么即使绘制代码没有问题也印不出来。需要先准备另一条通道。
如果要从动手开始,请按 5.1 取清单 → 用第 4 章和 5.1 的表判定打印目标 → 5.2~5.5 确认依赖 → 第 6 章选择通道 → 第 7 章验证 推进。判断时容易犹豫的地方,其背景在第 2~4 章说明。
以下把 Windows protected print mode 简称为 WPP。「队列」指在 Windows 中注册的打印目标,「打印后台处理程序」指接收打印作业并交给输出目标的机制。
图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 39 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle
2. 到底定了什么 ── 3 个阶段的时间表
2.1 结束的是供给与更新,而不是让现有驱动程序一齐停止
一手资料是 Microsoft Learn 的「End of servicing plan for third-party printer drivers on Windows」。计划在 2023 年 9 月公布,2025 年 5 月修订了日期。撰写本文时的计划如下。1
| 时期 | 会变更的内容 | 业务应用程序一侧要注意的事 |
|---|---|---|
| 2026年1月15日 | 在 Windows 11 以后与 Windows Server 2025 以后,不再把新的打印驱动程序上架到 Windows Update。现有驱动程序的更新可通过单独审核进行 | 配备新电脑、新打印机时,未必还能用以往的方式拿到厂商提供的驱动程序 |
| 2026年7月1日 | 把打印驱动程序的排名改为始终优先选择 Windows 自带的 IPP 类驱动程序 | 在 IPP 类驱动程序也匹配的设备上,更换电脑或重新检测打印机时可能选中别的驱动程序 |
| 2027年7月1日 | 除安全修复外,不再接受第三方打印驱动程序的更新 | 不要把停止更新的日期误当成应用程序一侧的备战期限 |
现有驱动程序仍可从 Windows Update 或厂商提供的安装程序安装,Microsoft 表示没有停用 v3/v4 驱动程序功能的计划。也就是说,这是一个 分阶段结束驱动程序供给与更新的计划,而不是在那一天把现场电脑里已经装好的驱动程序停用的计划。1
flowchart TB
accTitle: 驱动程序停止提供会改变什么、不会改变什么
accDescr: 仅靠打印驱动程序的停止提供本身,应用程序绘制 API 的入口和现有驱动程序不会改变,只有在新装或重新检测时选中了别的驱动程序,驱动程序返回的纸张列表、专有功能和队列名才会被替换,而包括虚拟打印机被删除在内的、由启用 Windows protected print mode 带来的变化属于另一套机制,在第 4 章讲解
eos["打印驱动程序的停止提供"]
eos --> keep["不会改变的东西"]
eos --> cond["新装、重新检测时选中了别的驱动程序"]
cond --> change["会被替换的东西"]
keep --> api["绘制 API 的入口"]
keep --> drv["现有驱动程序"]
change --> caps["纸张、纸盒、专有功能"]
change --> qname["队列名"]
图 1: 仅靠停止提供不会改变任何东西,只有在选中了别的驱动程序时,驱动程序原本提供的信息和名称才会被替换。包括虚拟打印机被删除在内的、启用 WPP 带来的变化属于另一套机制(第 4 章)。
2.2 现场里驱动程序发生更换,主要是两个场面
即使计划不会停掉现有驱动程序,在下面的场面里应用程序看到的环境也会改变。
| 场面 | 会发生什么 |
|---|---|
| 更换电脑、重装操作系统、重新检测打印机 | 如果该设备也匹配 IPP 类驱动程序,排名规则会让系统选中与以前不同的驱动程序 |
| 启用 WPP | 使用第三方驱动程序的打印机会被删除。兼容机型用 Windows Ready Print 重新注册,不兼容机型照原样就无法使用 |
当同一个设备匹配多个驱动程序包时,Windows 会给每个包评定排名(rank),并选出最好的那个。2026 年 7 月 1 日的变更,就是在这个选择中优先选 IPP 类驱动程序。在 IPP 类驱动程序不会成为候选的设备上,厂商的驱动程序包仍可能继续被选中。31
flowchart TB
accTitle: 现场中驱动程序被替换的两条路径
accDescr: 在 IPP 类驱动程序匹配的设备上,更换电脑、重装操作系统、重新检测打印机时排名规则会选中 IPP 类驱动程序而使驱动程序被替换,启用 Windows protected print mode 时使用第三方驱动程序的打印机会被删除,能用 Windows Ready Print 重新注册的机型驱动程序被替换,不能重新注册的机型则打印目标消失
site["现场的电脑"]
site --> r1["更换电脑、重装操作系统、重新检测"]
site --> r2["启用 Windows protected print mode"]
r1 --> rank["在匹配 IPP 类驱动程序的设备上优先选它"]
r2 --> del["删除使用第三方驱动程序的打印机"]
rank --> swap["驱动程序被替换"]
del --> re{"能否用 Ready Print 重新注册"}
re -->|"能"| swap
re -->|"不能"| lost["打印目标消失"]
图 2: 把停止提供计划和 WPP 分开考虑,就能区分出「驱动程序被换掉」的场面和「打印目标本身丢失」的场面。
这里还有一点很重要:不要把支持 IPP 和取得 Mopria 认证当成同一个条件。
| 条件 | 主要用于判定什么 |
|---|---|
| IPP 类驱动程序与该设备匹配 | 排名规则的变更是否可能导致驱动程序被替换 |
| 已取得 Mopria 认证,网络连接时 IPP 已启用且可达,USB 连接时处于 IPP over USB 模式 | 物理打印机能否在 WPP 下作为 Windows Ready Print 重新注册 |
在未取得 Mopria 认证但支持 IPP 的设备上,即使不使用 WPP,也可能因为排名规则的变更而被替换驱动程序。反过来说,仅仅驱动程序名是「Microsoft IPP Class Driver」,并不等于已经确认它在 WPP 下能用。14
flowchart TB
accTitle: 匹配 IPP 类驱动程序与取得 Mopria 认证是两个不同的条件
accDescr: 打印机只要支持 IPP,排名规则变更后就可能被选中 IPP 类驱动程序而替换驱动程序,而是否取得 Mopria 认证是另一个条件,它决定能否在 Windows protected print mode 下重新注册,网络连接机型需要 IPP 已启用且可达,USB 连接机型需要处于 IPP over USB 模式
printer["打印机"]
printer --> q1{"是否支持 IPP"}
q1 -->|"是"| rank["可能被替换"]
q1 -->|"否"| keep["保持厂商驱动程序"]
printer --> q2{"是否已取得 Mopria 认证"}
q2 -->|"否"| ng["无法在 WPP 下重新注册"]
q2 -->|"是"| q3{"是否 USB 连接"}
q3 -->|"否"| q5{"IPP 是否启用且可达"}
q5 -->|"是"| ok["可以在 WPP 下重新注册"]
q5 -->|"否"| ng
q3 -->|"是"| q4{"是否 IPP over USB 模式"}
q4 -->|"是"| ok
q4 -->|"否"| ng
图 3: 排名规则的影响取决于是否支持 IPP,而能否在 WPP 下留下来,则取决于 Mopria 认证再加上 IPP 已启用且可达(USB 连接时为 IPP over USB 模式)。
2.3 驱动程序签名有例外,但不保证能持续
2026 年 1 月 15 日之后,符合下列任意一条的驱动程序,仍可把签名的例外申请送入单独审核。1
- 面向无法取得 Mopria 认证的打印机。
- 把 Windows 10 及以下作为目标操作系统上限的驱动程序包。
- 原生 ARM64 驱动程序。
无论是 WHQL 还是 Attestation,提交都会被默认阻止,需要附上正当性说明文档进行人工审核。即使符合条件,也不保证厂商会提交、Microsoft 会批准。5 另外,能拿到已签名的驱动程序,和能在启用了 WPP 的环境中使用,是两回事。标签打印机与小票打印机的应对在第 6 章讲解。
flowchart TB
accTitle: 2026 年 1 月 15 日之后仍可获得驱动程序签名的条件
accDescr: 厂商提交驱动程序默认会被阻止,只有符合无法取得 Mopria 认证的打印机、以 Windows 10 及以下为上限的驱动程序包、原生 ARM64 驱动程序这三个条件之一的才能把例外申请送入单独审核,而审核后只是有可能获批,并不保证一定会被签名
submit["厂商提交驱动程序"]
submit --> block["默认被阻止"]
block --> c1["无法取得 Mopria 认证的机型"]
block --> c2["上限为 Windows 10 及以下"]
block --> c3["原生 ARM64"]
c1 --> apply["可以提出例外申请"]
c2 --> apply
c3 --> apply
apply --> review["单独审核"]
review --> maybe["有可能获批(没有保证)"]
图 4: 即使符合条件也只是能送进审核,是否会被签名并没有保证。
3. 机制 ── 传统的驱动程序通道与 Windows Ready Print
3.1 变的是绘制 API 之后的打印通道
在传统的 Windows 打印中,应用程序发出 GDI 或 XPS 的绘制命令,打印后台处理程序接收作业,打印驱动程序再把它转换成打印机的语言(PDL)发送出去。GDI 打印路径和 XPS 打印路径都建立在这个结构之上。6
flowchart TB
accTitle: 传统的驱动程序通道
accDescr: 业务应用程序的 GDI 或 XPS 绘制命令由以 SYSTEM 权限运行的打印后台处理程序假脱机,第三方的 v3 或 v4 驱动程序在没有驱动程序隔离时就在打印后台处理程序本体之中、在共享或隔离时则在与打印后台处理程序不同的进程中,把它转换成专有的 PDL 送往打印机
app["业务应用程序(GDI / XPS)"] --> spooler["打印后台处理程序(SYSTEM 权限)"]
spooler --> iso{"驱动程序隔离为"}
iso -->|"无"| inproc["在打印后台处理程序本体中运行第三方驱动程序"]
iso -->|"共享 / 隔离"| host["在另一个进程中运行第三方驱动程序"]
inproc --> pdl["转换成专有 PDL"]
host --> pdl
pdl --> printer["打印机"]
图 5: 在传统通道中,隔离与否会改变进程,但在打印堆栈里由第三方代码承担 PDL 转换这一结构是相同的。
作为它的后继而准备的,就是 Windows Ready Print。它是把基于 IPP(Internet Printing Protocol)的打印、基于 eSCL 的扫描以及 Universal Print 捆在一起的称呼,不需要第三方驱动程序。它面向已取得 Mopria 认证的打印机设计,并且不依赖 CPU 架构,这也是它的优点。7
Windows 10 21H2 以后自带了以网络和 USB 方式处理 Mopria 兼容打印机的 Microsoft IPP Class Driver。1 Universal Print 的云队列则使用自带的 Universal Print Class Driver。8
flowchart TB
accTitle: Windows Ready Print 的通道
accDescr: 业务应用程序的绘制命令由打印后台处理程序接收,自带的 Microsoft IPP Class Driver 在客户端渲染成 PWG Raster 或 PDF 并用 IPP 送往已取得 Mopria 认证的打印机,或者由自带的 Universal Print Class Driver 用 IPP over HTTPS 送往 Universal Print 服务,而 Windows protected print mode 只允许这条 Windows Ready Print 的通道
app["业务应用程序(GDI / XPS)"] --> spooler["打印后台处理程序"]
spooler --> ipp["Microsoft IPP Class Driver"]
ipp --> render["渲染成 PWG Raster / PDF"]
render --> printer["已取得 Mopria 认证的打印机(IPP)"]
spooler --> up["Universal Print Class Driver"]
up --> cloud["Universal Print 服务(IPP over HTTPS)"]
wpp["Windows protected print mode"] -.->|"只允许 Ready Print 的通道"| ipp
wpp -.-> up
图 6: Windows Ready Print 同样经过打印后台处理程序。改变的是承担转换与发送的角色,从第三方驱动程序换成了自带的类驱动程序。
IPP 是基于 HTTP 的协议,用 ipps://printer.example.com/ipp/print 这样的 URI 来标识打印机。无驱动打印所使用的 PDL 仅限于基于 PWG Raster、PDF 等公开标准的少数几种格式,最终文档在客户端一侧渲染。9 在 Universal Print 中,打印后台处理程序用 IPP over HTTPS 把作业送往服务。8
从应用程序看到的入口没有变。 用 GDI 或 XPS 绘制的应用程序调用的还是同样的 API。改变的是在那之后返回纸张和纸盒列表、提供专有功能、转换成 PDL 的那套机制。因此,比起只做绘制的应用程序,依赖驱动程序返回的信息和专有设置的应用程序受到的影响更大。至于厂商的专有功能,还要确认是否通过 Print Support App(PSA)提供。10
3.2 多功能一体机要把打印、传真、扫描分开确认
能否迁移到 Windows Ready Print,前提是设备搭载了该功能并实现了对应的协议。1
| 功能 | 网络连接时需要的支持 | USB 连接时追加的条件 |
|---|---|---|
| 打印 | IPP | IPP over USB 模式 |
| 传真发送 | IPP Fax Out | IPP over USB 模式 |
| 扫描 | eSCL 或 WS-Scan | IPP over USB 模式 |
不要只看 Mopria 的打印支持,就断定传真和扫描也能一起迁移。
flowchart TB
accTitle: 分别确认多功能一体机各功能的顺序
accDescr: 逐个选出要使用的功能,依次确认设备是否搭载、是否支持表中的协议、USB 连接时是否处于 IPP over USB 模式,不要把打印功能的结果套用到传真或扫描上,其余功能也要分别确认
start["选出一个要使用的功能"]
start --> feature{"设备是否搭载该功能?"}
feature -->|"是"| protocol{"是否支持表中的协议?"}
feature -->|"否"| unmet["该功能的条件未满足"]
protocol -->|"是"| usb{"是否 USB 连接?"}
protocol -->|"否"| unmet
usb -->|"是"| mode{"是否 IPP over USB 模式?"}
usb -->|"否"| checked["该功能的条件已确认"]
mode -->|"是"| checked
mode -->|"否"| unmet
checked --> next["其余功能分别确认"]
unmet --> next
图 7: 按功能是否搭载、是否支持协议、连接方式的追加条件的顺序确认。不要把打印的确认结果套用到传真或扫描上。
3.3 背后的原因是打印堆栈的安全性
在 Microsoft Learn 的说明中,打印相关的缺陷占该说明所统计的过去三年 MSRC(Microsoft Security Response Center)报告案例的 9%。打印后台处理程序以 SYSTEM 权限运行,标准用户也能广泛触及,并且会按需加载第三方代码。有些旧驱动程序与 CFG、CET 等现代缓解措施不兼容,形成了难以应用「需要全部参与方都支持」的那类缓解措施的结构。9
flowchart TB
accTitle: 只要还加载第三方驱动程序,缓解措施就无法生效的原因
accDescr: 以 SYSTEM 权限运行的打印后台处理程序会加载第三方代码,而旧驱动程序与 CFG、CET 等缓解措施不兼容,因此需要所有参与方都支持的缓解措施无法应用到打印后台处理程序上,漏洞也就更容易被利用
sys["打印后台处理程序以 SYSTEM 权限运行"]
load["按需加载第三方代码"]
old["旧驱动程序与缓解措施不兼容"]
sys --> risk["漏洞容易被利用"]
load --> nomit["无法应用缓解措施(CFG / CET / ACG)"]
old --> nomit
nomit --> risk
图 8: 缓解措施只有在所有参与方都支持时才起作用,因此只要不把驱动程序移出去,就守不住打印后台处理程序。
第三方驱动程序运行的位置,由 打印驱动程序隔离 的设置决定。11
| 隔离模式 | 驱动程序运行的位置 |
|---|---|
| 无(None) | 打印后台处理程序本体的进程内 |
| 共享(Shared) | 与打印后台处理程序不同、并与其他驱动程序共用的进程 |
| 隔离(Isolated) | 驱动程序专用的独立进程 |
在 INF 中声明了 DriverIsolation=2 的驱动程序默认使用共享进程,没有声明的驱动程序默认在打印后台处理程序本体中运行。管理员可以通过打印管理控制台或组策略覆盖。不过无论哪种模式,第三方代码都在打印堆栈之中运行这一点不会改变。11 WPP 就是把这种对第三方代码的依赖去掉的运行模式。
flowchart TB
accTitle: 第三方驱动程序运行在哪个进程里是如何决定的
accDescr: 在 INF 中声明了 DriverIsolation=2 的驱动程序默认运行在与打印后台处理程序不同的共享进程中,没有声明的驱动程序默认运行在打印后台处理程序本体中,管理员可以通过打印管理控制台或组策略覆盖为共享、打印后台处理程序本体之中、驱动程序专用的独立进程(隔离)三者之一
inf{"在 INF 中声明 DriverIsolation=2"}
inf -->|"是"| shared["运行在另一个共享进程中(默认)"]
inf -->|"否"| inproc["运行在打印后台处理程序本体中(默认)"]
admin["由管理员的设置或策略覆盖"] -.-> shared
admin -.-> inproc
admin -.-> isolated["运行在专用的独立进程中(隔离)"]
图 9: 隔离模式由 INF 的声明和管理员的设置决定,没有声明的旧驱动程序默认在打印后台处理程序本体中运行。
4. Windows protected print mode 下会消失什么
4.1 WPP 是「只使用 Windows Ready Print」的运行模式
WPP 是在 Windows 11 24H2 中引入的。在本文撰写时它默认关闭,关闭期间对驱动程序的安装和打印功能没有限制。1213 下面把启用的方法,以及谁能改回去区分开来。
| 启用的途径 | 设置的位置 | 改回关闭的方法 |
|---|---|---|
| 设置应用 | 「打印机和扫描仪」中的 Windows protected print mode | 如果是用户自己在设置应用中启用的,就可以由本人在设置应用中改回去 |
| 组策略 | 「计算机配置 > 管理模板 > 打印机 > Configure Windows protected print」 | 由管理员更改策略 |
| Intune | OMA-URI ./Device/Vendor/MSFT/Policy/Config/Printers/ConfigureWindowsProtectedPrint |
由管理员更改策略 |
如果是通过组策略启用的,使用者不联系管理员就无法解除。Intune 的 OMA-URI 也是应用同一套基于 ADMX 的设备策略的途径。可以由本人在设置界面改回去的,只有本人在设置界面启用的情况,请这样理解。14213
flowchart TB
accTitle: 启用 Windows protected print mode 的三条途径
accDescr: 可以通过设置应用、组策略、Intune 的 OMA-URI 之一启用 Windows protected print mode,能由本人在设置应用中改回去的只有本人在设置应用中启用的情况,通过组策略或 Intune 的策略下发的情况必须由管理员一侧更改策略才能解除
s["设置应用(本人启用)"] --> wpp["Windows protected print mode 已启用"]
g["组策略"] --> wpp
i["Intune(OMA-URI)"] --> wpp
s -.->|"本人可在设置应用中改回"| off["解除"]
g -.->|"本人无法改回"| adm["解除需管理员一侧更改策略"]
i -.->|"本人无法改回"| adm
图 10: 启用有三条途径,能由本人改回去的只有本人在设置应用中启用的情况。
4.2 会留下的队列、会消失的队列、需要重新注册的队列
启用时的影响不仅取决于打印机本身,还取决于 现在是用哪个驱动程序注册的。2
| 当前的打印目标 | 启用 WPP 后 | 应对方式 |
|---|---|---|
| 用第三方驱动程序(v3/v4)注册的物理打印机 | 队列被卸载,驱动程序也从驱动程序存储中删除 | 兼容机型用 Windows Ready Print 重新注册。不兼容机型则选择另一条通道或不使用 WPP |
| 虽已取得 Mopria 认证,但用厂商驱动程序注册的打印机 | 会先被删除一次。并不是取得了认证现有队列就能留下 | 网络连接要确认 IPP 已启用且可达,USB 连接要确认处于 IPP over USB 模式,然后重新注册 |
| 已用 Windows Ready Print 注册的兼容打印机 | 可以继续使用 | 验证能力、设置和实际输出 |
| Universal Print 的云队列 | 作为 Windows Ready Print 的一部分处于 WPP 兼容一侧 | 确认应用程序对队列名的依赖,并验证实际输出 |
| 不受支持的软件打印机 | 会被删除 | 第三方 PDF 打印机等要确认产品是否支持 WPP。报表存档应转向用 PDF 库直接生成 |
| 已更新为支持 WPP 的虚拟打印机 | 不要和不支持的产品一概而论。OneNote 备有 Protected virtual printer | 验证正在使用的产品和队列属于哪一种 |
| Microsoft XPS Document Writer、传真的虚拟打印机 | 会被删除 | 把 WPP 改回关闭后,XPS 从「Windows 功能」、传真从「Windows 传真和扫描」的可选功能中手动重新装回 |
flowchart TB
accTitle: 启用 Windows protected print mode 时打印机会发生什么
accDescr: 启用后用第三方驱动程序安装的打印机、不受支持的虚拟打印机、XPS Document Writer 和传真的虚拟打印机会被删除,已取得 Mopria 认证并且网络连接时 IPP 已启用且可达、USB 连接时处于 IPP over USB 模式的机型可以用 Windows Ready Print 重新注册,不支持的机型在启用期间无法使用
on["启用 WPP"]
on --> third["删除使用第三方驱动程序的打印机"]
on --> soft["删除不受支持的虚拟打印机"]
soft --> xps["XPS Document Writer 和传真也被删除"]
third --> mopria{"是否已取得 Mopria 认证"}
mopria -->|"是"| conn{"连接方式为"}
conn -->|"网络"| ipp{"IPP 是否启用且可达"}
conn -->|"USB"| usb{"是否 IPP over USB 模式"}
ipp -->|"是"| re["用 Windows Ready Print 重新注册"]
usb -->|"是"| re
ipp -->|"否"| no["启用期间无法使用"]
usb -->|"否"| no
mopria -->|"否"| no
图 11: 用厂商驱动程序建立的队列,即使已取得 Mopria 认证也会先消失一次。能否重新注册要另行确认条件。
在 WPP 启用期间,被删除的第三方驱动程序不能使用。另外,即使把 WPP 改回关闭,用 Windows Ready Print 重新装好的打印机也不会自动变回原来的驱动程序。210
在应用程序一侧,以厂商驱动程序返回的纸张、纸盒、专有功能,以及端口监视器 DLL 型的虚拟打印机、XPS Document Writer 为前提的处理,都是确认对象。用 XpsDocument 等直接生成 XPS 文件的处理,与向 XPS Document Writer 这个虚拟队列打印是两回事,不属于此次删除的对象。
4.3 管理和打印后台处理程序内部也有变更
在 WPP 下,不再加载端口监视器 DLL 等第三方二进制文件。AddPrintProvidorW 这类模块加载 API 也无法加载新模块,只会加载 IPP 所需的、由 Microsoft 签名的二进制文件。AddPrintProvidorW 是 winspool.h 中沿用下来的历史拼写,Microsoft Learn 的说明中写作 AddPrintProviderW。9
由于这些限制,XPS 的渲染改为以用户权限运行而不再是 SYSTEM,新的打印后台处理程序工作进程使用去掉了 SeTcbPrivilege 等权限的受限令牌。子进程的创建被禁止,CFG、CET、ACG 也会启用。9
flowchart TB
accTitle: Windows protected print mode 下打印后台处理程序的变化
accDescr: 由于不再加载第三方二进制文件,就可以做到模块加载的限制、以用户权限渲染 XPS、使用受限令牌的工作进程、禁止创建子进程以及启用 CFG、CET、ACG
nodrv["不加载第三方二进制文件"]
nodrv --> r["加载的限制"]
nodrv --> l["权限的收缩"]
nodrv --> m["缓解措施的启用"]
r --> r2["只加载 Microsoft 签名的二进制文件"]
l --> l2["用户权限的 XPS、受限令牌"]
m --> m2["禁止子进程、CFG / CET / ACG"]
图 12: 图 8 中无法生效的缓解措施,只有把第三方二进制文件移出去之后才能启用。
Point and Print 虽然保留了 IPP 配置,但不再安装第三方驱动程序。那些以「连接到打印服务器就会下发驱动程序」为前提的装机流程,也需要重新审视。9
WPP 当前是否启用,可以用 Windows 11 24H2 以后的 WinRT API Windows.Graphics.Printing.ProtectedPrint.WindowsProtectedPrintInfo.IsProtectedPrintEnabled 确认。15 组策略的设置值位于 HKLM\Software\Policies\Microsoft\Windows NT\Printers\WPP 之下的 WindowsProtectedPrintGroupPolicyState。13
另外,从启用了 WPP 的客户端,无法用打印管理去管理未启用 WPP 的打印服务器。要为管理人员另外准备一台关闭了 WPP 的管理用客户端。14
5. 现有应用程序的盘点 ── 要看的 4 个地方
flowchart TB
accTitle: 业务应用程序中要盘点的 4 个地方
accDescr: 业务应用程序的打印代码中,保存驱动程序专有设置、依赖队列名(包括 SDK 和库内部的)、依赖 WPP 不支持的虚拟打印机、向 WPP 下不会保留的队列做 RAW 发送这 4 个地方是改造对象,只做绘制的代码则是验证对象
app["业务应用程序的打印代码"]
app --> deps["盘点 4 类依赖"]
app --> d0["只做绘制"]
deps --> d1["保存专有设置"]
deps --> d2["依赖队列名"]
deps --> d3["依赖虚拟打印机"]
deps --> d4["RAW 发送"]
d2 -.-> d2n["包括 SDK 内部"]
d3 -.-> d3n["WPP 不支持的那些"]
d4 -.-> d4n["面向不会保留的队列"]
d1 --> fix["改造"]
d2 --> fix
d3 --> fix
d4 --> fix
d0 --> verify["验证"]
图 13: 只有这 4 类依赖是改造对象,只做绘制的代码交给验证。
5.1 先取得现场的驱动程序清单,判定打印目标
是否需要改造,要看过现场装了什么之后再定。用 PowerShell 的 PrintManagement 模块取得队列和驱动程序的清单。用 Get-Printer 和 Get-PrinterDriver 取清单不需要管理员权限,但后半段的 pnputil 需要管理员权限。1617
# 按队列列出驱动程序名、主版本号(3 = v3,4 = v4)、提供商、INF 文件名
Get-Printer |
Select-Object Name, DriverName, PortName,
@{ Name = "DriverMajorVersion"; Expression = { (Get-PrinterDriver -Name $_.DriverName).MajorVersion } },
@{ Name = "Manufacturer"; Expression = { (Get-PrinterDriver -Name $_.DriverName).Manufacturer } },
@{ Name = "InfName"; Expression = { Split-Path -Leaf (Get-PrinterDriver -Name $_.DriverName).InfPath } } |
Sort-Object DriverName |
Format-Table -AutoSize
# 只列举第三方的驱动程序包,并附上公开名(oemN.inf)、原始 INF 名和提供商(需要管理员权限)
pnputil /enum-drivers /class Printer
用 MajorVersion 可以分辨 v3 和 v4。18 不过,v3/v4 的区分与自带/第三方的区分是两回事。按下面的方式分类。
| 分类 | 分辨方法 | 接下来要确认的内容 |
|---|---|---|
| IPP 类驱动程序的队列 | DriverName 为 Microsoft IPP Class Driver |
Mopria 认证、网络的 IPP 启用与可达性、USB 的 IPP over USB 模式。不要只凭名字就断定它与 WPP 兼容 |
| Universal Print 的队列 | 使用自带的 Universal Print Class Driver8 | 按 WPP 兼容一侧对待,确认应用程序对队列名的依赖和输出 |
| 其他自带驱动程序的队列 | 既不是已知的类驱动程序名,也不在第三方包清单中 | XPS 和传真属于删除对象。不要把 Generic / Text Only 等当成一定会保留。Microsoft Print to PDF 没有被列入删除对象,因此要逐队列判定 |
| 厂商驱动程序的队列 | 把 INF 文件名和提供商与 pnputil 的第三方包清单对照 |
属于驱动程序被替换的候选。在 WPP 下现有队列会消失,因此要确认物理设备能否重新注册 |
Get-PrinterDriver 的 InfPath 是驱动程序存储内 INF 的路径,不保证返回公开名 oemN.inf。要把 pnputil /enum-drivers 返回的公开名、原始 INF 名和提供商,与 InfPath 的文件名及 Manufacturer 对照起来。pnputil /enum-drivers 只列举第三方包,自带的包不会出现在清单里。1917
flowchart TB
accTitle: 盘点现场打印机清单的步骤
accDescr: 用 PowerShell 取得队列和驱动程序的清单,通过 DriverName 以及与 Manufacturer 和 pnputil 第三方包清单的对照,分成 IPP 类驱动程序的队列、Universal Print 的队列(WPP 兼容)、其他自带驱动程序的队列(XPS 和传真会被删除,Generic / Text Only 不能当成一定保留,Microsoft Print to PDF 没有被列入删除对象因此要单独判定)、厂商驱动程序的队列(会被替换的候选),其中 IPP 类驱动程序的队列还要另外确认 Mopria 认证、网络连接时 IPP 是否启用且可达、USB 连接时是否处于 IPP over USB 模式,然后两类队列都要与应用程序的设置和打印代码所指向的队列对照后判定
list["用 Get-Printer / Get-PrinterDriver 取清单"]
list --> cls{"DriverName 和提供商为"}
cls -->|"IPP Class"| ipp["IPP 类驱动程序的队列"]
cls -->|"Universal Print Class"| up["Universal Print 的队列"]
cls -->|"其他自带"| inbox["用第 4 章的表逐个判定是否保留"]
cls -->|"厂商制"| vendor["会被替换的候选"]
ipp --> mop["确认 Mopria、IPP 可达性、USB"]
mop --> match["与应用程序的设置和代码对照"]
up --> match
inbox --> match
vendor --> match
match --> judge["用判断表分类"]
图 14: 取清单并按名字分类为止不需要管理员权限,对照提供商则需要 pnputil 的管理员权限。
把这份清单与应用程序的设置、打印代码、所用 SDK 引用的队列进行核对。按交付现场逐一记录驱动程序、Mopria 认证、连接方式、IPP 的可达性、USB 的运行模式,就能用第 4 章的表做判定。
如果在 WPP 下打印目标不会保留、也无法重新注册,那么要先于代码盘点,去准备第 6 章的另一条通道,或者做出不使用 WPP 的判断。 如果是即使不用 WPP 也可能受到 IPP 排名变更影响的设备,请接着确认 5.2 以后的内容。如果既不使用 WPP,也不受驱动程序替换的影响,那么判断就是保持当前通道继续运维。
如果要在应用程序内确认驱动程序名,可以用 WPF 的 System.Printing 读取 PrintQueue.QueueDriver.Name。20
using System.Printing;
// 从管理工具或桌面应用程序中,列举各队列的驱动程序名
using var server = new LocalPrintServer();
foreach (PrintQueue queue in server.GetPrintQueues(
new[] { EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections }))
{
Console.WriteLine($"{queue.Name}\t{queue.QueueDriver?.Name}\t{queue.QueuePort?.Name}");
}
不过,System.Printing 命名空间不支持在 Windows 服务内使用。如果是由常驻服务负责打印,请把这段诊断处理放到管理工具一侧。21 从服务打印的限制,请参阅Windows 服务的创建与运维和上一篇文章的第 7 章。
5.2 确认 1:有没有保存和恢复驱动程序专有的设置
最难找的就是打印设置的保存。保存方式不同,出问题的原因也不同。
| 保存的内容 | 典型实现 | 驱动程序变了之后会出问题的原因 |
|---|---|---|
DEVMODE 的非公开部分 |
把 DocumentProperties 的结果或 GetHdevmode 的缓冲区整块保存,再用 SetHdevmode 还原 |
非公开数据只有那个驱动程序才能解释 |
.NET Framework 的 PrinterSettings 整体 |
把打印对话框之后的对象做二进制序列化 | 内部复制过来的驱动程序非公开区域也可能一并被保存 |
| 公开属性的值 | 把 PaperSize、PaperSource、PrinterResolution、Duplex 等按自定义格式保存 |
出问题的不是非公开缓冲区,而是自定义纸张、纸盒编号等的含义发生变化 |
含私有扩展的 PrintTicket |
保存包含厂商专有命名空间的 XML | 专有扩展依赖于原来的驱动程序或机型 |
DEVMODE 可以在公开成员之后带上由 dmDriverExtra 指明大小的非公开数据。Windows 校验的只有公开部分,损坏的非公开数据可能在应用程序或打印后台处理程序的进程中让驱动程序崩溃。22
flowchart TB
accTitle: DEVMODE 的公开部分与非公开部分
accDescr: DEVMODE 结构体在公开成员之后可以带上由 dmDriverExtra 指明的、驱动程序自定义的非公开数据,只有公开部分会被 Windows 校验,非公开部分只有那个驱动程序才能解释,因此整块保存后一旦驱动程序变了就会失去意义
dm["DEVMODE 结构体"]
dm --> pub["公开部分(dmSize)"]
dm --> priv["非公开部分(dmDriverExtra)"]
pub --> chk["由 Windows 校验"]
priv --> only["只有那个驱动程序能解释"]
only --> lost["驱动程序一变就失去意义"]
图 15: 在整块保存下来的设置中,会出问题的是非公开部分。
用 SetHdevmode 接收了设置的 PrinterSettings,会把这块非公开区域复制到内部。23 .NET Framework 版的 PrinterSettings 带有 Serializable 特性,而二进制序列化默认也包含 private 字段,因此保存整个对象同样潜藏着相同的依赖。2425
另一方面,.NET 版的 PrinterSettings 没有 Serializable 特性,把公开属性逐个按自定义格式保存的实现,并不会保存原生的 DEVMODE 缓冲区或 dmDriverExtra 区域。24 这种情况下要注意的是 依赖驱动程序的值。
flowchart TB
accTitle: 保存 DEVMODE 和保存托管值的出问题方式不同
accDescr: 把 DEVMODE 缓冲区整块保存的实现,以及把用 SetHdevmode 接收了非公开区域之后的 PrinterSettings 整体序列化的实现,都会连非公开部分一起带走,驱动程序一变就失去意义,而保存公开属性值的实现则因为 Custom 或厂商专有的纸张、纸盒编号是针对厂商驱动程序清单的值,无法保证在 IPP 类驱动程序下含义相同,两者都要换成只保存意图的设计
a["整块保存 DEVMODE"]
a --> a1["连非公开部分一起带走"]
s["保存 SetHdevmode 之后的整个对象"] --> a1
a1 --> a2["驱动程序一变就失去意义"]
b["保存公开属性的值"]
b --> b1["带走自定义纸张、纸盒的编号"]
b1 --> b2["无法保证在新驱动程序下含义相同"]
a2 --> c["两者都改为只保存意图"]
b2 --> c
图 16: 把 SetHdevmode 之后的对象整体保存属于非公开部分那一侧,虽然出问题的方式不同,但两者都是把驱动程序的状态带在身上。
如果 RawKind 表示的是标准的 PaperKind 或 PaperSourceKind,那么即使驱动程序变了含义也能保持。没有保证的是,Custom 或厂商专有的编号在另一个驱动程序下仍指同一种纸张、同一个纸盒。请不要把标准值也一律当成会出问题的东西。2627
flowchart TB
accTitle: 保存下来的纸张、纸盒值中会出问题的部分
accDescr: RawKind 中对应 A4 等标准纸张(PaperKind)或 Upper、Lower 等标准进纸源(PaperSourceKind)的值,即使驱动程序变了含义也能保持,而 Custom 或厂商专有的值是针对厂商驱动程序清单的编号,无法保证 IPP 类驱动程序会把它解释成同一种纸张、同一个纸盒
raw["保存下来的 RawKind"]
raw --> std["标准值(PaperKind 等)"]
raw --> cus["Custom、厂商专有的值"]
std --> keep["驱动程序变了含义也保持"]
cus --> lost["无法保证被解释成同一种纸张、同一个纸盒"]
图 17: 会出问题的不是标准值,而是针对厂商驱动程序清单的自定义值。
PrintTicket 也一样,公开关键字定义在 psk 命名空间中,但它可以包含设备专有的私有扩展。规定要求第三方的元素必须放在与该第三方明确关联的命名空间中。如果保存的 XML 中混有厂商的命名空间,就要把它当作驱动程序专有设置来确认。2829
应对:只保存「意图」
把纸张尺寸、方向、双面、份数、纸盒选择这些意图,用公开关键字放进自己的配置文件里。不要把驱动程序的内部状态带过去,而是在打印之前与当前的能力做核对。
在 WPF 中,用 PrintQueue.GetPrintCapabilities 取得能力,把请求做成 PrintTicket 交给 MergeAndValidatePrintTicket。30 这里要注意的是,不受支持的请求未必一定会报错。驱动程序会解决冲突,通常会返回一张把冲突项替换成默认值之类的有效票据。
如果 ValidationResult.ConflictStatus 为 ConflictResolved,就把 ValidatedPrintTicket 中的双面和纸盒与请求做比较,把差异记入日志并通知使用者。31
flowchart TB
accTitle: 只保存意图并在打印前与能力核对的设计
accDescr: 配置文件中只用公开关键字保存纸张、方向、双面、份数、纸盒的意图,在打印之前用 GetPrintCapabilities 取得当前打印机的能力,交给 MergeAndValidatePrintTicket,如果 ConflictStatus 为 NoConflict 就直接打印,如果为 ConflictResolved 就把被替换的项目与请求对照后转入日志和通知
cfg["配置文件:只保存意图(纸张、方向、双面、份数)"]
cfg --> caps["打印前调用 GetPrintCapabilities"]
caps --> merge["MergeAndValidatePrintTicket"]
merge --> st{"ConflictStatus 为"}
st -->|"NoConflict"| print["打印"]
st -->|"ConflictResolved"| tell["把与请求的差异记入日志并通知"]
图 18: 保存设置的意图,并在打印时用当前的能力做校验。被替换掉的设置不要默默使用,要确认差异。
在 WinForms 中,要从 PrinterSettings.PaperSizes 按 Kind 或尺寸而不是按名称重新挑选纸张。进纸源的 PaperSources,如果是 Upper 或 Lower 这种唯一的标准值就可以复用,但多个专有纸盒有可能被一并返回成 PaperSourceKind.Custom。而且 PaperSource 没有纸张尺寸。不要只凭 Kind 去确定专有纸盒,要么显式重新映射到当前的能力上,要么让使用者重新选择。27
5.3 确认 2:有没有依赖打印机名、队列名
上一篇文章建议把打印机名放进配置文件。这里要补充的前提是,在驱动程序被替换或重新检测之后,并不保证会建立同名的队列。指向旧队列名的设置,照原样就会找不到打印目标。
要确认的位置有三处:配置文件、代码中写死的名称,以及 SDK 和报表库的内部。即使自己的代码里没有固定名称,厂商 SDK 也可能在内部调用特定的队列或驱动程序。请把 SDK 的文档与 5.1 的清单对照起来。
flowchart TB
accTitle: 队列名依赖藏身的三个位置
accDescr: 对队列名的依赖藏在配置文件中的固定名称、代码中写死的固定名称、以及厂商 SDK 或报表库内部调用的队列和驱动程序这三处,前两者可以通过搜索代码和配置找到,后者要用 SDK 的文档和 5.1 的清单来确认
dep["对队列名的依赖"]
dep --> cfg["配置文件中的固定名称"]
dep --> code["代码中写死的固定名称"]
dep --> sdk["SDK、库内部的固定"]
cfg --> grep["搜索代码和配置找出来"]
code --> grep
sdk --> doc["用 SDK 的文档和 5.1 的清单确认"]
图 19: 即使自己的代码里没有队列名,SDK 或库的内部也可能还留着依赖。
应对方法是,在启动时确认配置的名称是否存在于 PrinterSettings.InstalledPrinters 中,找不到就记录日志并通知使用者。同时也要让使用者能从设置界面重新选择打印目标。
绝对不要默默回退到默认打印机。 因为这会把单据从别的部门的打印机里印出来这种问题,掩盖成一次正常的打印。
flowchart TB
accTitle: 启动时对打印机名的校验
accDescr: 启动时确认配置文件中的打印机名是否存在于 InstalledPrinters 中,存在就打印,不存在就记录日志并通知用户让其重新选择,不要默默回退到默认打印机
start["启动时:配置中的打印机名"]
start --> exists{"是否在 InstalledPrinters 中"}
exists -->|"在"| print["打印到该队列"]
exists -->|"不在"| log["记录日志并通知用户"]
log --> pick["在设置界面重新选择"]
exists -.->|"绝对不要这样做"| silent["默默回退到默认打印机"]
图 20: 找不到时默默换一台打印机输出的实现,会制造出最晚被发现的问题。
5.4 确认 3:有没有把虚拟打印机用于生成文件
打印到第三方 PDF 打印机再监视输出文件夹的报表存档,或者用 XPS Document Writer 生成中间文件的处理,一旦所用的队列在 WPP 下被删除就会失效。
不过,被删除的是 WPP 不支持的 软件打印机。请不要把端口监视器 DLL 型等不支持的产品,与 OneNote 这类已更新为支持 WPP 的产品混为一谈。请在第 7 章的步骤 3 中确认实际使用的队列是否属于删除对象。2
如果需要的是 PDF,那么正如上一篇文章第 5 章所说,转向用 PDF 库直接生成的架构,才是更不容易受打印堆栈变化影响的设计。
5.5 确认 4:RAW 发送是否经过了在 WPP 下会消失的队列
RAW 发送是指按 OpenPrinter → StartDocPrinter(数据类型「RAW」)→ WritePrinter → EndDocPrinter 的顺序,把打印机语言的数据经打印后台处理程序送出去的方式。文档一侧必须用硬件的语言完整描述打印设置,DEVMODE 的设置不会被使用。3233
它在标签和小票上是常规做法,但 RAW 发送并不是「不经过打印后台处理程序的直接通信」。即使实际上只是把驱动程序当作通往端口的通道,一旦该队列在 WPP 下消失,通道也就没有了。
flowchart TB
accTitle: RAW 发送的通道以及在 WPP 下会消失的环节
accDescr: 应用程序用 OpenPrinter、StartDocPrinter、WritePrinter 把打印机语言的数据送往打印后台处理程序,在 WPP 下不会保留的队列(例如厂商驱动程序的队列)充当通道,数据经端口到达打印机,因此一旦该队列在 WPP 下消失,通道也就没有了
app["应用程序:StartDocPrinter(RAW)、WritePrinter"]
app --> spooler["打印后台处理程序"]
spooler --> queue["在 WPP 下不会保留的队列(通道)"]
queue --> port["端口"]
port --> printer["标签、小票打印机"]
wpp["启用 WPP"] -.->|"队列消失"| queue
图 21: RAW 发送只是把驱动程序当作通道使用,但通道本身会消失。
要确认的是数据被送往了哪一组队列、驱动程序和端口的组合。不只是厂商驱动程序的队列,Generic / Text Only 等面向物理打印机的、IPP 类驱动程序以外的自带驱动程序,也不能当成一定会保留。用第 7 章的删除清单确认会消失的那些,同样需要另一条通道。13
另外,Microsoft Learn 中并没有写明可以把专有 PDL 以 RAW 方式送往 IPP 类驱动程序的队列。这取决于打印机一侧的 IPP 实现所接受的 PDL,所以请不要判断成「换成 IPP 之后同样的 RAW 数据也能通过」。
6. 标签打印机与小票打印机的退路
小村软件建议,为标签和小票至少准备一条不依赖打印后台处理程序的输出通道。
即使符合第 2 章的签名例外,也不保证厂商驱动程序会继续提供、能在 WPP 下使用。1 在 Microsoft 的疑难解答中也有这样的案例:2021 年的更新之后,USB 连接的小票、标签打印机无法打印,最后靠 Known Issue Rollback 才解决。34 把输出通道分开掌握就是一种准备。
| 通道 | 是否依赖打印后台处理程序 | 适合的场景与注意事项 |
|---|---|---|
| 与设备直接通过 TCP/USB/串口通信的厂商 SDK | 不依赖 | 适合厂商会长期维护 SDK 的机型。要跟进 SDK 的位数、依赖运行时、签名的更新 |
| 内部调用 Windows 队列和驱动程序的厂商 SDK | 依赖 | 即使作为既有资产保留,内部的队列一旦在 WPP 下消失也会停下来。它不能成为独立于打印后台处理程序的退路 |
| 用 TCP 套接字直接发送打印机语言 | 不依赖 | 适合网络连接的标签打印机。要设计断线、重发、超时 |
| 用串口(虚拟 COM)/USB 直接发送 | 不依赖 | 适合小票打印机,或与计测装置并置的设备。前提是要选好虚拟 COM、HID、WinUSB |
| 经 IPP 类驱动程序打印(IPP / IPP over USB) | 依赖。满足条件则与 WPP 兼容 | 要确认 Mopria 认证、网络的 IPP 启用与可达性、USB 的运行模式。它不是走到打印后台处理程序之外的通道 |
TCP 的重连设计可以借鉴串口通信应用的陷阱中的思路。USB 的方式选型请参阅在 Windows 应用中处理 USB 设备的方法。IPP 在有些打印机上默认是关闭的,需要手动启用。4
光看「SDK」这个名字,是判断不出它是不是独立通道的。 请用 SDK 的文档和 5.1 的清单,确认它是直接与设备通信,还是最终仍要调用 Windows 的队列。
如果使用 Windows 的队列,Universal Print 虽然处于 WPP 兼容一侧,但它同样经过打印后台处理程序。既不是 IPP 类驱动程序也不是 Universal Print 的队列,如果是第三方驱动程序或 XPS、传真,就会在 WPP 下停下来;而像 Microsoft Print to PDF 这种没有被列入删除对象的自带队列,则要单独判定。「WPP 兼容」和「不依赖打印后台处理程序」是两种不同的分类。
flowchart TB
accTitle: 判定输出通道是否依赖打印后台处理程序的流程
accDescr: 候选通道如果送往 Windows 的打印队列,那么 IPP 类驱动程序的队列中打印机已取得 Mopria 认证且网络连接时 IPP 已启用且可达、USB 连接时处于 IPP over USB 模式的,以及作为 Windows Ready Print 一部分的 Universal Print 的队列,虽然依赖打印后台处理程序但与 WPP 兼容,未取得 Mopria 认证的机型、第三方驱动程序的队列、以及 XPS Document Writer 和传真这类第 4 章表中会被删除的队列则可能在 WPP 下停下来,而像 Microsoft Print to PDF 这种没有被列入删除对象的自带队列按 5.1 单独判定,如果不送往队列,SDK 若在内部调用队列或驱动程序就回到同样的判定,不调用的 SDK 和直接送往设备的通道则是不依赖打印后台处理程序的退路
route["候选的输出通道"]
route --> q1{"是否送往 Windows 的打印队列"}
q1 -->|"是"| q4{"是否 IPP 类驱动程序的队列"}
q4 -->|"是"| q5{"是否已取得 Mopria 认证"}
q5 -->|"是"| q9{"IPP 是否可达(USB 为 over USB)"}
q9 -->|"是"| depok["依赖但与 WPP 兼容"]
q9 -->|"否"| dep["依赖,且可能在 WPP 下停下来"]
q5 -->|"否"| dep
q4 -->|"否"| q6{"是否 Universal Print 的队列"}
q6 -->|"是"| depok
q6 -->|"否"| q7{"第 4 章的表中是否会被删除"}
q7 -->|"是(第三方驱动程序、XPS、传真)"| dep
q7 -->|"否(Print to PDF 等)"| indiv["单独判定(5.1)"]
q1 -->|"否"| q2{"是否经由厂商 SDK"}
q2 -->|"是"| q3{"内部是否调用队列、驱动程序"}
chk["确认:SDK 文档与 5.1 的清单"] -.-> q3
q3 -->|"是"| q4
q3 -->|"否"| indep["不依赖的退路"]
q2 -->|"否"| indep
图 22: 最终取决于经过哪个队列,除了已取得 Mopria 认证机型的 IPP 类驱动程序队列和 Universal Print 的队列之外,再除去没有被列入删除对象的自带队列,其余都可能在 WPP 下停下来。
7. 验证步骤 ── 使用 WPP 的情况与不使用的情况
先确认使用 WPP 和不使用 WPP 时验证的分叉。两者都要保留变更前的输出,并在实际使用的打印目标上确认。
flowchart TB
accTitle: 按是否使用 WPP 区分的打印验证步骤
accDescr: 在验证机上重现全部队列并保存变更前的输出,使用 WPP 时启用它并记录被删除的队列,比较兼容机型重新注册之后的输出,同时确认不兼容机型的另一条通道,不使用 WPP 时则在保持关闭的状态下对物理的 IPP 排名变更对象机重新检测并比较输出,其余的则在实际的队列上确认打印
prepare["重现全部队列并记录"]
prepare --> baseline["保存变更前的打印结果"]
baseline --> use{"是否使用 WPP"}
use -->|"使用"| enable["启用并记录被删除的队列"]
enable --> compatible["兼容机型按需重新注册"]
compatible --> compare["用与变更前相同的打印做比较"]
enable --> alternate["不兼容机型用另一条通道确认"]
use -->|"不使用"| physical{"是否物理的 IPP 排名变更对象"}
physical -->|"是"| redetect["保持关闭状态删除并重新检测"]
redetect --> compare
physical -->|"否"| existing["在实际的队列上确认打印"]
compare --> finish["记录结果并恢复验证机"]
alternate --> finish
existing --> finish
图 23: 把变更前的输出作为共同基准,只在使用 WPP 的环境中启用它。不使用的环境则确认 IPP 的排名变更和在实际队列上的打印。
7.1 共同的准备:在验证机而不是生产机上重现全部打印目标
验证要使用 Windows 11 24H2 以后的电脑,不要在生产机上进行。如果是网络连接,可以用能访问同一台打印机的虚拟机验证。如果要评估 USB 连接的重新注册或直接通信,除非虚拟机能透传同一个 USB 接口,否则要使用物理的验证机。
无论是否使用 WPP,都要按下面的步骤 1、2 保留变更前的状态。
- 重现现场中应用程序使用的全部队列。 不只是厂商驱动程序的物理打印机,第三方 PDF 虚拟打印机和
Generic / Text Only等自带队列也要按同样的配置装上。用 5.1 的脚本记录驱动程序名和版本。能通过删除差异判定的,只有验证机上存在的队列。 - 把打印功能全部跑一遍并保存输出。 确认纸张、纸盒、双面、份数的设置界面,各报表的打印、PDF 输出、标签打印,做出日后可供比较的基准。
flowchart TB
accTitle: 需要在验证机上重现的队列
accDescr: 在 5.1 的盘点中查明的应用程序所用队列里,厂商驱动程序的物理打印机、第三方 PDF 等虚拟打印机、Generic / Text Only 等自带驱动程序的队列都要按与现场相同的配置在验证机上重现,说明能用步骤 3 的删除清单判定的只有验证机上存在的队列
inv["5.1 的盘点:应用程序使用的队列"]
inv --> phys["物理打印机(厂商制)"]
inv --> virt["虚拟打印机(第三方 PDF 等)"]
inv --> inbox["自带驱动程序(Generic / Text Only 等)"]
phys --> vm["在验证机上按相同配置重现"]
virt --> vm
inbox --> vm
vm --> judge["用步骤 3 的删除清单判定是否保留"]
note["验证机上没有的队列不会出现在删除差异中"] -.-> judge
图 24: 验证机上没有的队列不会出现在删除差异中,所以要先把盘点查明的队列全部重现出来。
7.2 使用 WPP 的环境:启用它,把消失的打印目标也纳入验证
在步骤 1、2 之后,按下面的顺序推进。
- 启用 WPP,记录会被删除的队列。 在设置应用的「打印机和扫描仪」中选择「Windows protected print mode」的「设置」,删除对象会显示在对话框里。2 用组策略启用时不会出现对话框,因此要保存应用之前的
Get-Printer结果,应用之后重启验证机。用IsProtectedPrintEnabled或设置界面确认已启用之后再取一次清单,记录差异。14 - 重新安装被删除的兼容打印机。 用 Windows Ready Print 重新装上,并用 5.1 的脚本确认物理打印机的
DriverName已经变成 Microsoft IPP Class Driver。 - 重复相同的打印,与变更前比较。 确认设置界面的选项、保存设置的恢复、以及因队列名变化导致的打印目标丢失。输出的留白、字体、表格线也要与步骤 2 的输出对照。
- 不兼容的机型用第 6 章的另一条通道打印。 在重现出「打印机不在列表中」的状态之后,确认仍能输出。验证对象不只是改过的代码,还包括准备好的通道。
7.3 不使用 WPP 的环境:不启用它,验证驱动程序选择的变化
在决定不使用 WPP 的环境中,不执行步骤 3、4。因为一旦启用 WPP,队列会整个被删除,只由排名规则带来的影响就看不见了。
| 打印目标 | 在步骤 1、2 之后要做的事 |
|---|---|
| 会成为排名变更对象的物理 IPP 兼容机 | 在保持 WPP 关闭的状态下,在验证机上删除、重新检测、重新安装。用 5.1 的脚本确认是否变成了 IPP 类驱动程序,并进行步骤 5 的比较 |
| 云、虚拟的队列 | 既没有要重新检测的物理设备,也没有排名变更,因此在实际使用的队列上确认步骤 2 的输出 |
7.4 验证之后的恢复,以及整理装机流程
在本地设置应用中启用了 WPP 的验证机,可以用「关闭」改回去。如果是从组策略或 Intune 应用的,就需要管理员一侧更改策略,所以要配合启用的途径准备好恢复步骤。213
即使把 WPP 改回关闭,用 Windows Ready Print 重新装好的打印机也会保持原样。之前不兼容的打印机要手动重新安装。210
在装机方面,要把 WPP 策略和打印机的重新注册步骤,纳入从组策略到 Intune中梳理过的下发机制。用 Point and Print 下发第三方驱动程序,自 2021 年的 KB5005652 起默认就需要管理员凭据,34 而在 WPP 下下发本身就不会进行。9 在使用 WPP 的环境中,要把依赖驱动程序下发的步骤,换成这套策略应用与重新注册的步骤。
flowchart TB
accTitle: 装机流程的重新审视
accDescr: 用 Point and Print 下发第三方驱动程序的步骤,因为 2021 年以后需要管理员凭据而且在 WPP 下下发本身就不会进行,所以要从流程中去掉,改为把 WPP 的策略(组策略或 OMA-URI)和打印机的重新注册步骤纳入下发机制
old["用 Point and Print 下发第三方驱动程序"]
old --> why1["2021 年以后需要管理员凭据"]
old --> why2["WPP 下下发本身不会进行"]
why1 --> drop["从流程中去掉"]
why2 --> drop
drop --> add["纳入 WPP 的策略与重新注册步骤"]
图 25: 下发驱动程序的步骤,被换成了下发 WPP 策略与打印机重新注册的步骤。
8. 总结
需要做的应对,按 「打印目标是否还在」→「代码依赖了什么」→「在什么条件下验证」 的顺序确定。
| 确认结果 | 应对 |
|---|---|
| 队列在 WPP 下不会保留,物理打印机也无法重新注册 | 准备另一条通道,或者先做出不使用 WPP 的判断 |
| 保存了驱动程序专有设置 | 保存意图而不是状态,并在打印之前确认能力 |
| 依赖队列名或虚拟打印机 | 准备好打印目标的存在确认、日志、通知和重新选择。PDF 用库直接生成 |
| RAW 发送的通道在 WPP 下会消失 | 准备不依赖打印后台处理程序的通道 |
| 能确保打印目标,且只做绘制、不依赖特定驱动程序 | 不要一律重写,验证实际输出即可 |
改完一处不等于结束。 改完设置之后,还要接着确认队列名、虚拟打印机、RAW 发送,最后用第 7 章确认输出。对于即使不使用 WPP 也可能遇到 IPP 排名变更的设备,需要做依赖盘点,以及保持 WPP 关闭状态的重新检测验证。
flowchart TB
accTitle: 判断是要修改还是只需确认的决策树
accDescr: 先判定打印目标的队列在 WPP 下是否保留(Universal Print 的云队列或支持 WPP 的虚拟打印机)或者物理打印机能否用 Windows Ready Print 重新注册,如果不行就选择准备另一条通道或不使用 WPP,如果该设备支持 IPP 并且可能因排名变更而被替换,就按驱动程序专有设置的保存、对队列名或虚拟打印机的依赖(包括 SDK 和库内部)、RAW 发送的顺序确认,符合的就修改后进入下一步,最后使用 WPP 的环境进入第 7 章的 WPP 启用验证,不使用的环境中物理的 IPP 兼容机进入保持 WPP 关闭的重新检测验证,云和虚拟的队列进入实际输出确认,既不使用 WPP 也不受替换影响的则保持当前通道继续运维
q0{"队列在 WPP 下保留或可重新注册吗"}
q0 -->|"否"| alt{"该怎么办"}
alt -->|"准备另一条通道"| qi{"支持 IPP 且可能被替换吗"}
alt -->|"不使用 WPP"| qi2{"支持 IPP 且可能被替换吗"}
qi -->|"是"| q1{"是否保存了驱动程序专有设置"}
qi -->|"否"| verify["只做验证(第 7 章)"]
qi2 -->|"是"| q1
qi2 -->|"否"| keep["保持当前通道继续运维"]
q0 -->|"是"| q1
q1 -->|"是"| fix1["修改(5.2)"]
fix1 --> q2{"是否依赖队列名、虚拟打印机"}
sdk["也包括 SDK、库内部的依赖"] -.-> q2
q1 -->|"否"| q2
q2 -->|"是"| fix2["修改(5.3、5.4)"]
fix2 --> q3{"是否把 RAW 发送送往 WPP 下不保留的队列"}
q2 -->|"否"| q3
q3 -->|"是"| fix3["准备通道(第 6 章)"]
fix3 --> vq{"是否使用 WPP"}
q3 -->|"否"| vq
vq -->|"是"| verify
vq -->|"否"| pq{"是否物理的 IPP 兼容机"}
pq -->|"是"| redetect["不启用 WPP 的重新检测验证(7.3)"]
pq -->|"否"| outchk["在实际的队列上确认输出"]
图 26: 先判定队列在 WPP 下是否保留,代码的依赖改完一处也要继续下一项确认,改过的通道最后同样要验证。不使用 WPP 时,不做第 7 章的 WPP 启用,而是让物理的 IPP 兼容机做保持 WPP 关闭的重新检测,让云、虚拟的队列做实际输出确认。
备战的期限,要放在自家的部署计划上而不是 Microsoft 的日期上
据说 WPP 将来会默认启用,但没有给出时期。10 备战的期限要放在自家启用 WPP 之前,或者把 Windows 11 24H2 以后的功能更新推向现场之前。2027 年 7 月 1 日是驱动程序供给侧的节点,并不是业务应用程序一侧的截止日期。1
flowchart TB
accTitle: 业务应用程序一侧备战期限的设定方式
accDescr: 现在就做清单获取和盘点,把通道的更换与验证放在自家启用 WPP 之前或推送 24H2 以后功能更新之前这个自家一侧的期限内完成,2027 年 7 月 1 日的驱动程序停止更新是供给侧的节点而不是备战的期限,要先把系统做成时期未定的 WPP 默认启用无论何时到来都不受影响的状态
now["现在:取清单与盘点"]
now --> prep["通道更换与验证"]
prep --> deadline["期限:自家启用 WPP 之前 / 功能更新推送之前"]
deadline --> fine["WPP 默认启用(时期未定)到来时也不受影响"]
ms["2027 年 7 月 1 日:驱动程序停止更新"] -.->|"是供给侧的节点而不是期限"| prep
图 27: 期限放在自家的部署计划上,不要把 Microsoft 的节点日期当成截止日期。
首先从取得交付现场的清单、掌握设置、打印目标、输出通道的依赖开始。PDF 直接生成,标签和小票则持有一条独立于打印堆栈的通道。在此基础上,再按实际的部署条件确认打印。
停留在 Windows 10 的环境不在这个计划的对象范围内,但普通通道的 Windows 10(22H2)支持已在 2025 年 10 月结束。Enterprise LTSC、IoT Enterprise LTSC 的期限按版本而异,需要确认生命周期。包含 ESU 和 LTSC 的判断请参阅Windows 10 停止支持后的现实解决方案,工业用电脑请参阅工业用PC应该安装哪种Windows。在迁移目标的 Windows 11 上重做一遍第 7 章的验证,才算把打印的准备做完。
相关文章
- Windows 业务应用程序的打印与 PDF 输出 ── System.Drawing.Printing / WPF / 报表库的取舍选型
- Excel 报表输出的实现方法 - COM / Open XML / 模板
- Windows 服务的创建与运维 ── 从任务计划程序的取舍到 BackgroundService 服务化
- 在 Windows 应用中处理 USB 设备的方法 ── 虚拟 COM、HID、WinUSB 该如何选择
- 串口通信应用的陷阱 - 涵盖重连与日志设计
- 从组策略到 Intune ── 中小企业的设备管理迁移
- Windows 10 停止支持后的现实解决方案 ── ESU、LTSC、更换设备的判断
相关的咨询领域
小村软件有限公司承接的工作包括:盘点拥有报表、标签打印的业务应用程序对打印驱动程序的依赖,重新审视打印通道(PDF 直接生成、标签打印机的直接控制),以及设计以 Windows protected print mode 为前提的验证计划。
参考链接
-
Microsoft Learn, End of servicing plan for third-party printer drivers on Windows. 关于 2025 年 5 月更新的时间表(2026 年 1 月 15 日、2026 年 7 月 1 日、2027 年 7 月 1 日)、对象为 Windows 11 以后与 Windows Server 2025 以后、现有驱动程序仍可安装且没有停用 v3/v4 功能的计划、签名例外的 3 个条件(无法取得 Mopria 认证、上限为 Windows 10 及以下、原生 ARM64)、USB 设备只有在 IPP over USB 模式下才能使用各项功能、以及 Windows 10 21H2 以后自带 Microsoft IPP Class Driver。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
Microsoft Learn, Overview of Windows protected print mode. 关于启用时使用第三方驱动程序的打印机会被卸载并从驱动程序存储中删除、即使已取得 Mopria 认证但用第三方驱动程序安装的打印机也需要重新安装、不受支持的软件打印机(如 OneNote (Desktop))与 XPS、传真会被删除、通过组策略启用时用户无法解除、以及在设置应用中启用和关闭的步骤。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Step 2: A Driver Package for the Device is Selected. 关于多个驱动程序包匹配时,Windows 会给每个包评定排名并安装排名最好的那个,排名相同则按日期和版本选择。 ↩
-
Microsoft Learn, IPP printers with the Universal Print Connector. 关于 Microsoft IPP Class Driver 是与已取得 Mopria 认证的打印机通过 IPP 通信的自带驱动程序,以及有些打印机的 IPP 默认关闭需要手动启用。 ↩ ↩2
-
Microsoft Learn, Legacy printer driver submission process. 关于 2026 年 1 月 15 日之后,无论 WHQL 还是 Attestation,打印驱动程序的提交都会被默认阻止,需要附上正当性说明文档进行人工审核。 ↩
-
Microsoft Learn, Windows Print Path Overview. 关于 Windows 中存在 GDI 打印路径和 XPS 打印路径这两条主要的打印路径。 ↩
-
Microsoft Learn, Discover Windows Ready Print. 关于 Windows Ready Print 是包含 IPP、eSCL、Universal Print 的称呼,不需要第三方驱动程序,面向已取得 Mopria 认证的打印机设计,并且不依赖电脑的架构。 ↩
-
Microsoft Learn, Universal Print troubleshooting - Understanding the stages of a print job. 关于 Universal Print 的打印机使用自带的 Universal Print class driver,打印后台处理程序用 IPP over HTTPS 把作业送往服务。 ↩ ↩2 ↩3
-
Microsoft Learn, More information on Windows protected print mode for enterprises and developers. 关于打印缺陷占过去三年 MSRC 报告的 9%、打印后台处理程序以 SYSTEM 运行并加载第三方代码、旧驱动程序与 CFG/CET/ACG 不兼容、IPP 基于 HTTP POST 并用 URI 标识且在客户端渲染 PWG Raster 和 PDF 等少数 PDL、WPP 下的模块加载限制与用户权限的 XPS 渲染、受限令牌、禁止创建子进程和二进制缓解措施、以及 Point and Print 不再安装第三方驱动程序。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Windows protected print mode FAQ. 关于不兼容的打印机在启用期间无法重新安装、关闭之后要手动装回、专有功能通过 Print Support App 提供、以及 Windows protected print mode 将来的某个时点会默认启用。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Printer driver isolation. 关于隔离模式(Shared / Isolated / None)的含义、没有在 INF 中声明
DriverIsolation关键字的驱动程序默认在打印后台处理程序进程内运行、以及管理员可以通过打印管理控制台或打印后台处理程序函数覆盖各驱动程序的设置。 ↩ ↩2 -
Microsoft Learn, What’s new in Windows 11, version 24H2. 关于 Windows protected print mode 在 24H2 中加入,并可通过设置应用或组策略启用。 ↩
-
Microsoft Learn, Policy CSP - Printers: ConfigureWindowsProtectedPrint. 关于适用的操作系统为 Windows 11 24H2 以后、默认关闭且对驱动程序和打印功能没有限制、以及 ADMX 对应的注册表项
Software\Policies\Microsoft\Windows NT\Printers\WPP和值WindowsProtectedPrintGroupPolicyState。 ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Windows protected print mode for enterprises. 关于用组策略「Configure Windows protected print」启用的步骤、Intune 的 OMA-URI、以及从启用了 WPP 的客户端无法用打印管理去管理未启用 WPP 的服务器。 ↩ ↩2 ↩3
-
Microsoft Learn, WindowsProtectedPrintInfo.IsProtectedPrintEnabled Property. 关于在 Windows 11 24H2 中引入的、返回当前设备上 WPP 是否启用的静态属性。 ↩
-
Microsoft Learn, Get-PrinterDriver. 关于它返回指定计算机的打印驱动程序清单,并且不需要管理员凭据。 ↩
-
Microsoft Learn, PnPUtil Command Syntax. 关于
/enum-drivers列举第三方的驱动程序包,Windows 11 21H2 以后可以用/class按类名筛选,以及需要以管理员身份打开命令提示符运行。 ↩ ↩2 -
Microsoft Learn, How to display printer status in a UWP device app. 关于用
get-printer | Select Name, {(get-printerdriver -Name $_.DriverName).MajorVersion}分辨 v3 还是 v4 的做法。 ↩ -
Microsoft Learn, PnPUtil. 关于列举驱动程序存储中的包时不包含自带(in-box)的包,只列举自带以外的包。 ↩
-
Microsoft Learn, PrintQueue.QueueDriver Property. 关于可以把队列所用的打印驱动程序取为
PrintDriver。 ↩ -
Microsoft Learn, PrintServer Class. 关于
System.Printing命名空间的类不支持在 Windows 服务或 ASP.NET 应用程序中使用,可能引起性能下降或运行时异常。 ↩ -
Microsoft Learn, DEVMODEW structure (wingdi.h). 关于可以在公开成员之后紧接着放置驱动程序定义的非公开成员、其大小由
dmDriverExtra指明、Windows 只校验公开部分、以及非公开部分损坏的数据可能让驱动程序崩溃。 ↩ -
dotnet/winforms(GitHub), PrinterSettings.cs. 关于
SetHdevmode会把dmDriverExtra那部分非公开区域复制到内部、GetHdevmode再把它写回、以及其他路径不会持有非公开区域。 ↩ -
Microsoft Learn, PrinterSettings Class. 关于 .NET Framework 版的声明带有
Serializable特性而 .NET 版没有、以及GetHdevmode与SetHdevmode是与DEVMODE的相互转换。 ↩ ↩2 -
Microsoft Learn, SerializableAttribute Class. 关于带有
Serializable特性的类型默认会序列化 private 和 public 的全部字段,要排除则需加上NonSerialized特性。 ↩ -
Microsoft Learn, PaperSize.RawKind Property. 关于
RawKind是表示标准纸张种类的值或自定义值的整数。 ↩ -
Microsoft Learn, PaperSourceKind Enum. 关于除了
Upper、Lower等标准进纸源种类之外,还定义了表示打印机专有进纸源的Custom。 ↩ ↩2 -
Microsoft Learn, Print Schema. 关于 Print Schema 允许第三方扩展,私有的 Property 元素必须属于与该第三方明确关联的命名空间。 ↩
-
Microsoft Learn, Print Schema-Related Technologies. 关于 PrintTicket 是
DEVMODE的后继,设备专有的 PrintTicket 可以包含面向特定机型的私有扩展。 ↩ -
Microsoft Learn, How to: Validate and Merge PrintTickets. 关于用
PrintQueue.GetPrintCapabilities确认打印机支持的功能,并用MergeAndValidatePrintTicket把请求内容合并、校验成打印机专属的有效PrintTicket的步骤。 ↩ -
Microsoft Learn, ConflictStatus Enum. 关于
MergeAndValidatePrintTicket会让驱动程序替换不支持的设置并返回有效的票据,并用ValidationResult.ConflictStatus的ConflictResolved报告发生过替换。 ↩ -
Microsoft Learn, WritePrinter function. 关于从
StartDocPrinter到EndDocPrinter的步骤、数据类型为「RAW」时文档必须用硬件的语言完整描述相当于DEVMODE的设置、以及WritePrinter是阻塞函数从 UI 线程调用时可能显得无响应。 ↩ -
Microsoft Learn, RAW data type. 关于 RAW 数据不经进一步处理就送往打印监视器,由 PCL 命令构成的文件就是一例。 ↩
-
Microsoft Learn, Printing issue troubleshooting guidance. 关于 KB5005652 之后 Point and Print 的默认行为变更使其开始要求管理员凭据,以及 2021 年应用更新程序后 USB 连接的小票、标签打印机无法打印并靠 Known Issue Rollback 解决的案例。 ↩ ↩2
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Windows 业务应用程序的打印与 PDF 输出 ── System.Drawing.Printing / WPF / 报表库的取舍选型
本文按需求整理成判断表,梳理 PrintDocument 的 WinForms 打印、WPF 的 FlowDocument/FixedDocument 打印,以及 PDF 输出的可选方案。并从实务角度讲解分页控制、DPI 偏差等实现陷阱,以及从 Windows 服务打印、依...
WinForms/WPF应用的多语言化 ── resx、附属程序集与区域文化切换的实务
本文从实务角度系统梳理Windows桌面应用多语言化的要点:CurrentCulture与CurrentUICulture的区别、resx与附属程序集构成的资源机制、WinForms的Localizable属性、WPF的现实方案选择、运行时语言切换,以及排版、格式、RTL等内容。
Windows 应用的任务栏托盘常驻与 Toast 通知 —— NotifyIcon 的坑与 AppNotification 的选型
本文整理了将业务 Windows 应用常驻在任务栏托盘(通知区域)并通过 Toast 通知告知用户的实现要点。内容涵盖 NotifyIcon 的正确用法与「关闭后驻留托盘」的设计、资源管理器重启后的重新注册、三种 Toast API(Windows App SDK AppN...
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
UI 线程 & 计时器
整理 WPF / WinForms UI 线程、异步流程、Dispatcher 使用、计时器判断的主题页面。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
因为对拥有报表、标签打印的业务应用程序重新审视并改造打印通道,属于 Windows 应用程序开发的咨询范围。
技术咨询 & 设计评审
因为盘点现有应用程序对打印驱动程序的依赖,并决定替换顺序的设计评审,属于技术咨询的范围。
常见问题
汇总了咨询这一主题时常见的问题。
- 现在正在运行的打印机和应用程序,过了 2026 年 7 月或 2027 年 7 月就会突然印不出来吗?
- 不会。Microsoft 的计划是「不把新的第三方驱动程序上架到 Windows Update」「在排名中优先选择 IPP 类驱动程序」「不再接受更新」这样的供给侧分阶段收尾,文档明确写明它不会停用现有的驱动程序。现有驱动程序仍可从厂商提供的安装程序安装。危险的是两个场面:在 IPP 类驱动程序也匹配的设备上,更换电脑或重装操作系统时驱动程序被自动换成 IPP 类驱动程序的场面;以及启用了 Windows protected print mode 的场面。
- Windows protected print mode 会默认启用吗?
- 在本文撰写时(2026 年 9 月)默认是关闭的,需要在设置应用、组策略或 Intune 中启用。不过 Microsoft 的 FAQ 明确说明「将来的某个时点会默认启用」。由于没有给出时期,先把环境做成即使被启用也不会为难的状态,才是应有的准备。
- 标签打印机和小票打印机会怎么样?
- 对于无法取得 Mopria 认证的打印机,文档把它列入了 2026 年 1 月 15 日之后仍可例外获得驱动程序签名的条件之中。也就是说厂商驱动程序确实有可能暂时保留,但在启用了 Windows protected print mode 的环境中,使用第三方驱动程序的打印机会被卸载,所以照原样是用不了的。如果持有与设备直接通过 TCP/USB/串口通信的厂商 SDK,或者直接发送打印机语言来控制的通道,就能与打印后台处理程序的变化脱钩。内部调用 Windows 队列或驱动程序的 SDK,在第三方驱动程序消失后同样会停下来,因此不能作为退路。
- 应用程序的打印代码保持 PrintDocument 就可以吗?
- GDI 打印路径和 XPS 打印路径都会保留,Microsoft 也明确表示没有停用 v3/v4 驱动程序功能的计划。只是用 PrintDocument 或 FixedDocument 绘制的应用程序,不是需要修改的对象,而是需要确认的对象。需要修改的是:保存和恢复驱动程序专有设置(DEVMODE 的非公开部分或 PrintTicket 的私有命名空间)的代码;依赖特定队列名或虚拟打印机名的代码(包括厂商 SDK 和报表库内部调用的部分);以及把 RAW 数据经打印后台处理程序送往在 WPP 下不会保留的队列(例如厂商驱动程序的队列)的代码。不过,如果打印目标的打印机本身是无法用 Windows Ready Print 重新注册的机型,那么即使代码只做绘制,在 Windows protected print mode 下队列也会整个消失,所以要先准备另一条通道。
- 应该从什么开始动手?
- 从取得交付现场的打印机和驱动程序清单开始。用 PowerShell 的 Get-Printer 和 Get-PrinterDriver,不需要管理员权限就能查出哪个队列在用哪个驱动程序(是 v3 还是 v4,是不是 IPP 类驱动程序)。接着,把该清单与应用程序做名称指定、设置保存、RAW 发送的队列对照起来,用本文的判断表分到「保持原样」「验证」「更换通道」三类中。