系列文章:
【Windows 11 - 自定義右鍵選單(IExplorerCommand 程式實作)】上一篇文章中有提到:它是一種讓傳統 Win32 應用能夠享有部分 UWP 功能的包裝方法,為縮小傳統 Win 32 應用程式與這些新 Windows API 和功能之間的差距,因而引入 Sparse Package 技術。
Sparse Package 技術是 MSIX 打包技術的一種延伸。MSIX 是一種新的Windows 應用程式套件格式,可為所有 Windows 應用程式提供新式封裝體驗。 MSIX 套件格式除了支援對 Win32、WPF 和 Windows Forms 應用程式的新式封裝和部署功能,也保留了現有應用程式套件和/或安裝檔案的功能。
<Identity> 屬性用於指定應用程序的基本資訊,這些基本資訊包含以下:
在 <com:ComServer> 內部的 <com:SurrogateServer> 定義了一個代理伺服器,用於管理 COM 對象。在這個例子中,DisplayName 屬性為這個 COM 伺服器命名。
接著,我們將使用在上一篇文章中安裝的 Windows 11 SDK 內包含的工具「Makeappx」來進行打包。makeappx.exe 是一個命令行工具,專門用於創建 MSIX 或 APPX 套件。透過使用這個工具,我們可以將 AppxManifest.xml 包裝成一個 MSIX 套件。打包的命令如下:
為了簽署 MSIX 套件,我們通常需要一個有效的憑證。然而,對於開發和測試階段,我們可以使用「自我簽署憑證」來完成這一過程。自簽名憑證可以由開發者自己生成,雖然它不被操作系統預設為信任的,但足以用於開發和測試目的。
最後我們需要匯出該憑證檔案,請使用「Export-PfxCertificate」。使用時必須附帶參數「-Password」使用密碼,或使用「-ProtectTo」添加使用者或群組。以下為使用密碼的命令:
我們可以使用以下命令註冊 DLL :
Sparse Package 技術是 MSIX 打包技術的一種延伸。MSIX 是一種新的Windows 應用程式套件格式,可為所有 Windows 應用程式提供新式封裝體驗。 MSIX 套件格式除了支援對 Win32、WPF 和 Windows Forms 應用程式的新式封裝和部署功能,也保留了現有應用程式套件和/或安裝檔案的功能。
更多關於MSIX可以參考微軟的官方文件:
什麼是 MSIX?
在準備 AppxManifest.xml 文件的過程中,有幾個重要的屬性需要注意和設定,接下來讓我們依序解說:
準備AppxManifest.xml
進行 Sparse Package 打包的第一步,就是需要準備一個關鍵文件:「AppxManifest.xml」。這個文件是整個 MSIX 打包過程的核心,它描述了打包的應用的元數據資訊。它是一個 XML 文件,其中包含系統部署、顯示及更新 MSIX 應用程式所需的資訊。 此資訊包括套件識別資料、套件相依性、必要功能、視覺元素和擴充點。以下為自定義右鍵選單的 AppxManifest.xml 範例:<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
IgnorableNamespaces="uap uap2 uap3 rescap desktop desktop4 desktop5 uap10 com">
<Identity Name="Win11ContextMenuDemo" ProcessorArchitecture="neutral" Publisher="CN=MomoChenIsMe, C=TW" Version="1.0.0.0" />
<Properties>
<DisplayName>Win11ContextMenuDemo</DisplayName>
<PublisherDisplayName>Win11ContextMenuDemo</PublisherDisplayName>
<Logo>Assets\Logo.png</Logo>
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.22000.0" MaxVersionTested="10.0.22621.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources"/>
</Capabilities>
<Applications>
<Application Id="Win11ContextMenuDemo" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
<uap:VisualElements AppListEntry="none" DisplayName="Win11ContextMenuDemo" Description="Win11ContextMenuDemo" BackgroundColor="transparent" Square150x150Logo="Assets\Logo.png" Square44x44Logo="Assets\Logo.png">
</uap:VisualElements>
<Extensions>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="MainMenuWithWin11ContextMenuDemo" Clsid="15589FA6-768B-4826-97B8-D12DE265B3BB" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Win11ContextMenuDemo Shell Extension">
<com:Class Id="15589FA6-768B-4826-97B8-D12DE265B3BB" Path="Win11ContextMenuDemo.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
</Package>
應用程式基本資訊
<Identity Name="Win11ContextMenuDemo" ProcessorArchitecture="neutral" Publisher="CN=MomoChenIsMe, C=TW" Version="1.0.0.0" />
- Name:應用程式的名稱。
- ProcessorArchitecture:處理器架構。在這裡,設置為 "neutral" 表示應用程式是和處理器無關的。
- Publisher:發行者身份。簽署 MSIX 時會用到。
- Version:應用程式的版本號。
應用程式基本屬性
<Properties>
<DisplayName>Win11ContextMenuDemo</DisplayName>
<PublisherDisplayName>Win11ContextMenuDemo</PublisherDisplayName>
<Logo>Assets\Logo.png</Logo>
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
</Properties>
文件中的 <Properties> 部分是用來定義應用程式的一些基本屬性和顯示設定,包含以下屬性:
- DisplayName:應用程式在 Windows 操作系統中顯示的名稱。
- PublisherDisplayName:發行者的顯示名稱。
- Logo:應用程式的Icon或Logo。
- uap10:AllowExternalContent:這表示應用可以引用和使用不在原始 MSIX 套件內的文件和資源。💡 這裡需要設定為「true」,這樣我們可以從外部位置來封裝。詳細的資訊可以【點我前往】參考。
文件資源管理器上下文選單擴展 (windows.fileExplorerContextMenus)
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="MainMenuWithWin11ContextMenuDemo" Clsid="15589FA6-768B-4826-97B8-D12DE265B3BB" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
在 <desktop4:FileExplorerContextMenus> 內,我們定義了對特定項目類型的處理方式:
- <desktop5:ItemType Type="Directory\Background">:表示這個擴展會出現在目錄的背景(空白區域)的右鍵選單中。
- <desktop5:ItemType Type="Directory">:表示這個擴展會出現在資料夾本身。
- <desktop5:ItemType Type="*">:表示這個擴展會出現在所有檔案格式。
💡 這裡的 Clsid 就是我們上一篇為主選單所設定的延伸模組識別 UUID。
COM 伺服器擴展 (windows.comServer)
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Win11ContextMenuDemo Shell Extension">
<com:Class Id="15589FA6-768B-4826-97B8-D12DE265B3BB" Path="Win11ContextMenuDemo.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
而 <com:Class> 標籤定義了一個 COM 類:
- Id:屬性指定了 COM 類的唯一識別碼和上方的 Clsid 是同一組 Id。
- Path:指明了實現這個類的 DLL 檔案的路徑。
- ThreadingModel:則指定了該類的線程模型。💡 可以設定「STA」或「MTA」。
打包 Sparse package 套件
在完成 AppxManifest.xml 文件的準備和 DLL 文件的編譯後,我們的下一步是將這些文件進行打包。首先,將準備好的 AppxManifest.xml 文件和編譯好的 DLL 文件放置在同一個資料夾中。
這樣做的好處就是等等註冊 DLL 時,系統可以直接抓到同層的 MSIX 檔案進行安裝。
💡 上一篇文章中,我們註冊 DLL 時會執行 InstallContextMenu 方法,該方法就會在執行階段使用當前的路徑,找到MSIX進行 Sparse Package 的安裝。
makeappx pack /d <來源資料夾路徑> /p <輸出套件文件路徑>
其中「來源資料夾路徑」是放置 AppxManifest.xml 的資料夾路徑,而「輸出套件文件路徑」則是你希望生成的套件文件的儲存位置和文件名。
簽署 Sparse Package
在成功產生 MSIX 套件之後,重要的一步是對套件進行簽名。這是因為 Windows 系統出於安全考慮,要求所有的 MSIX 套件必須被正確簽名後才能安裝。所以,在這個階段,儘管我們已經有了 MSIX 套件,但我們還不能直接進行安裝。為了簽署 MSIX 套件,我們通常需要一個有效的憑證。然而,對於開發和測試階段,我們可以使用「自我簽署憑證」來完成這一過程。自簽名憑證可以由開發者自己生成,雖然它不被操作系統預設為信任的,但足以用於開發和測試目的。
另外在 DLL 內安裝 Sparse Package 的程式碼中,我們也需要添加並執行一個 AllowUnsigned 的方法,這樣就可以在開發模式下安裝這個 MSIX 套件了。
// 註冊 Sparse Package。
// Registers the Sparse Package.
HRESULT RegisterSparsePackage()
{
const wstring contextMenuDirectoryPath = Win11ContextMenuDemo::Path::GetContextMenuDirectoryPath();
const wstring sparsePackageFullPath = contextMenuDirectoryPath + L"\\" + CONTEXTMENUNAME + L".msix";
Uri externalLocationUri(contextMenuDirectoryPath);
Uri packageUri(sparsePackageFullPath);
AddPackageOptions options;
options.ExternalLocationUri(externalLocationUri);
// 允許使用 AllowUnsigned
options.AllowUnsigned(true);
PackageManager packageManager;
auto deploymentOperation = packageManager.AddPackageByUriAsync(packageUri, options);
auto deployResult = deploymentOperation.get();
if (!SUCCEEDED(deployResult.ExtendedErrorCode()))
{
return deployResult.ExtendedErrorCode();
}
return S_OK;
}
接著我們使用 PowerShell 來建立自我簽署憑證,我們可以使用「New-SelfSignedCertificate」進行簽署。命令為以下:
New-SelfSignedCertificate -Type Custom -Subject "CN=MomoChenIsMe, C=TW" -KeyUsage DigitalSignature -FriendlyName "MomoChenIsMe" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")
- Subject:參數值必須和 AppxManifest.xml 檔案內 <Identity> 屬性中的 Publisher 一致。
- KeyUsage :定義憑證的用途。 自我簽署憑證應設定為 DigitalSignature。
- FriendlyName:方便識別憑證的名稱。
- CertStoreLocation :儲存憑證的路徑。預設為:「Cert:\CurrentUser\My」。
- TextExtension :允許添加一些非標準的或特定的擴展到憑證中。
關於 New-SelfSignedCertificate 的詳細資訊可以參考微軟官方文件:
如果要檢視剛才在「Cert:\CurrentUser\My」建立的憑證可以使用以下命令:
Set-Location Cert:\CurrentUser\My
Get-ChildItem
如果需要刪除憑證則可以使用以下命令:
Remove-Item Cert:\CurrentUser\My\<憑證的 Thumbprint>
$password = ConvertTo-SecureString -String <使用密碼> -Force -AsPlainText
Export-PfxCertificate -cert "Cert:\CurrentUser\My\<憑證的 Thumbprint>" -FilePath <輸出憑證文件路徑>.pfx -Password $password
生成完自我簽署證書(pxf)並且匯出後,我們可以使用「SignTool」工具來對 MSIX 套件進行簽名。命令如下:
signtool sign /a /fd SHA256 /f <憑證文件路徑> /p <憑證密碼> <MSIX套件路徑>
註冊 DLL
完成以上所有步驟後,我們就可以在開發和測試環境中安裝和運行我們的 MSIX 套件。當我們註冊 DLL 時會執行「InstallContextMenu」方法,該方法就會在執行階段使用當前的路徑找到打包簽署好的 MSIX 進行安裝。一旦 Sparse Package 安裝完成,我們的擴展功能在啟動時便會自動生效。這意味著,當條件滿足時(例如在特定場景下右鍵點擊),我們的 DLL 就會被調用,進而產生自定義的右鍵選單。
regsvr32.exe <DLL 文件路徑>
若要移除先前創建的自定義右鍵選單,我們可以透過註銷先前註冊的 DLL 即可移除。註銷的過程中,DLL 程式碼會執行「UnInstallContextMenu」方法來執行必要的清理和解除 Sparse Package 安裝。註銷的命令為以下:
regsvr32.exe /u <DLL 文件路徑>
總結
在這一系列文章中,展示了在 Windows 11 中自定義右鍵選單的整個過程。由於相關的中文資源相對較少,我在研究的過程都是靠著 Notepad++ 的 NppShell 開源專案慢慢去理解。這些經驗雖然可能在某些觀念上不盡完全正確,特別是在 Sparse Package 實作部分,但我仍希望能夠通過分享實戰經驗來幫助有需要建置 Windows 11 自定義右鍵選單的人!
參考資料
- https://learn.microsoft.com/zh-tw/windows/msix/overview
- https://learn.microsoft.com/zh-tw/windows/apps/desktop/modernize/modernize-packaged-apps
- https://learn.microsoft.com/zh-tw/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps
- https://learn.microsoft.com/zh-tw/windows/msix/package/create-certificate-package-signing
- https://learn.microsoft.com/en-us/windows/msix/package/unsigned-package
0 Comments
張貼留言