在 Windows 使用过程中,Windows Update 是一项非常重要的系统基础设施,但在某些场景下,用户可能希望暂时控制自动更新行为。例如测试环境、旧软件兼容性环境、长期运行的专用设备,或者需要避免系统在特定时间自动重启的场景。
UpdateLock 正是针对这一需求设计的一个轻量级 Windows 工具。项目地址:
与许多依赖脚本、PowerShell 或第三方运行时的方案不同,UpdateLock 采用 原生 C++/Win32 实现,以单文件 EXE 的形式运行,并针对 Windows 7、Windows 10 和 Windows 11 分别采用不同的系统策略。
本文主要从工程实现角度介绍项目。关闭系统更新可能降低系统安全性,实际使用时应根据设备用途和安全策略谨慎决定。
一、为什么要做 UpdateLock?
Windows Update 并不是简单地“开”或者“关”。
不同 Windows 版本在更新机制、策略项以及相关服务方面存在明显差异。尤其是 Windows 7 与 Windows 10/11,在更新管理模型上并不完全相同。
因此,一个真正面向多个 Windows 版本的更新控制工具,需要解决几个问题:
- 如何识别当前 Windows 版本?
- 不同系统应该修改哪些策略?
- 如何避免粗暴地删除系统文件或者破坏更新组件?
- 修改之前如何保存用户原有配置?
- 如果修改失败,如何恢复现场?
- 恢复之后如何确认配置真的恢复成功?
- 如何在没有 .NET、PowerShell 或其他额外组件的情况下运行?
UpdateLock 的设计基本围绕这些问题展开。
项目 README 将它定义为一个“Win7/10/11 自动更新关闭工具”,采用原生 Win32 x86 EXE,并通过 Windows Registry API 和 Service Control Manager API 完成系统配置修改。
二、项目最大的特点:原生 Win32
UpdateLock 并没有选择 PowerShell、批处理或者 reg.exe、sc.exe 这样的外部命令,而是直接调用 Windows API。
从源码可以看到,它直接包含了:
windows.hcommctrl.hshellapi.hshlobj.hsddl.haclapi.hwincrypt.hobjbase.hwuapi.h
核心逻辑直接建立在 Win32 API 之上。
这种设计带来的好处非常明显。
1. 不依赖 PowerShell
很多 Windows 自动化脚本都会执行 PowerShell,例如:
Set-Service
Set-ItemProperty
Get-Service
而 UpdateLock 不依赖这些命令。
这意味着它不需要考虑 PowerShell 是否可用、执行策略是否限制脚本,以及外部命令输出解析等问题。
2. 不依赖 .NET Runtime
项目采用静态 CRT 的方式构建,目标是在 Windows 7 SP1、Windows 10 和 Windows 11 上直接运行。
因此最终用户面对的就是一个比较传统的:
UpdateLock.exe
而不是需要先安装运行环境的软件包。
3. 更直接的错误处理
直接调用 Win32 API,也意味着程序可以获得更加明确的系统错误码。
源码中定义了 ThrowWin32(),会把 Win32 错误码转换为用户可以理解的错误信息,例如:
Win32 5:拒绝访问
这种方式比简单地判断一个外部命令返回值更加直接。
三、Win7 与 Win10/11 为什么要区别处理?
这是 UpdateLock 设计中比较值得关注的一点。
项目没有采用“一套策略打天下”的方式,而是明确将 Windows 7 与 Windows 10/11 分开处理。
Windows 7
Windows 7 的处理主要涉及:
NoAutoUpdateDisableWindowsUpdateAccesswuauserv
其中 wuauserv 是 Windows Update 服务。
也就是说,Win7 的方案除了修改策略之外,还会通过 Windows 的 Service Control Manager 对 Windows Update 服务进行停止和禁用。
但项目刻意没有进一步处理:
- BITS
- CryptSvc
- TrustedInstaller
- SoftwareDistribution
- Catroot2
- 系统文件
这种克制其实非常重要。
一个系统工具如果为了“彻底关闭更新”而直接删除更新缓存、修改系统目录甚至替换系统文件,很容易从一个配置工具变成系统破坏工具。
UpdateLock 的方案明显更倾向于:
只修改完成目标所必需的配置。
四、Windows 10/11:主要通过策略控制
Windows 10 和 Windows 11 的处理方式有所不同。
项目主要涉及以下策略:
ExcludeWUDriversInQualityUpdate
SetUpdateNotificationLevel
UpdateNotificationLevel
NoAutoUpdate
NoAutoRebootWithLoggedOnUsers
AUOptions
其中比较值得注意的是 AUOptions。
关闭时,项目不是简单地把它固定成某个值,而是采用:
关闭时删除,恢复时重新写回原来的状态。
这体现了一个很重要的配置管理思想:
“恢复”不应该意味着恢复成程序认为的默认值,而应该恢复成用户运行程序之前的状态。
例如用户原来的配置可能是:
AUOptions = 4
程序执行关闭操作后,这个值可能被删除。
当用户选择恢复时,程序重新写入:
AUOptions = 4
而不是自作主张地写入另一个默认值。
五、2.1 版本进一步限制 Windows Update 入口
UpdateLock 2.1 又增加了一项:
SetDisableUXWUAccess=1
这个策略的目标并不是停止 Windows Update 服务,而是进一步限制 Windows Update 的用户界面入口。
简单来说,就是减少用户通过 Windows Update 页面进行:
- 手动检查更新
- 下载更新
- 安装更新
等操作的机会。
这里有一个很重要的区别:
Windows 10/11 不会因此直接停止 wuauserv、BITS、UsoSvc、WaaSMedicSvc 或 Update Orchestrator。
这说明项目作者并没有采用“看到更新服务就全部禁用”的思路,而是尽量使用 Windows 本身提供的策略机制。
从系统稳定性的角度看,这是一种相对保守的方案。
六、备份与恢复是这个项目的核心设计之一
如果只是修改注册表,其实并不难。
真正困难的是:
修改之后如何可靠地恢复?
UpdateLock 在这一点上做得比较完整。
Windows 10/11 的策略备份位于:
%ProgramData%\UpdateLock\policy-backup-v1.txt
新增的 Windows Update 用户界面访问限制,则单独保存:
%ProgramData%\UpdateLock\windows-update-access-backup-v1.txt
Windows 7 使用:
%ProgramData%\UpdateLock\windows7-original-state-v1.txt
这种设计意味着程序在修改系统之前,会先记录原始状态。
而不是简单地假设:
恢复 = 删除我刚刚写入的值
两者有本质区别。
例如:
用户原配置
↓
读取并保存
↓
UpdateLock 修改
↓
Windows Update 被限制
↓
用户选择恢复
↓
重新写入原配置
这样才能真正做到“恢复”。
七、源码中的 RAII:避免资源泄漏
作为一个原生 C++ Windows 程序,UpdateLock 还使用了 RAII 来管理系统资源。
源码中可以看到几个典型的封装:
ScopedHandle
ScopedRegKey
ScopedServiceHandle
例如:
struct ScopedRegKey {
HKEY value = nullptr;
~ScopedRegKey() {
if (value) RegCloseKey(value);
}
};
这类设计解决的是一个非常典型的 C++/Win32 问题。
Win32 API 大量使用类似:
HANDLE
HKEY
SC_HANDLE
的资源句柄。
如果程序中途发生异常,很容易出现:
打开资源
↓
执行操作
↓
中途失败
↓
没有释放资源
而 RAII 可以让资源生命周期与 C++ 对象绑定。
因此:
{
ScopedRegKey key;
// 操作注册表
}
离开作用域后,注册表句柄自动释放。
这对于一个需要频繁处理注册表、服务和文件的系统工具来说尤其重要。
八、注册表操作并不是简单的“写值”
源码中的注册表逻辑也比较值得分析。
程序定义了类似这样的策略描述:
struct PolicyValue {
const wchar_t* path;
const wchar_t* name;
};
然后使用:
ReadDwordState()
SetDword()
DeleteDword()
RestorePolicy()
VerifyPolicy()
形成了一套比较清晰的操作链。
可以抽象为:
Read
↓
Backup
↓
Modify
↓
Read Back
↓
Verify
而不是:
Modify
↓
Done
这就是系统工具和简单脚本之间一个很重要的区别。
九、修改之后还要“回读验证”
UpdateLock 有一个很实用的机制:
写入之后重新读取注册表进行验证。
源码中可以看到:
void VerifyPolicy(const PolicyState& expected)
它会重新调用读取函数,然后比较:
exists
value
是否与预期一致。
如果不一致,就抛出错误。
这意味着程序不是看到:
RegSetValueExW()
返回成功,就认为整个操作已经完成。
而是:
写入成功
↓
重新读取
↓
比较预期状态
↓
确认一致
对于涉及系统策略的工具来说,这种设计可以显著提高操作的可靠性。
十、失败回滚
进一步看,这套设计还可以形成一个完整的事务式流程:
保存原始状态
↓
修改策略 A
↓
修改策略 B
↓
修改策略 C
↓
某一步失败
↓
恢复 A
↓
恢复 B
↓
恢复 C
也就是说,它并不是简单执行一连串互不相关的注册表写操作。
如果中途失败,程序可以尝试将之前已经修改的内容恢复到原状态。
对于 Windows 系统配置工具来说,这一点非常关键。
因为系统配置修改最怕的并不是“完全失败”,而是:
成功了一半。
例如:
策略 1 修改成功
策略 2 修改成功
策略 3 修改失败
如果没有回滚机制,用户最终得到的就是一个半修改状态。
十一、备份文件本身也考虑了安全性
源码中还有一个容易被忽略的细节:
程序会创建:
%ProgramData%\UpdateLock
并设置目录权限。
源码使用安全描述符:
D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)
也就是说,备份目录的访问权限被限制在系统账户和管理员范围。
这个设计很有意义。
因为备份文件中记录的是系统策略状态,如果允许普通用户随意修改这些文件,就可能出现:
程序保存状态
↓
文件被篡改
↓
用户点击恢复
↓
程序恢复恶意/错误配置
所以对于系统配置备份而言:
备份文件不是普通的文本文件。
它同样需要考虑完整性和权限。
十二、备份格式为什么使用 Base64?
源码还提供了:
Base64Encode()
Base64Decode()
并结合 UTF-8 与 UTF-16 之间的转换。
Windows API 大量使用 Unicode 字符串,而备份文件本身则采用 UTF-8 文本格式。
因此项目需要处理:
Windows UTF-16
↓
UTF-8
↓
Base64
↓
备份文件
恢复时再反向转换。
这种设计的好处是让备份文件格式更加明确,同时避免某些特殊字符、换行符或者本地编码环境造成解析问题。
十三、管理员权限与单实例保护
因为修改的是:
HKEY_LOCAL_MACHINE
以及 Windows 服务等系统级资源,所以程序需要管理员权限。
除此之外,项目还加入了单实例保护。
源码中定义了:
const wchar_t* const kMutexName =
L"Global\\UpdateLock.SingleInstance.7A64EA9A";
通过 Windows Mutex 可以避免用户同时启动多个 UpdateLock 实例。
为什么需要单实例?
因为系统配置修改通常不是适合并发执行的操作。
如果两个实例同时执行:
实例 A:读取旧配置
实例 B:读取旧配置
实例 A:写入新配置
实例 B:写入新配置
实例 A:备份
实例 B:备份
最终的备份状态就可能出现问题。
因此:
单实例保护实际上也是配置事务安全的一部分。
十四、项目没有“暴力破坏”Windows Update
从工程角度来看,UpdateLock 一个值得肯定的地方,是它对修改边界控制得比较明确。
它没有把目标扩展成:
删除 SoftwareDistribution
删除 Catroot2
禁用所有相关服务
删除更新文件
修改系统文件
阻止 Windows Update 进程启动
而是主要集中在:
Windows Update Policy
+
必要的服务控制
+
备份与恢复
这是一种更符合系统工具设计原则的方式:
尽量通过操作系统提供的配置接口实现需求,而不是绕过系统机制。
十五、对普通用户来说,它解决了什么问题?
如果从用户角度来看,UpdateLock 的价值可以概括成一句话:
用一个原生、轻量的 Windows 程序,把复杂的更新策略修改、备份、恢复和验证封装起来。
用户不需要手动打开:
注册表编辑器
本地组策略
服务管理器
命令提示符
PowerShell
然后逐项修改。
程序负责完成:
识别系统
↓
读取原配置
↓
备份
↓
修改策略
↓
验证
↓
必要时回滚
恢复时则反过来:
读取备份
↓
恢复原配置
↓
回读
↓
验证
这比让普通用户手动操作注册表要友好得多。
十六、它也存在明确的使用边界
需要特别强调的是:
关闭或限制 Windows Update 并不是没有代价的。
首先,Windows Update 不只是负责系统补丁,也涉及驱动程序等内容。
项目 README 特别指出,关闭 Windows Update 的驱动更新后,新接入的 USB 或其他硬件可能无法自动在线获取驱动。
因此如果设备需要安装新硬件,可以考虑:
恢复 Windows Update
↓
安装/获取驱动
↓
重新关闭更新策略
此外,不建议在 Windows 正在安装或卸载更新时执行关闭或恢复操作。
对于生产环境设备,也应该提前确认:
- 是否存在企业更新策略;
- 是否有安全补丁要求;
- 是否依赖 Windows Update 获取驱动;
- 是否有统一终端管理系统;
- 是否有恢复系统配置的要求。
换句话说:
UpdateLock 是一个系统配置工具,而不是 Windows 更新机制的替代品。
十七、构建方式
项目使用 Visual Studio Build Tools、MSVC v141 工具链以及 Windows 10 SDK 19041。
在 native 目录执行:
build-native.cmd
即可进行原生构建。
项目 README 表明,构建输出位于:
native\build
并且构建脚本同时生成:
UpdateLock.exe
以及原生测试程序。
这种项目结构比较适合希望研究 Win32 系统编程的开发者。
你可以直接从源码看到:
Win32 API
↓
Registry API
↓
SCM API
↓
文件 API
↓
安全描述符 API
↓
GUI
而不是隐藏在大量第三方框架之后。
十八、如何评价这个项目?
如果从“功能复杂度”来看,UpdateLock 并不是一个特别庞大的项目。
但如果从“系统工具的工程质量”来看,它里面包含了不少值得学习的设计:
1. 原生 API
避免依赖 PowerShell、CMD、reg.exe 和 sc.exe。
2. 状态备份
修改前记录原始配置。
3. 可恢复
恢复的是用户原来的状态,而不是简单恢复默认值。
4. 回读验证
写入之后再次读取确认。
5. 失败回滚
尽可能避免系统处于半修改状态。
6. RAII
自动管理 Win32 资源。
7. 权限控制
限制备份目录访问权限。
8. 单实例
避免多个配置操作同时进行。
这些设计其实已经超出了一个简单“关闭 Windows Update 脚本”的范畴。
十九、从这个项目可以学到什么?
如果你正在学习 Windows C++ 开发,UpdateLock 值得关注的并不仅仅是“如何关闭 Windows Update”。
更值得学习的是它背后的工程思路:
不要直接修改
↓
先读取状态
不要假设修改成功
↓
写入后验证
不要覆盖用户配置
↓
先备份
不要只考虑成功路径
↓
设计失败回滚
不要依赖外部命令
↓
直接调用系统 API
不要无限扩大修改范围
↓
只操作必要的系统接口
这些原则同样适用于很多其他系统工具,例如:
- Windows 服务配置器;
- 注册表策略管理工具;
- 系统初始化工具;
- 企业终端配置工具;
- 本地安全策略管理器;
- 系统维护程序。
二十、结语
UpdateLock 是一个体量不大的 Windows 原生工具,但它体现了一种比较典型的系统工具开发思路:少依赖、直接调用系统 API、明确修改边界,并把备份、验证和恢复放在核心位置。
它并没有试图通过删除系统文件或者粗暴禁用所有相关服务来解决 Windows Update 问题,而是针对 Windows 7 与 Windows 10/11 的差异分别设计策略。
尤其是:
原始状态备份
+
策略修改
+
回读验证
+
失败回滚
+
完整恢复
构成了整个项目比较重要的工程闭环。
对于普通用户来说,它提供的是一个简单的 Windows Update 控制工具;而对于开发者来说,它更值得研究的部分,则是 Win32 注册表操作、服务控制、权限管理、RAII、原子文件写入以及系统配置事务化处理。
如果你的目标是学习 Windows 原生 C++,这个项目是一个不错的小型案例:代码规模并不庞大,却覆盖了不少真实 Windows 系统工具会遇到的问题。
项目地址:
https://github.com/hhht110/UpdateLock
注:本文依据 UpdateLock 仓库当前 README 与源码进行整理。Windows 更新策略会随着系统版本、补丁和企业管理策略变化,具体行为应以目标系统实际测试结果为准。






暂无评论内容