PowerToys Command Palette 扩展本地开发指南:从稀疏包签名到 CmdPal 热重载的完整循环
导读
本文面向需要在本地迭代 Microsoft.CmdPal.Ext.PowerToys 扩展代码的开发者,完整讲解"在 Windows 源码仓库中编译该 Command Palette 扩展并用稀疏包(Sparse Package)为其授予包标识"的本地开发流程。读完本文你将掌握:PackageIdentity 项目如何产出带签名的 PowerToysSparse.msix、为何扩展二进制必须落入 WinUI3Apps\ 子目录、如何信任开发证书并执行 Add-AppxPackage 注册,以及普通代码改动与清单改动分别应重做哪些步骤。
背景:为什么扩展需要一个"稀疏包"
Command Palette(CmdPal)通过 Windows 的 app extension 机制发现第三方命令提供者。扩展若要以 Win32(full-trust)进程形式承载并提供包标识,就必须被一个已注册的 MSIX 包"包装"。在 PowerToys 仓库中,这个身份载体是稀疏包(Sparse Package)——一种只包含清单、不含 payload 的 MSIX,作用是仅授予包标识(Package Identity),实际可执行文件仍放在外部目录。
仓库用 PackageIdentity/AppxManifest.xml 定义这个共享身份:
<Identity Name="Microsoft.PowerToys.SparseApp" ... Version="0.0.1.0" />:包名与版本;本地构建时,版本与发布者会被 BuildSparsePackage.ps1 依据src/Version.props和开发证书动态改写。- 该清单把多个 full-trust Win32 组件归入同一个
Microsoft.PowerToys.SparseApp,包括PowerToys.Settings.exe、PowerToys.ImageResizer.exe、PowerToys.AdvancedPaste.exe,以及我们要迭代的Microsoft.CmdPal.Ext.PowerToys.exe。 - 扩展所在
<Application>(AppxManifest.xml)声明了两件事:Executable="Microsoft.CmdPal.Ext.PowerToys.exe":该 exe 相对稀疏包的ExternalLocation(即输出根目录下的WinUI3Apps\子文件夹)解析;- 一个
windows.comServer(Class Id7EC02C7D-8F98-4A2E-9F23-B58C2C2F2B17)以及一个windows.appExtension(com.microsoft.commandpalette/ IdPowerToys),后者把 CmdPal 扩展的激活指向同一个 Class Id。
因此稀疏包与扩展必须针对同一平台与配置构建(例如同为 x64\Debug),否则清单中的相对路径无法解析到真实二进制。
相关来源:扩展本体是一个 C# WinExe(见 Microsoft.CmdPal.Ext.PowerToys.csproj),入口
Main只在收到-RegisterProcessAsComServer参数时才进入 COM Server 模式(见 Program.cs),并用[Guid("7EC02C7D-...")](PowerToysExtension.cs)与清单中的Class Id一一对应——这正是"注册身份"与"进程内实现"通过 GUID 耦合的源码证据。
本地开发循环:五个步骤
官方文档(powertoys-extension-local-development.md)给出的标准循环如下,下面结合仓库实现逐条展开。
步骤 1:构建 PackageIdentity,产出稀疏 MSIX
构建 PackageIdentity.vcxproj。它本质是一个 Utility 类型的 C++ 项目,真正的逻辑在 BuildSparsePackage.ps1(vcxproj 在 PrepareForBuild 之前以 pwsh -File BuildSparsePackage.ps1 -Platform ... -Configuration ... 调用它,见 PackageIdentity.vcxproj)。
脚本按如下步骤产出 PowerToysSparse.msix:
- 在
src/PackageIdentity/.user\下准备开发证书(见下文"信任开发证书"); - 读取
src/Version.props获取版本号,并把版本写回临时的清单副本(BuildSparsePackage.ps1); - 用 Windows SDK 的
makeappx.exe把"仅清单 + Images 资产"打成一个不含 payload 的稀疏 MSIX(本地构建还会把发布者改写为开发证书主体CN=PowerToys Dev, ...,见 BuildSparsePackage.ps1); - 用
signtool.exe以开发证书指纹签名(CI 可用-NoSign跳过,见 BuildSparsePackage.ps1)。
产出位置:$(仓库根)/<Platform>/<Configuration>/PowerToysSparse.msix,例如 x64\Debug\PowerToysSparse.msix。构建日志末尾会直接打印下一步要执行的 Add-AppxPackage 命令(BuildSparsePackage.ps1)。
提示:也可以绕过 vcxproj 直接运行 BuildSparsePackage.ps1,脚本支持
-Platform(x64/arm64)、-Configuration(Debug/Release)、-Clean、-ForceCert、-NoSign、-CIBuild、-DevRegister、-Unregister等参数(见脚本开头 [Param] 块)。其中-DevRegister会自动完成"信任证书 + 注册稀疏包"两件事,适合一键开发注册。
步骤 2:信任开发证书
Add-AppxPackage 只接受受信任签名者的包。构建脚本会自动生成(或复用)开发证书 src/PackageIdentity/.user/PowerToysSparse.certificate.sample.cer,并把其私钥保存在 Cert:\CurrentUser\My(证书主体 CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US,有效期 12 个月,见 [BuildSparsePackage.ps1](https://gitcode.com/GitHub_Trending/po/PowerToys/blob/7bf87a308bdfe13313228560e2a3e31683f76172/src/PackageIdentity/BuildSparsePackage.ps1?utm_source=gitcode_repo_files#L50-L56, L193-L232))。
将其导入 CurrentUser\TrustedPeople:
$repoRoot = "C:/git/PowerToys"
Import-Certificate -FilePath "$repoRoot/src/PackageIdentity/.user/PowerToysSparse.certificate.sample.cer" -CertStoreLocation Cert:\CurrentUser\TrustedPeople
若 Windows 仍报告信任失败(典型错误码 0x800B0109,即证书链不受信),把同一证书也导入 Cert:\CurrentUser\TrustedRoot。这也是脚本 -DevRegister 分支默认同时导入两个存储的原因(BuildSparsePackage.ps1)。
步骤 3:注册稀疏包
执行构建输出里打印的 Add-AppxPackage 命令。它的形态是:
Add-AppxPackage -Path "<repo>\<Platform>\<Configuration>\PowerToysSparse.msix" -ExternalLocation "<repo>\<Platform>\<Configuration>\WinUI3Apps"
-Path指向刚生成的稀疏 MSIX;-ExternalLocation指向同一输出根下的WinUI3Apps\目录——这就是扩展 exe 所在目录,稀疏包的"外部内容"都从这里按清单相对路径解析(清单里同时有<uap10:AllowExternalContent>true</uap10:AllowExternalContent>,见 AppxManifest.xml)。
注册成功后,系统内出现包 Microsoft.PowerToys.SparseApp,且具备 runFullTrust、internetClient、systemAIModels 等能力(见 AppxManifest.xml),为其中的 full-trust exe 提供稳定的包家族名(Package Family Name)。
步骤 4:以相同平台与配置构建扩展本体
构建 Microsoft.CmdPal.Ext.PowerToys.csproj。关键点:
- 项目把
OutputPath直接指向输出根下的WinUI3Apps\子目录:$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\(csproj),例如x64\Debug\WinUI3Apps或ARM64\Debug\WinUI3Apps——与清单Executable="Microsoft.CmdPal.Ext.PowerToys.exe"相对ExternalLocation的解析位置精确对应; - 未显式传
RuntimeIdentifier时,按平台自动选择win-x64或win-arm64(csproj); - 项目以自包含 + AOT 发布方式产出原生代码(
SelfContained/PublishAot/PublishTrimmed均为 true,见 csproj); - 它引用了一系列模块服务与公共库,包括
Awake.ModuleServices、colorPicker/ColorPicker.ModuleServices、FancyZonesEditorCommon、Workspaces.ModuleServices、ManagedCommon、Common.Search、Common.UI与Microsoft.CommandPalette.Extensions.Toolkit(见 csproj),因此这些模块的改动也可能需要一并重新编译。
构建完成后,检查 x64\Debug\WinUI3Apps\Microsoft.CmdPal.Ext.PowerToys.exe 是否已更新。
步骤 5:重启 Command Palette
关闭所有正在运行的 CmdPal 实例后重新启动。CmdPal 在启动时重新枚举已注册的 app extension(com.microsoft.commandpalette),激活并加载新的扩展进程(-RegisterProcessAsComServer 模式)。若扩展未重新加载,优先排查是否残留了旧的 CmdPal / 扩展进程,或确认步骤 4 的产物确实覆盖了 WinUI3Apps 下的 exe。
何时需要重做哪些步骤
官方文档给出了精简的取舍原则,展开如下:
| 改动类型 | 需要执行的步骤 | 原因(源码依据) |
|---|---|---|
| 普通 C# 代码改动(如新增某个模块命令、修改命令逻辑) | 仅步骤 4 + 步骤 5 | 清单不变、证书不变、身份不变,只需更新 WinUI3Apps 下的 exe 并让 CmdPal 重载 |
稀疏包清单变化(如新增 <Application>、改 Class Id、改 Capabilities) |
步骤 1 → 3 → 4 → 5 | 清单内容在打包时才固化进 msix,注册信息以注册时的清单为准 |
签名证书变化(证书过期、被删除、-ForceCert 重建) |
步骤 1 → 2 → 3 → 4 → 5 | 新证书的 Thumbprint/发布者不同,必须重新签名并重新信任,注册时的发布者需与清单一致 |
切换输出根(如 x64\Debug ↔ ARM64\Debug) |
步骤 1 → 3 → 4 → 5 | -ExternalLocation 指向的是绝对输出路径,换目录意味着上一次注册失效,需重新注册 |
| 只想清理注册(不再需要该稀疏包) | 运行 pwsh -File src/PackageIdentity/BuildSparsePackage.ps1 -Unregister |
脚本 -Unregister 分支会移除 Microsoft.PowerToys.SparseApp(BuildSparsePackage.ps1) |
普通代码改动时,重建 PackageIdentity 并非必须——稀疏包只是身份的"外壳",内容没变就不必重打、重签、重注册,这能显著缩短每次迭代的周期。
常见问题与排障线索
Add-AppxPackage报0x800B0109(证书链不受信):开发证书尚未进入受信任存储。按步骤 2 同时导入TrustedPeople与TrustedRoot后重试。- 注册成功但 CmdPal 里看不到 PowerToys 命令:确认扩展 exe 是否存在于
-ExternalLocation指向的WinUI3Apps\目录,且与稀疏包同平台同配置;确认 CmdPal 已完全退出后重启。 - 改了代码但行为没变化:检查 csproj 是否把输出写到了同一个输出根(路径拼接来自
$(RepoRoot)$(Platform)\$(Configuration),平台不一致时会落到另一个目录);必要时在扩展进程内查看日志。
扩展本身把运行时日志写到 \CmdPal\PowerToysExtension\Logs(见 Program.cs),启动参数、进程架构等信息都会记录在该目录下,是排查加载失败的第一手资料。
小结
PowerToys 的命令面板扩展采用"稀疏包提供身份 + ExternalLocation 外部内容 + WinUI3Apps 输出目录"三层结构,把 MSIX 身份管理与实际二进制迭代解耦。日常开发只需遵循"重编译扩展 → 重启 CmdPal"的快速循环;只有触及清单、证书或切换输出根时,才需要重走"重建 PackageIdentity → 信任证书 → Add-AppxPackage 注册"的完整链路。相关脚本 BuildSparsePackage.ps1 与清单 AppxManifest.xml 是理解这套机制的最佳入口。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00