一个用 C# + WinUI 3(Windows App SDK)+ Windows Community Toolkit 写的 Windows 自定义 URL 协议管理工具。
可以像 weixin:// 那样创建自己的协议,把它绑定到本机任意可执行程序,并把协议 URL 里的参数转发给目标程序;
同时提供新增 / 编辑 / 删除 / 一键测试等管理能力,并通过 GitHub Actions 在 windows-latest 上自动构建出可安装的 MSIX。
| 功能 | 说明 |
|---|---|
| 创建协议 | 形如 myapp://,写入 HKCU\Software\Classes\myapp |
| 绑定程序 | 每个协议绑定一个本机可执行文件(.exe / .bat / .cmd / .ps1 或任意文件) |
| 参数转发 | 默认把完整 URL 交给目标程序;也可只传 host / path / id 之类的片段 |
| 管理 | 新增、编辑、删除、启用/停用、搜索、一键「全部重新注册」 |
| 状态可视 | 每行实时显示 已注册 / 未注册 / 需更新 / 被其他程序占用 / 已停用 |
| 一键测试 | 通过 Shell 发起 myapp://open?id=123,验证能否唤起目标程序 |
| 导出 | 把任一协议导出成 .reg 文件,方便批量部署或备份 |
| 免管理员 | 只写 HKEY_CURRENT_USER,不需要提权,不影响同机其他用户 |
界面全部使用 WinUI 原生控件(NavigationView、InfoBar、ContentDialog、ItemsRepeater、ToggleSwitch)与
Windows Community Toolkit 的 SettingsCard(卡片式布局)/ BoolToVisibilityConverter 以及 CommunityToolkit.Mvvm(MVVM Toolkit),没有自造控件。
-
把本仓库推到 GitHub(默认分支
main或master)。 -
Actions→ Build MSIX → 等待完成。 -
下载名为
UrlProtocolManager-x64-Release的构建产物,解压后得到:Install.ps1 UrlProtocolManager.cer UrlProtocolManager_1.0.0.0_x64.msix -
在同一目录下打开 PowerShell 执行:
.\Install.ps1 -Package .\UrlProtocolManager_1.0.0.0_x64.msix
脚本会先把自签名证书导入「当前用户 → 受信任人」,再
Add-AppxPackage安装。不需要管理员权限。
CI 生成的是每次构建临时创建的自签名证书,仅用于旁加载测试,不适合对外分发。 若要正式分发,请在仓库 Settings → Secrets 里配置自己的证书(见 使用自己的签名证书)。
UrlProtocolManager/
├── .github/
│ └── workflows/
│ └── build-msix.yml # GitHub Actions:还原 → 编译 → 打包 MSIX → 签名 → 上传产物
├── install/
│ └── Install.ps1 # 用户侧安装脚本(导入证书 + Add-AppxPackage)
├── src/
│ └── UrlProtocolManager/
│ ├── UrlProtocolManager.csproj
│ ├── Package.appxmanifest # ★ 含 unvirtualizedResources / RegistryWriteVirtualization
│ ├── App.xaml(.cs)
│ ├── MainWindow.xaml(.cs) # NavigationView + 自定义标题栏
│ ├── Assets/ # MSIX 图标(已生成好)
│ ├── Models/
│ │ └── ProtocolRegistration.cs
│ ├── Services/
│ │ ├── AppPaths.cs # %LOCALAPPDATA%\UrlProtocolManager
│ │ ├── NativeMethods.cs # SHChangeNotify(SHCNE_ASSOCCHANGED)
│ │ ├── ProtocolCommandBuilder.cs # 令牌展开 + 生成 shell\open\command
│ │ ├── ProtocolLauncher.cs # 测试调用(Shell 发起 URL)
│ │ ├── ProtocolRegistry.cs # ★ 读写 HKCU\Software\Classes
│ │ ├── ProtocolStore.cs # JSON 持久化(原子写)
│ │ ├── ProtocolValidator.cs # 协议名 / 路径校验
│ │ └── ScriptLauncher.cs # 生成 PowerShell 启动脚本
│ ├── ViewModels/
│ │ ├── IProtocolDialogService.cs
│ │ ├── ProtocolItemViewModel.cs
│ │ └── ProtocolsViewModel.cs
│ └── Views/
│ ├── ProtocolsPage.xaml(.cs) # 协议列表
│ ├── AboutPage.xaml(.cs)
│ ├── ProtocolEditDialog.xaml(.cs)
│ └── DialogService.cs
├── UrlProtocolManager.sln
├── .gitignore
└── README.md
Windows 的自定义 URL 协议本质上就是一个 HKEY_CLASSES_ROOT\<scheme> 键,带一个 URL Protocol 空值和一个 shell\open\command。
HKCR 是 HKLM\Software\Classes 与 HKCU\Software\Classes 的合并视图,所以写 HKCU 即可做到「仅当前用户、免管理员」。
本应用为每个协议写入:
HKEY_CURRENT_USER\Software\Classes\myapp
(默认) = "URL:myapp"
"URL Protocol" = "" ← 告诉 Windows 这是一个 URL 协议
"FriendlyTypeName" = "我的应用"
"UrlProtocolManagerId" = "<内部 GUID>" ← 标记:这个键是本应用创建的
DefaultIcon\(默认) = "C:\Apps\MyApp.exe",0
shell\open\command\(默认) = "C:\Apps\MyApp.exe" "%1"
%1 由 Windows 在调用时替换为完整 URL。写完还会调用 SHChangeNotify(SHCNE_ASSOCCHANGED),让资源管理器立刻生效,无需注销。
UrlProtocolManagerId是识别「谁创建的」的关键:删除时如果发现这个键不是本应用写的,会先弹确认框,不会误删别的程序注册的协议。
这是本项目唯一需要特别注意的地方,也是很多同类工具做不出来的原因。
MSIX 打包应用对 HKCU 和 %AppData% 的写入默认是虚拟化的:写进的是一个只有自己能看见的私有 hive(User.dat),
资源管理器和其他程序根本看不到,因此协议注册「看起来成功了但就是调不起来」。
解决办法是在清单里关闭虚拟化(PowerShell 里验证过的写法):
<Package
xmlns:rescap=".../restrictedcapabilities"
xmlns:desktop6=".../desktop/windows10/6"
IgnorableNamespaces="uap rescap desktop desktop6">
<Properties>
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
</Properties>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources" />
</Capabilities>
</Package>unvirtualizedResources是必需的受限能力,否则上面两个属性不生效。- 两个属性都要关:注册表要真实写入;
%AppData%也要真实,否则外部进程(powershell.exe)读不到生成的启动脚本。 - 该功能需要 Windows 10 2004(10.0.19041)及以上,清单里的
MinVersion已按此设置。 - 代价:关闭虚拟化后,这些注册表项不会随应用卸载被系统自动清理。请在应用内点「删除」来移除协议。
注册表里的命令串是静态的,Windows 只会把 %1 替换成完整 URL。因此:
模式 A · 原生传参(默认,零额外进程)
模板只含 %1 / {url} 时,注册表命令就是:
"C:\Apps\MyApp.exe" "%1"
调用 myapp://open?id=123 时目标程序收到完整 URL。速度最快,无中间进程。
模式 B · 脚本解析参数(用到其它令牌时自动切换)
若模板用了 {host} / {path} / {query} / {query:id} / {fragment},
应用会生成一份 PowerShell 启动脚本(保存在数据目录的 Launchers\<scheme>.ps1),注册表命令变成:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "…\Launchers\myapp.ps1" "%1"
脚本解析 URL、展开令牌后用 System.Diagnostics.Process 启动目标程序。
例如 myapp://open?id=123 + 模板 --id {query:id} → 目标程序实际收到:
"C:\Apps\MyApp.exe" --id 123
编辑对话框底部会实时预览这两种结果(注册表命令 + 目标程序实际收到的命令行),所见即所得。
本地没有 MSVC 工具链也能构建 —— CI 用的是 windows-latest runner,自带 VS 2022 / MSBuild / Windows SDK / .NET 8。
如果要在本地打开,需要:
- Windows 10 2004(10.0.19041)或更高版本,建议 Windows 11
- Visual Studio 2022 17.10+,工作负载:
.NET 桌面开发Windows 应用程序开发(提供 WinUI 3 / Windows App SDK 项目模板与 MSIX 打包工具)- 单个组件里勾选
Windows 11 SDK (10.0.22621.0)或更高
命令行构建(等价于 CI 做的事):
# 还原
msbuild src\UrlProtocolManager\UrlProtocolManager.csproj /t:Restore /p:Configuration=Release /p:Platform=x64
# 编译 + 打包 MSIX(输出到 bin\x64\Release\...\AppPackages 或 /p:AppxPackageDir 指定的目录)
msbuild src\UrlProtocolManager\UrlProtocolManager.csproj /p:Configuration=Release /p:Platform=x64 /p:GenerateAppxPackageOnBuild=true /p:AppxBundle=Never /p:AppxPackageSigningEnabled=false首次在本机安装 MSIX 前,需要先信任签名证书,做法与
install/Install.ps1一致。
工作流文件:.github/workflows/build-msix.yml
触发条件:
- 推送到
main/master - 推送
v*标签 - 对
main/master的 PR - 手动
Run workflow(可选参数:配置 / 平台 / 是否自包含 / 是否上传 PFX)
流程:
actions/checkout@v4actions/setup-dotnet@v4(.NET 8.0.x)microsoft/setup-msbuild@v2- 还原
msbuild /t:Restore - 编译 + 打包
/p:GenerateAppxPackageOnBuild=true /p:AppxBundle=Never /p:AppxPackageSigningEnabled=false - 定位生成的
.msix - 用
New-SelfSignedCertificate现场创建代码签名证书(Subject 与清单里的Publisher一致) - 用
signtool sign /fd SHA256签名并signtool verify - 与
install/Install.ps1、.cer一起打包上传
产物:
| 产物名 | 内容 |
|---|---|
UrlProtocolManager-x64-Release |
直接可用:.msix + Install.ps1 + UrlProtocolManager.cer |
UrlProtocolManager-x64-Release-raw |
MSIX 原始输出目录(排错用) |
UrlProtocolManager-signing-pfx |
仅当手动勾选 upload_pfx=true 时上传 |
默认的自签名证书每次构建都会重新生成,只能用于旁加载。若想用固定证书:
-
把
.pfx用 Base64 编码后存成仓库 Secret,例如PFX_BASE64。 -
再建一个 Secret
PFX_PASSWORD保存证书密码(工作流已经会读它)。 -
把「Create self-signed signing certificate」这一步替换成:
- name: Import signing certificate shell: pwsh run: | New-Item -ItemType Directory -Force -Path artifacts/cert | Out-Null [IO.File]::WriteAllBytes("artifacts/cert/UrlProtocolManager.pfx", [Convert]::FromBase64String("${{ secrets.PFX_BASE64 }}")) Export-Certificate -Cert (Import-PfxCertificate -FilePath artifacts/cert/UrlProtocolManager.pfx ` -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString "${{ secrets.PFX_PASSWORD }}" -AsPlainText -Force)) ` -FilePath artifacts/cert/UrlProtocolManager.cer | Out-Null
-
同时把
Package.appxmanifest里的Publisher改成与你证书Subject完全一致的字符串(例如CN=Your Company Name)。
| 令牌 | 含义 | 示例(myapp://open/item/42?id=123&from=web#top) |
需要模式 B |
|---|---|---|---|
%1 |
完整 URL(Windows 原生占位符) | myapp://open/item/42?id=123&from=web#top |
否 |
{url} |
同 %1 |
同上 | 否 |
{scheme} |
协议名 | myapp |
是 |
{host} |
主机名部分 | open |
是 |
{path} |
路径部分(去掉首斜杠) | item/42 |
是 |
{query} |
完整查询串 | id=123&from=web |
是 |
{query:<key>} |
某个查询参数的值 | {query:id} → 123 |
是 |
{fragment} |
片段 | top |
是 |
常用写法:
| 想达到的效果 | 模板 |
|---|---|
| 把完整 URL 交给程序(默认) | "%1" |
| 只传 id | --id {query:id} |
| 只传查询串 | --query "{query}" |
| 传子路径 | open "{path}" |
| 同时传多个 | --url "{url}" --id {query:id} --from {query:from} |
| 内容 | 路径 |
|---|---|
| 协议定义(JSON) | %LOCALAPPDATA%\UrlProtocolManager\protocols.json |
| PowerShell 启动脚本 | %LOCALAPPDATA%\UrlProtocolManager\Launchers\<scheme>.ps1 |
.reg 导出 |
%LOCALAPPDATA%\UrlProtocolManager\Exports\<scheme>.reg |
| 注册表 | HKEY_CURRENT_USER\Software\Classes\<scheme> |
「关于」页有按钮可以直接打开数据目录。
Q:点「测试」没反应,或者弹出「你要如何打开这个链接?」 说明协议没有真正写进注册表。先在列表里确认状态徽章是绿色的「已注册」; 如果不是,点「编辑 → 保存」或「全部重新注册」。再用管理员以外的普通 PowerShell 检查:
Get-ItemProperty 'Registry::HKEY_CURRENT_USER\Software\Classes\myapp\shell\open\command'Q:注册表里查不到,但应用显示「已注册」
几乎一定是 MSIX 虚拟化没关掉。请确认三件事都在 Package.appxmanifest 里:
desktop6:RegistryWriteVirtualization=disabled、rescap:Capability Name="unvirtualizedResources",
以及系统版本 ≥ Windows 10 2004(10.0.19041)。修改清单后需要重新打包并安装才生效。
Q:安装时报错 0x800B0109(证书不受信任)
先导入证书再装:
Import-Certificate .\UrlProtocolManager.cer -CertStoreLocation Cert:\CurrentUser\TrustedPeople
Add-AppxPackage .\UrlProtocolManager_1.0.0.0_x64.msixQ:安装时提示缺少 WindowsAppSDK 依赖
默认构建是自包含的(MSIX 里带运行时,体积约 100-150 MB)。
如果你手动选了 self_contained=false,目标机器就必须已安装 Windows App SDK 1.7 运行时,可改用自包含构建。
Q:CI 报 signtool.exe not found
windows-latest 镜像自带 Windows SDK。若镜像变更导致找不到,可在 Sign MSIX 步骤前加一步
choco install windows-sdk-10.1 或改用 MakeAppx/SignTool 的绝对路径。
Q:CI 打包这一步找不到 .msix
把 SelfContained 改成 false 试一次(在 Run workflow 里选),并看日志里
Build and package MSIX 步骤是否有 APPX0104 之类的错误。
Q:卸载应用后协议还在 这是关闭虚拟化的预期行为(注册表项不再被系统自动清理)。重新安装后删除,或者手动执行:
Remove-Item -Path 'Registry::HKEY_CURRENT_USER\Software\Classes\myapp' -RecurseQ:想给所有用户注册(写 HKLM)
需要管理员权限,且 MSIX 中写入 HKLM 受限。建议的做法是:在应用内把协议导出成 .reg,
再用登录脚本 / 组策略把它导入到需要的机器上。
| 项 | 版本 |
|---|---|
| 语言 / 目标框架 | C# 12 / net8.0-windows10.0.22621.0 |
| Microsoft.WindowsAppSDK | 1.7.250606001 |
| CommunityToolkit.Mvvm | 8.4.2 |
| CommunityToolkit.WinUI.Converters | 8.2.251219 |
| CommunityToolkit.WinUI.Controls.SettingsControls | 8.2.251219 |
| 打包 | 单项目 MSIX(<WindowsPackageType>MSIX</WindowsPackageType> + <EnableMsixTooling>),自包含 |
| CI | GitHub Actions,windows-latest |