首页
/ PowerToys Command Palette 扩展本地开发指南:从稀疏包签名到 CmdPal 热重载的完整循环

PowerToys Command Palette 扩展本地开发指南:从稀疏包签名到 CmdPal 热重载的完整循环

2026-09-06 19:03:48作者:幸俭卉

导读

本文面向需要在本地迭代 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.exePowerToys.ImageResizer.exePowerToys.AdvancedPaste.exe,以及我们要迭代的 Microsoft.CmdPal.Ext.PowerToys.exe
  • 扩展所在 <Application>AppxManifest.xml)声明了两件事:
    1. Executable="Microsoft.CmdPal.Ext.PowerToys.exe":该 exe 相对稀疏包的 ExternalLocation(即输出根目录下的 WinUI3Apps\ 子文件夹)解析;
    2. 一个 windows.comServer(Class Id 7EC02C7D-8F98-4A2E-9F23-B58C2C2F2B17)以及一个 windows.appExtensioncom.microsoft.commandpalette / Id PowerToys),后者把 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

  1. src/PackageIdentity/.user\ 下准备开发证书(见下文"信任开发证书");
  2. 读取 src/Version.props 获取版本号,并把版本写回临时的清单副本(BuildSparsePackage.ps1);
  3. 用 Windows SDK 的 makeappx.exe 把"仅清单 + Images 资产"打成一个不含 payload 的稀疏 MSIX(本地构建还会把发布者改写为开发证书主体 CN=PowerToys Dev, ...,见 BuildSparsePackage.ps1);
  4. signtool.exe 以开发证书指纹签名(CI 可用 -NoSign 跳过,见 BuildSparsePackage.ps1)。

产出位置$(仓库根)/<Platform>/<Configuration>/PowerToysSparse.msix,例如 x64\Debug\PowerToysSparse.msix。构建日志末尾会直接打印下一步要执行的 Add-AppxPackage 命令(BuildSparsePackage.ps1)。

提示:也可以绕过 vcxproj 直接运行 BuildSparsePackage.ps1,脚本支持 -Platformx64/arm64)、-ConfigurationDebug/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,且具备 runFullTrustinternetClientsystemAIModels 等能力(见 AppxManifest.xml),为其中的 full-trust exe 提供稳定的包家族名(Package Family Name)。

步骤 4:以相同平台与配置构建扩展本体

构建 Microsoft.CmdPal.Ext.PowerToys.csproj。关键点:

  • 项目把 OutputPath 直接指向输出根下的 WinUI3Apps\ 子目录:$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\csproj),例如 x64\Debug\WinUI3AppsARM64\Debug\WinUI3Apps——与清单 Executable="Microsoft.CmdPal.Ext.PowerToys.exe" 相对 ExternalLocation 的解析位置精确对应;
  • 未显式传 RuntimeIdentifier 时,按平台自动选择 win-x64win-arm64csproj);
  • 项目以自包含 + AOT 发布方式产出原生代码(SelfContained/PublishAot/PublishTrimmed 均为 true,见 csproj);
  • 它引用了一系列模块服务与公共库,包括 Awake.ModuleServicescolorPicker/ColorPicker.ModuleServicesFancyZonesEditorCommonWorkspaces.ModuleServicesManagedCommonCommon.SearchCommon.UIMicrosoft.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\DebugARM64\Debug 步骤 1 → 3 → 4 → 5 -ExternalLocation 指向的是绝对输出路径,换目录意味着上一次注册失效,需重新注册
只想清理注册(不再需要该稀疏包) 运行 pwsh -File src/PackageIdentity/BuildSparsePackage.ps1 -Unregister 脚本 -Unregister 分支会移除 Microsoft.PowerToys.SparseAppBuildSparsePackage.ps1

普通代码改动时,重建 PackageIdentity 并非必须——稀疏包只是身份的"外壳",内容没变就不必重打、重签、重注册,这能显著缩短每次迭代的周期。

常见问题与排障线索

  • Add-AppxPackage0x800B0109(证书链不受信):开发证书尚未进入受信任存储。按步骤 2 同时导入 TrustedPeopleTrustedRoot 后重试。
  • 注册成功但 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 是理解这套机制的最佳入口。

登录后查看全文
热门项目推荐
相关项目推荐