Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

URL 协议管理器(WinUI 3 / C#)

一个用 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 原生控件(NavigationViewInfoBarContentDialogItemsRepeaterToggleSwitch)与 Windows Community ToolkitSettingsCard(卡片式布局)/ BoolToVisibilityConverter 以及 CommunityToolkit.Mvvm(MVVM Toolkit),没有自造控件。


快速开始(只想拿安装包)

  1. 把本仓库推到 GitHub(默认分支 mainmaster)。

  2. ActionsBuild MSIX → 等待完成。

  3. 下载名为 UrlProtocolManager-x64-Release 的构建产物,解压后得到:

    Install.ps1
    UrlProtocolManager.cer
    UrlProtocolManager_1.0.0.0_x64.msix
    
  4. 在同一目录下打开 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\commandHKCRHKLM\Software\ClassesHKCU\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 的注册表虚拟化

这是本项目唯一需要特别注意的地方,也是很多同类工具做不出来的原因。

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 Actions 构建

工作流文件:.github/workflows/build-msix.yml

触发条件:

  • 推送到 main / master
  • 推送 v* 标签
  • main / master 的 PR
  • 手动 Run workflow(可选参数:配置 / 平台 / 是否自包含 / 是否上传 PFX)

流程:

  1. actions/checkout@v4
  2. actions/setup-dotnet@v4(.NET 8.0.x)
  3. microsoft/setup-msbuild@v2
  4. 还原 msbuild /t:Restore
  5. 编译 + 打包 /p:GenerateAppxPackageOnBuild=true /p:AppxBundle=Never /p:AppxPackageSigningEnabled=false
  6. 定位生成的 .msix
  7. New-SelfSignedCertificate 现场创建代码签名证书(Subject 与清单里的 Publisher 一致)
  8. signtool sign /fd SHA256 签名并 signtool verify
  9. install/Install.ps1.cer 一起打包上传

产物:

产物名 内容
UrlProtocolManager-x64-Release 直接可用.msix + Install.ps1 + UrlProtocolManager.cer
UrlProtocolManager-x64-Release-raw MSIX 原始输出目录(排错用)
UrlProtocolManager-signing-pfx 仅当手动勾选 upload_pfx=true 时上传

使用自己的签名证书

默认的自签名证书每次构建都会重新生成,只能用于旁加载。若想用固定证书:

  1. .pfx 用 Base64 编码后存成仓库 Secret,例如 PFX_BASE64

  2. 再建一个 Secret PFX_PASSWORD 保存证书密码(工作流已经会读它)。

  3. 把「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
  4. 同时把 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=disabledrescap: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.msix

Q:安装时提示缺少 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 打包这一步找不到 .msixSelfContained 改成 false 试一次(在 Run workflow 里选),并看日志里 Build and package MSIX 步骤是否有 APPX0104 之类的错误。

Q:卸载应用后协议还在 这是关闭虚拟化的预期行为(注册表项不再被系统自动清理)。重新安装后删除,或者手动执行:

Remove-Item -Path 'Registry::HKEY_CURRENT_USER\Software\Classes\myapp' -Recurse

Q:想给所有用户注册(写 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

About

WinUI 3 自定义 URL 协议管理工具:注册系统级 myapp:// 协议、参数转发、MSIX 打包与 CI 构建

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages