mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
Introduce shared sparse package identity for PowerToys (#42352)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This pull request adds support for building, installing, and managing a shared sparse MSIX package to grant package identity to select Win32 components in PowerToys. It introduces a new `PackageIdentity` project, updates the installer to handle the new MSIX package during install/uninstall, and provides developer documentation for working with the sparse package. Additionally, new dependencies and signing rules are included to support these changes. **Sparse Package Identity Support** * Added new `PackageIdentity` project to the solution for building the sparse MSIX package, and included it in solution/project build configurations (`PowerToys.sln`). [[1]](diffhunk://#diff-ca837ce490070b91656ffffe31cbad8865ba9174e0f020231f77baf35ff3f811R29) [[2]](diffhunk://#diff-ca837ce490070b91656ffffe31cbad8865ba9174e0f020231f77baf35ff3f811R54-R55) [[3]](diffhunk://#diff-ca837ce490070b91656ffffe31cbad8865ba9174e0f020231f77baf35ff3f811R873-R880) * Added developer documentation (`sparse-package.md`) and updated documentation indexes to describe how to build, register, and consume the sparse MSIX package. [[1]](diffhunk://#diff-b4e39fb55a49c6de336d5847d75a55dd1d14840578da0ed9130f0130b61b34aaR1-R87) [[2]](diffhunk://#diff-d0f204e503506a26ef2aa3605a8d64ac353393526fb5dcf48d4287c821f3edbcR31) [[3]](diffhunk://#diff-430296c8d28f70d8a0164b44d7dfc30ffb1fb32466dad181947f35885b7f28d1R13) **Installer Enhancements** * Implemented new custom actions in the installer to install and uninstall the `PowerToysSparse.msix` package, supporting both per-user and machine-level scenarios (`CustomAction.cpp`, `CustomAction.def`, `Product.wxs`). [[1]](diffhunk://#diff-a7680a20bf0315cff463a95588a100c99d2afc53030f6e947f1f1dcaca5eefd7R597-R806) [[2]](diffhunk://#diff-79daec0ccfcea63a2f3acb7d811b8b508529921123c754111bbccbea98b2bd74R36-R37) [[3]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR115) [[4]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR127) [[5]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR149) [[6]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR205-R210) **Build and Dependency Updates** * Added new NuGet package dependencies for Windows App SDK AI and Runtime to support MSIX and sparse package features (`Directory.Packages.props`). * Updated signing pipeline to include the new `PowerToysSparse.msix` artifact (`.pipelines/ESRPSigning_core.json`). <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
This commit is contained in:
7
.github/actions/spell-check/expect.txt
vendored
7
.github/actions/spell-check/expect.txt
vendored
@@ -193,6 +193,7 @@ changecursor
|
|||||||
CHILDACTIVATE
|
CHILDACTIVATE
|
||||||
CHILDWINDOW
|
CHILDWINDOW
|
||||||
CHOOSEFONT
|
CHOOSEFONT
|
||||||
|
CIBUILD
|
||||||
cidl
|
cidl
|
||||||
CIELCh
|
CIELCh
|
||||||
cim
|
cim
|
||||||
@@ -383,6 +384,7 @@ DISPLAYFREQUENCY
|
|||||||
displayname
|
displayname
|
||||||
DISPLAYORIENTATION
|
DISPLAYORIENTATION
|
||||||
divyan
|
divyan
|
||||||
|
djwsxzxb
|
||||||
Dlg
|
Dlg
|
||||||
DLGFRAME
|
DLGFRAME
|
||||||
DLGMODALFRAME
|
DLGMODALFRAME
|
||||||
@@ -443,6 +445,7 @@ EDITSHORTCUTS
|
|||||||
EDITTEXT
|
EDITTEXT
|
||||||
EFile
|
EFile
|
||||||
ekus
|
ekus
|
||||||
|
eku
|
||||||
emojis
|
emojis
|
||||||
ENABLEDELAYEDEXPANSION
|
ENABLEDELAYEDEXPANSION
|
||||||
ENABLEDPOPUP
|
ENABLEDPOPUP
|
||||||
@@ -816,6 +819,7 @@ keyvault
|
|||||||
KILLFOCUS
|
KILLFOCUS
|
||||||
killrunner
|
killrunner
|
||||||
kmph
|
kmph
|
||||||
|
ksa
|
||||||
kvp
|
kvp
|
||||||
Kybd
|
Kybd
|
||||||
LARGEICON
|
LARGEICON
|
||||||
@@ -922,6 +926,7 @@ LWA
|
|||||||
lwin
|
lwin
|
||||||
LZero
|
LZero
|
||||||
MAGTRANSFORM
|
MAGTRANSFORM
|
||||||
|
makeappx
|
||||||
MAKEINTRESOURCE
|
MAKEINTRESOURCE
|
||||||
MAKEINTRESOURCEA
|
MAKEINTRESOURCEA
|
||||||
MAKEINTRESOURCEW
|
MAKEINTRESOURCEW
|
||||||
@@ -1254,6 +1259,7 @@ pinvoke
|
|||||||
pipename
|
pipename
|
||||||
PKBDLLHOOKSTRUCT
|
PKBDLLHOOKSTRUCT
|
||||||
pkgfamily
|
pkgfamily
|
||||||
|
PKI
|
||||||
plib
|
plib
|
||||||
ploc
|
ploc
|
||||||
ploca
|
ploca
|
||||||
@@ -1695,6 +1701,7 @@ syskeydown
|
|||||||
SYSKEYUP
|
SYSKEYUP
|
||||||
SYSLIB
|
SYSLIB
|
||||||
SYSMENU
|
SYSMENU
|
||||||
|
systemai
|
||||||
SYSTEMAPPS
|
SYSTEMAPPS
|
||||||
SYSTEMMODAL
|
SYSTEMMODAL
|
||||||
SYSTEMTIME
|
SYSTEMTIME
|
||||||
|
|||||||
@@ -235,7 +235,9 @@
|
|||||||
"*Microsoft.CmdPal.UI_*.msix",
|
"*Microsoft.CmdPal.UI_*.msix",
|
||||||
|
|
||||||
"PowerToys.DSC.dll",
|
"PowerToys.DSC.dll",
|
||||||
"PowerToys.DSC.exe"
|
"PowerToys.DSC.exe",
|
||||||
|
|
||||||
|
"PowerToysSparse.msix"
|
||||||
],
|
],
|
||||||
"SigningInfo": {
|
"SigningInfo": {
|
||||||
"Operations": [
|
"Operations": [
|
||||||
|
|||||||
@@ -61,6 +61,8 @@
|
|||||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
|
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
|
||||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||||
|
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
|
||||||
|
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />
|
||||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||||
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner
|
|||||||
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} = {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}
|
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} = {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}
|
||||||
{D940E07F-532C-4FF3-883F-790DA014F19A} = {D940E07F-532C-4FF3-883F-790DA014F19A}
|
{D940E07F-532C-4FF3-883F-790DA014F19A} = {D940E07F-532C-4FF3-883F-790DA014F19A}
|
||||||
{DA425894-6E13-404F-8DCB-78584EC0557A} = {DA425894-6E13-404F-8DCB-78584EC0557A}
|
{DA425894-6E13-404F-8DCB-78584EC0557A} = {DA425894-6E13-404F-8DCB-78584EC0557A}
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D} = {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}
|
||||||
{E364F67B-BB12-4E91-B639-355866EBCD8B} = {E364F67B-BB12-4E91-B639-355866EBCD8B}
|
{E364F67B-BB12-4E91-B639-355866EBCD8B} = {E364F67B-BB12-4E91-B639-355866EBCD8B}
|
||||||
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}
|
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
@@ -50,6 +51,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{1AFB64
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common.Lib.UnitTests", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common.Lib.UnitTests", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PackageIdentity", "src\PackageIdentity\PackageIdentity.vcxproj", "{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}"
|
||||||
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename", "{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename", "{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}"
|
||||||
@@ -867,6 +870,14 @@ Global
|
|||||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|ARM64.Build.0 = Release|ARM64
|
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.ActiveCfg = Release|x64
|
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.ActiveCfg = Release|x64
|
||||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.Build.0 = Release|x64
|
{1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.Build.0 = Release|x64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.Build.0 = Release|x64
|
||||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.Build.0 = Debug|ARM64
|
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64
|
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
|||||||
@@ -594,6 +594,216 @@ LExit:
|
|||||||
return WcaFinalize(er);
|
return WcaFinalize(er);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
|
||||||
|
{
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
UINT er = ERROR_SUCCESS;
|
||||||
|
LPWSTR customActionData = nullptr;
|
||||||
|
std::wstring installFolderPath;
|
||||||
|
std::wstring installScope;
|
||||||
|
std::wstring msixPath;
|
||||||
|
std::wstring data;
|
||||||
|
size_t delimiterPos;
|
||||||
|
bool isMachineLevel = false;
|
||||||
|
|
||||||
|
hr = WcaInitialize(hInstall, "InstallPackageIdentityMSIXCA");
|
||||||
|
ExitOnFailure(hr, "Failed to initialize");
|
||||||
|
|
||||||
|
hr = WcaGetProperty(L"CustomActionData", &customActionData);
|
||||||
|
ExitOnFailure(hr, "Failed to get CustomActionData property");
|
||||||
|
|
||||||
|
// Parse CustomActionData: "[INSTALLFOLDER];[InstallScope]"
|
||||||
|
data = customActionData;
|
||||||
|
delimiterPos = data.find(L';');
|
||||||
|
installFolderPath = data.substr(0, delimiterPos);
|
||||||
|
installScope = data.substr(delimiterPos + 1);
|
||||||
|
|
||||||
|
// Check if this is a machine-level installation
|
||||||
|
if (installScope == L"perMachine")
|
||||||
|
{
|
||||||
|
isMachineLevel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info(L"Installing PackageIdentity MSIX - perUser: {}", !isMachineLevel);
|
||||||
|
|
||||||
|
// Construct path to PackageIdentity MSIX
|
||||||
|
msixPath = installFolderPath;
|
||||||
|
msixPath += L"PowerToysSparse.msix";
|
||||||
|
|
||||||
|
if (std::filesystem::exists(msixPath))
|
||||||
|
{
|
||||||
|
using namespace winrt::Windows::Management::Deployment;
|
||||||
|
using namespace winrt::Windows::Foundation;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder)
|
||||||
|
Uri externalUri{ externalLocation }; // External location URI for sparse package content
|
||||||
|
Uri packageUri{ msixPath }; // The MSIX file URI
|
||||||
|
|
||||||
|
PackageManager packageManager;
|
||||||
|
|
||||||
|
if (isMachineLevel)
|
||||||
|
{
|
||||||
|
// Machine-level installation
|
||||||
|
|
||||||
|
StagePackageOptions stageOptions;
|
||||||
|
stageOptions.ExternalLocationUri(externalUri);
|
||||||
|
|
||||||
|
auto stageResult = packageManager.StagePackageByUriAsync(packageUri, stageOptions).get();
|
||||||
|
|
||||||
|
uint32_t stageErrorCode = static_cast<uint32_t>(stageResult.ExtendedErrorCode());
|
||||||
|
if (stageErrorCode == 0)
|
||||||
|
{
|
||||||
|
std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto provisionResult = packageManager.ProvisionPackageForAllUsersAsync(packageFamilyName).get();
|
||||||
|
uint32_t provisionErrorCode = static_cast<uint32_t>(provisionResult.ExtendedErrorCode());
|
||||||
|
|
||||||
|
if (provisionErrorCode != 0)
|
||||||
|
{
|
||||||
|
Logger::error(L"Machine-level provisioning failed: 0x{:08X}", provisionErrorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error& ex)
|
||||||
|
{
|
||||||
|
Logger::error(L"Provisioning exception: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Package staging failed: 0x{:08X}", stageErrorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddPackageOptions addOptions;
|
||||||
|
addOptions.ExternalLocationUri(externalUri);
|
||||||
|
|
||||||
|
auto addResult = packageManager.AddPackageByUriAsync(packageUri, addOptions).get();
|
||||||
|
|
||||||
|
if (!addResult.IsRegistered())
|
||||||
|
{
|
||||||
|
uint32_t errorCode = static_cast<uint32_t>(addResult.ExtendedErrorCode());
|
||||||
|
Logger::error(L"Per-user installation failed: 0x{:08X}", errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex)
|
||||||
|
{
|
||||||
|
Logger::error(L"PackageIdentity MSIX installation failed - Exception: {}",
|
||||||
|
winrt::to_hstring(ex.what()).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"PackageIdentity MSIX not found: " + msixPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
LExit:
|
||||||
|
ReleaseStr(customActionData);
|
||||||
|
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
|
||||||
|
return WcaFinalize(er);
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT __stdcall UninstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
|
||||||
|
{
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
UINT er = ERROR_SUCCESS;
|
||||||
|
using namespace winrt::Windows::Management::Deployment;
|
||||||
|
using namespace winrt::Windows::Foundation;
|
||||||
|
|
||||||
|
LPWSTR installScope = nullptr;
|
||||||
|
bool isMachineLevel = false;
|
||||||
|
|
||||||
|
PackageManager pm;
|
||||||
|
|
||||||
|
hr = WcaInitialize(hInstall, "UninstallPackageIdentityMSIXCA");
|
||||||
|
ExitOnFailure(hr, "Failed to initialize");
|
||||||
|
|
||||||
|
// Check if this was a machine-level installation
|
||||||
|
hr = WcaGetProperty(L"InstallScope", &installScope);
|
||||||
|
if (SUCCEEDED(hr) && installScope && wcscmp(installScope, L"perMachine") == 0)
|
||||||
|
{
|
||||||
|
isMachineLevel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info(L"Uninstalling PackageIdentity MSIX - perUser: {}", !isMachineLevel);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe";
|
||||||
|
|
||||||
|
if (isMachineLevel)
|
||||||
|
{
|
||||||
|
// Machine-level uninstallation: deprovision + remove for all users
|
||||||
|
|
||||||
|
// First deprovision the package
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto deprovisionResult = pm.DeprovisionPackageForAllUsersAsync(packageFamilyName).get();
|
||||||
|
if (deprovisionResult.IsRegistered())
|
||||||
|
{
|
||||||
|
Logger::warn(L"Machine-level deprovisioning completed with warnings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error& ex)
|
||||||
|
{
|
||||||
|
Logger::warn(L"Machine-level deprovisioning failed: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then remove packages for all users
|
||||||
|
auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main);
|
||||||
|
for (const auto& package : packages)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto machineResult = pm.RemovePackageAsync(package.Id().FullName(), RemovalOptions::RemoveForAllUsers).get();
|
||||||
|
if (machineResult.IsRegistered())
|
||||||
|
{
|
||||||
|
uint32_t errorCode = static_cast<uint32_t>(machineResult.ExtendedErrorCode());
|
||||||
|
Logger::error(L"Machine-level removal failed: 0x{:08X} - {}", errorCode, machineResult.ErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error& ex)
|
||||||
|
{
|
||||||
|
Logger::error(L"Machine-level removal exception: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Per-user uninstallation: standard removal
|
||||||
|
|
||||||
|
auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main);
|
||||||
|
for (const auto& package : packages)
|
||||||
|
{
|
||||||
|
auto userResult = pm.RemovePackageAsync(package.Id().FullName()).get();
|
||||||
|
if (userResult.IsRegistered())
|
||||||
|
{
|
||||||
|
uint32_t errorCode = static_cast<uint32_t>(userResult.ExtendedErrorCode());
|
||||||
|
Logger::error(L"Per-user removal failed: 0x{:08X} - {}", errorCode, userResult.ErrorText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& ex)
|
||||||
|
{
|
||||||
|
std::string errorMsg = "Failed to uninstall PackageIdentity MSIX: " + std::string(ex.what());
|
||||||
|
Logger::error(errorMsg);
|
||||||
|
// Don't fail the entire uninstallation if PackageIdentity fails
|
||||||
|
Logger::warn(L"Continuing uninstallation despite PackageIdentity MSIX error");
|
||||||
|
}
|
||||||
|
|
||||||
|
LExit:
|
||||||
|
ReleaseStr(installScope);
|
||||||
|
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
|
||||||
|
return WcaFinalize(er);
|
||||||
|
}
|
||||||
|
|
||||||
UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName)
|
UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName)
|
||||||
{
|
{
|
||||||
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
||||||
|
|||||||
@@ -33,3 +33,5 @@ EXPORTS
|
|||||||
CleanPowerRenameRuntimeRegistryCA
|
CleanPowerRenameRuntimeRegistryCA
|
||||||
CleanNewPlusRuntimeRegistryCA
|
CleanNewPlusRuntimeRegistryCA
|
||||||
SetBundleInstallLocationCA
|
SetBundleInstallLocationCA
|
||||||
|
InstallPackageIdentityMSIXCA
|
||||||
|
UninstallPackageIdentityMSIXCA
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
<Custom Action="SetUninstallCommandNotFoundParam" Before="UninstallCommandNotFound" />
|
<Custom Action="SetUninstallCommandNotFoundParam" Before="UninstallCommandNotFound" />
|
||||||
<Custom Action="SetUpgradeCommandNotFoundParam" Before="UpgradeCommandNotFound" />
|
<Custom Action="SetUpgradeCommandNotFoundParam" Before="UpgradeCommandNotFound" />
|
||||||
<Custom Action="SetApplyModulesRegistryChangeSetsParam" Before="ApplyModulesRegistryChangeSets" />
|
<Custom Action="SetApplyModulesRegistryChangeSetsParam" Before="ApplyModulesRegistryChangeSets" />
|
||||||
|
<Custom Action="SetInstallPackageIdentityMSIXParam" Before="InstallPackageIdentityMSIX" />
|
||||||
|
|
||||||
<?if $(var.PerUser) = "true" ?>
|
<?if $(var.PerUser) = "true" ?>
|
||||||
<Custom Action="SetInstallDSCModuleParam" Before="InstallDSCModule" />
|
<Custom Action="SetInstallDSCModuleParam" Before="InstallDSCModule" />
|
||||||
@@ -123,6 +124,7 @@
|
|||||||
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed" />
|
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed" />
|
||||||
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
|
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
|
||||||
<Custom Action="InstallCmdPalPackage" After="InstallFiles" Condition="NOT Installed" />
|
<Custom Action="InstallCmdPalPackage" After="InstallFiles" Condition="NOT Installed" />
|
||||||
|
<Custom Action="InstallPackageIdentityMSIX" After="InstallFiles" Condition="NOT Installed" />
|
||||||
<Custom Action="override Wix4CloseApplications_$(sys.BUILDARCHSHORT)" Before="RemoveFiles" />
|
<Custom Action="override Wix4CloseApplications_$(sys.BUILDARCHSHORT)" Before="RemoveFiles" />
|
||||||
<Custom Action="RemovePowerToysSchTasks" After="RemoveFiles" />
|
<Custom Action="RemovePowerToysSchTasks" After="RemoveFiles" />
|
||||||
<!-- TODO: Use to activate embedded MSIX -->
|
<!-- TODO: Use to activate embedded MSIX -->
|
||||||
@@ -144,6 +146,7 @@
|
|||||||
<Custom Action="UnRegisterCmdPalPackage" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
<Custom Action="UnRegisterCmdPalPackage" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||||
<Custom Action="UninstallCommandNotFound" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
<Custom Action="UninstallCommandNotFound" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||||
<Custom Action="UpgradeCommandNotFound" After="InstallFiles" Condition="WIX_UPGRADE_DETECTED" />
|
<Custom Action="UpgradeCommandNotFound" After="InstallFiles" Condition="WIX_UPGRADE_DETECTED" />
|
||||||
|
<Custom Action="UninstallPackageIdentityMSIX" Before="RemoveFiles" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||||
<Custom Action="UninstallServicesTask" After="InstallFinalize" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
<Custom Action="UninstallServicesTask" After="InstallFinalize" Condition="Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")" />
|
||||||
<!-- TODO: Use to activate embedded MSIX -->
|
<!-- TODO: Use to activate embedded MSIX -->
|
||||||
<!--<Custom Action="UninstallEmbeddedMSIXTask" After="InstallFinalize">
|
<!--<Custom Action="UninstallEmbeddedMSIXTask" After="InstallFinalize">
|
||||||
@@ -199,6 +202,12 @@
|
|||||||
|
|
||||||
<CustomAction Id="UninstallEmbeddedMSIXTask" Return="ignore" Impersonate="yes" DllEntry="UninstallEmbeddedMSIXCA" BinaryRef="PTCustomActions" />
|
<CustomAction Id="UninstallEmbeddedMSIXTask" Return="ignore" Impersonate="yes" DllEntry="UninstallEmbeddedMSIXCA" BinaryRef="PTCustomActions" />
|
||||||
|
|
||||||
|
<CustomAction Id="SetInstallPackageIdentityMSIXParam" Property="InstallPackageIdentityMSIX" Value="[INSTALLFOLDER];[InstallScope]" />
|
||||||
|
|
||||||
|
<CustomAction Id="InstallPackageIdentityMSIX" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="InstallPackageIdentityMSIXCA" BinaryRef="PTCustomActions" />
|
||||||
|
|
||||||
|
<CustomAction Id="UninstallPackageIdentityMSIX" Return="ignore" Impersonate="yes" DllEntry="UninstallPackageIdentityMSIXCA" BinaryRef="PTCustomActions" />
|
||||||
|
|
||||||
<CustomAction Id="InstallDSCModule" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="InstallDSCModuleCA" BinaryRef="PTCustomActions" />
|
<CustomAction Id="InstallDSCModule" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="InstallDSCModuleCA" BinaryRef="PTCustomActions" />
|
||||||
|
|
||||||
<CustomAction Id="UninstallDSCModule" Return="ignore" Impersonate="yes" DllEntry="UninstallDSCModuleCA" BinaryRef="PTCustomActions" />
|
<CustomAction Id="UninstallDSCModule" Return="ignore" Impersonate="yes" DllEntry="UninstallDSCModuleCA" BinaryRef="PTCustomActions" />
|
||||||
|
|||||||
67
src/PackageIdentity/AppxManifest.xml
Normal file
67
src/PackageIdentity/AppxManifest.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Sparse package manifest (moved to PackageIdentity folder for cleaner organization).
|
||||||
|
Based on Windows AI Foundry WPF sparse sample with PowerOCR customizations. -->
|
||||||
|
<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:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||||
|
xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10"
|
||||||
|
IgnorableNamespaces="uap uap2 uap3 rescap desktop uap10 systemai">
|
||||||
|
<Identity
|
||||||
|
Name="Microsoft.PowerToys.SparseApp"
|
||||||
|
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||||
|
Version="0.0.1.0" />
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>PowerToys.SparseApp</DisplayName>
|
||||||
|
<PublisherDisplayName>PowerToys</PublisherDisplayName>
|
||||||
|
<Logo>Images\StoreLogo.png</Logo>
|
||||||
|
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<Resources>
|
||||||
|
<Resource Language="en-us" />
|
||||||
|
</Resources>
|
||||||
|
<Dependencies>
|
||||||
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19000.0" MaxVersionTested="10.0.26226.0" />
|
||||||
|
</Dependencies>
|
||||||
|
<Capabilities>
|
||||||
|
<rescap:Capability Name="runFullTrust" />
|
||||||
|
<systemai:Capability Name="systemAIModels"/>
|
||||||
|
<rescap:Capability Name="unvirtualizedResources"/>
|
||||||
|
</Capabilities>
|
||||||
|
|
||||||
|
<Applications>
|
||||||
|
<Application Id="PowerToys.OCR" Executable="PowerToys.PowerOCR.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="PowerToys.OCR"
|
||||||
|
Description="PowerToys OCR Module"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Images\Square44x44Logo.png">
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
<Application Id="PowerToys.SettingsUI" Executable="PowerToys.Settings.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="PowerToys.SettingsUI"
|
||||||
|
Description="PowerToys Settings UI"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Images\Square44x44Logo.png">
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="PowerToys.ImageResizer"
|
||||||
|
Description="PowerToys Image Resizer UI"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Images\Square44x44Logo.png">
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
</Package>
|
||||||
6
src/PackageIdentity/BuildSparsePackage.cmd
Normal file
6
src/PackageIdentity/BuildSparsePackage.cmd
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
REM Wrapper to invoke PowerToys sparse package build script.
|
||||||
|
REM Pass through all arguments (e.g. Platform=arm64 Configuration=Debug -Clean)
|
||||||
|
|
||||||
|
powershell -ExecutionPolicy Bypass -NoLogo -NoProfile -File "%~dp0\BuildSparsePackage.ps1" %*
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
422
src/PackageIdentity/BuildSparsePackage.ps1
Normal file
422
src/PackageIdentity/BuildSparsePackage.ps1
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[ValidateSet("arm64", "x64")]
|
||||||
|
[string]$Platform = "x64",
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[ValidateSet("Debug", "Release")]
|
||||||
|
[string]$Configuration = "Release",
|
||||||
|
|
||||||
|
[switch]$Clean,
|
||||||
|
[switch]$ForceCert,
|
||||||
|
[switch]$NoSign,
|
||||||
|
[switch]$CIBuild
|
||||||
|
)
|
||||||
|
|
||||||
|
# PowerToys sparse packaging helper.
|
||||||
|
# Generates a sparse MSIX (no payload) that grants package identity to selected Win32 components.
|
||||||
|
# Multiple applications (PowerOCR, Settings UI, etc.) can share this single sparse identity.
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$isCIBuild = $false
|
||||||
|
if ($CIBuild.IsPresent) {
|
||||||
|
$isCIBuild = $true
|
||||||
|
} elseif ($env:CIBuild) {
|
||||||
|
$isCIBuild = $env:CIBuild -ieq 'true'
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentPublisherHint = $script:Config.CertSubject
|
||||||
|
|
||||||
|
# Configuration constants - centralized management
|
||||||
|
$script:Config = @{
|
||||||
|
IdentityName = "Microsoft.PowerToys.SparseApp"
|
||||||
|
SparseMsixName = "PowerToysSparse.msix"
|
||||||
|
CertPrefix = "PowerToysSparse"
|
||||||
|
CertSubject = 'CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US'
|
||||||
|
CertValidMonths = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Helper Functions
|
||||||
|
|
||||||
|
function Find-WindowsSDKTool {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$ToolName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Architecture = "x64"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simple fallback: check common Windows SDK locations
|
||||||
|
$commonPaths = @(
|
||||||
|
"${env:ProgramFiles}\Windows Kits\10\bin\*\$Architecture\$ToolName",
|
||||||
|
"${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\$Architecture\$ToolName",
|
||||||
|
"${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x86\$ToolName" # SignTool fallback
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($pattern in $commonPaths) {
|
||||||
|
$found = Get-ChildItem $pattern -ErrorAction SilentlyContinue |
|
||||||
|
Sort-Object Name -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($found) {
|
||||||
|
Write-BuildLog "Found $ToolName at: $($found.FullName)" -Level Info
|
||||||
|
return $found.FullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "$ToolName not found. Please ensure Windows SDK is installed."
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-CertificateValidity {
|
||||||
|
param([string]$ThumbprintFile)
|
||||||
|
|
||||||
|
if (-not (Test-Path $ThumbprintFile)) { return $false }
|
||||||
|
|
||||||
|
try {
|
||||||
|
$thumb = (Get-Content $ThumbprintFile -Raw).Trim()
|
||||||
|
if (-not $thumb) { return $false }
|
||||||
|
$cert = Get-Item "cert:\CurrentUser\My\$thumb" -ErrorAction Stop
|
||||||
|
return $cert.HasPrivateKey -and $cert.NotAfter -gt (Get-Date)
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-BuildLog {
|
||||||
|
param([string]$Message, [string]$Level = "Info")
|
||||||
|
|
||||||
|
$colors = @{ Error = "Red"; Warning = "Yellow"; Success = "Green"; Info = "Cyan" }
|
||||||
|
$color = if ($colors.ContainsKey($Level)) { $colors[$Level] } else { "White" }
|
||||||
|
|
||||||
|
Write-Host "[$(Get-Date -f 'HH:mm:ss')] $Message" -ForegroundColor $color
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stop-FileProcesses {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$FilePath
|
||||||
|
)
|
||||||
|
|
||||||
|
# This function is kept for compatibility but simplified since
|
||||||
|
# the staging directory approach resolves the file lock issues
|
||||||
|
Write-Verbose "File process check for: $FilePath"
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
# Environment diagnostics for troubleshooting
|
||||||
|
Write-BuildLog "Starting PackageIdentity build process..." -Level Info
|
||||||
|
Write-BuildLog "PowerShell Version: $($PSVersionTable.PSVersion)" -Level Info
|
||||||
|
try {
|
||||||
|
$execPolicy = Get-ExecutionPolicy
|
||||||
|
Write-BuildLog "Execution Policy: $execPolicy" -Level Info
|
||||||
|
} catch {
|
||||||
|
Write-BuildLog "Execution Policy: Unable to determine (MSBuild environment)" -Level Info
|
||||||
|
}
|
||||||
|
Write-BuildLog "Current User: $env:USERNAME" -Level Info
|
||||||
|
Write-BuildLog "Build Platform: $Platform, Configuration: $Configuration" -Level Info
|
||||||
|
|
||||||
|
# Check for Visual Studio environment
|
||||||
|
if ($env:VSINSTALLDIR) {
|
||||||
|
Write-BuildLog "Running in Visual Studio environment: $env:VSINSTALLDIR" -Level Info
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure certificate provider is available
|
||||||
|
try {
|
||||||
|
# Force load certificate provider for MSBuild environment
|
||||||
|
if (-not (Get-PSProvider -PSProvider Certificate -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-BuildLog "Loading certificate provider..." -Level Warning
|
||||||
|
Import-Module Microsoft.PowerShell.Security -Force
|
||||||
|
}
|
||||||
|
if (-not (Test-Path 'Cert:\CurrentUser')) {
|
||||||
|
Write-BuildLog "Certificate drive not available, attempting to initialize..." -Level Warning
|
||||||
|
Import-Module PKI -ErrorAction SilentlyContinue
|
||||||
|
# Try to access the certificate store to force initialization
|
||||||
|
Get-ChildItem "Cert:\CurrentUser\My" -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-BuildLog ("Note: Certificate provider setup may need manual configuration: {0}" -f $_) -Level Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
# Project root folder (now set to current script folder for local builds)
|
||||||
|
$ProjectRoot = $PSScriptRoot
|
||||||
|
$UserFolder = Join-Path $ProjectRoot '.user'
|
||||||
|
if (-not (Test-Path $UserFolder)) { New-Item -ItemType Directory -Path $UserFolder | Out-Null }
|
||||||
|
|
||||||
|
# Certificate file paths using configuration
|
||||||
|
$prefix = $script:Config.CertPrefix
|
||||||
|
$CertThumbFile, $CertCerFile = @('.thumbprint', '.cer') |
|
||||||
|
ForEach-Object { Join-Path $UserFolder "$prefix.certificate.sample$_" }
|
||||||
|
|
||||||
|
# Clean option: remove bin/obj and uninstall existing sparse package if present
|
||||||
|
if ($Clean) {
|
||||||
|
Write-BuildLog "Cleaning build artifacts..." -Level Info
|
||||||
|
'bin','obj' | ForEach-Object {
|
||||||
|
$target = Join-Path $ProjectRoot $_
|
||||||
|
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
|
||||||
|
}
|
||||||
|
Write-BuildLog "Attempting to remove existing sparse package (best effort)" -Level Info
|
||||||
|
try { Get-AppxPackage -Name $script:Config.IdentityName | Remove-AppxPackage } catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Force certificate regeneration if requested
|
||||||
|
if ($ForceCert -and (Test-Path $UserFolder)) {
|
||||||
|
Write-BuildLog "ForceCert specified: removing existing certificate artifacts..." -Level Warning
|
||||||
|
Remove-Item $UserFolder -Recurse -Force
|
||||||
|
New-Item -ItemType Directory -Path $UserFolder | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure dev cert (development only; not for production use) - skip if NoSign specified
|
||||||
|
$needNewCert = -not $NoSign -and (-not (Test-Path $CertThumbFile) -or $ForceCert -or -not (Test-CertificateValidity -ThumbprintFile $CertThumbFile))
|
||||||
|
|
||||||
|
if ($needNewCert) {
|
||||||
|
Write-BuildLog "Generating development certificate (prefix=$($script:Config.CertPrefix))..." -Level Info
|
||||||
|
|
||||||
|
# Clear stale files in the certificate cache
|
||||||
|
if (Test-Path $UserFolder) {
|
||||||
|
Get-ChildItem -Path $UserFolder | ForEach-Object {
|
||||||
|
if ($_.PSIsContainer) {
|
||||||
|
Remove-Item $_.FullName -Recurse -Force
|
||||||
|
} else {
|
||||||
|
Remove-Item $_.FullName -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $UserFolder)) {
|
||||||
|
New-Item -ItemType Directory -Path $UserFolder | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = Get-Date
|
||||||
|
$expiration = $now.AddMonths($script:Config.CertValidMonths)
|
||||||
|
# Subject MUST match <Identity Publisher="..."> inside AppxManifest.xml
|
||||||
|
$friendlyName = "PowerToys Dev Sparse Cert Create=$now"
|
||||||
|
$keyFriendly = "PowerToys Dev Sparse Key Create=$now"
|
||||||
|
|
||||||
|
$certStore = 'cert:\CurrentUser\My'
|
||||||
|
$ekuOid = '2.5.29.37'
|
||||||
|
$ekuValue = '1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13'
|
||||||
|
$eku = "$ekuOid={text}$ekuValue"
|
||||||
|
|
||||||
|
$cert = New-SelfSignedCertificate -CertStoreLocation $certStore `
|
||||||
|
-NotAfter $expiration `
|
||||||
|
-Subject $script:Config.CertSubject `
|
||||||
|
-FriendlyName $friendlyName `
|
||||||
|
-KeyFriendlyName $keyFriendly `
|
||||||
|
-KeyDescription $keyFriendly `
|
||||||
|
-TextExtension $eku
|
||||||
|
|
||||||
|
# Export certificate files
|
||||||
|
Set-Content -Path $CertThumbFile -Value $cert.Thumbprint -Force
|
||||||
|
Export-Certificate -Cert $cert -FilePath $CertCerFile -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine output directory - using PowerToys standard structure
|
||||||
|
# Navigate to PowerToys root (two levels up from src/PackageIdentity)
|
||||||
|
$PowerToysRoot = Split-Path (Split-Path $ProjectRoot -Parent) -Parent
|
||||||
|
$outDir = Join-Path $PowerToysRoot "$Platform\$Configuration"
|
||||||
|
|
||||||
|
if (-not (Test-Path $outDir)) {
|
||||||
|
Write-BuildLog "Creating output directory: $outDir" -Level Info
|
||||||
|
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# PackageIdentity folder (this script location) containing the sparse manifest and assets
|
||||||
|
$sparseDir = $PSScriptRoot
|
||||||
|
$manifestPath = Join-Path $sparseDir 'AppxManifest.xml'
|
||||||
|
if (-not (Test-Path $manifestPath)) { throw "Missing AppxManifest.xml in PackageIdentity folder: $manifestPath" }
|
||||||
|
|
||||||
|
$versionPropsPath = Join-Path $PowerToysRoot 'src\Version.props'
|
||||||
|
$targetManifestVersion = $null
|
||||||
|
$versionCandidate = $null
|
||||||
|
if (Test-Path $versionPropsPath) {
|
||||||
|
try {
|
||||||
|
[xml]$propsXml = Get-Content -Path $versionPropsPath -Raw
|
||||||
|
$versionCandidate = $propsXml.Project.PropertyGroup.Version
|
||||||
|
} catch {
|
||||||
|
Write-BuildLog ("Unable to read version from {0}: {1}" -f $versionPropsPath, $_) -Level Warning
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-BuildLog "Version.props not found at $versionPropsPath; manifest version will remain unchanged." -Level Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($versionCandidate) {
|
||||||
|
$targetManifestVersion = $versionCandidate.Trim()
|
||||||
|
if (($targetManifestVersion -split '\.').Count -lt 4) {
|
||||||
|
$targetManifestVersion = "$targetManifestVersion.0"
|
||||||
|
}
|
||||||
|
Write-BuildLog "Using sparse package version from Version.props: $targetManifestVersion" -Level Info
|
||||||
|
} else {
|
||||||
|
Write-BuildLog "No version value provided; manifest version will remain unchanged." -Level Info
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find MakeAppx.exe from Windows SDK
|
||||||
|
try {
|
||||||
|
$hostSdkArchitecture = if ([System.Environment]::Is64BitProcess) { 'x64' } else { 'x86' }
|
||||||
|
$makeAppxPath = Find-WindowsSDKTool -ToolName "makeappx.exe" -Architecture $hostSdkArchitecture
|
||||||
|
} catch {
|
||||||
|
Write-Error "MakeAppx.exe not found. Please ensure Windows SDK is installed."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pack sparse MSIX from PackageIdentity folder
|
||||||
|
$msixPath = Join-Path $outDir $script:Config.SparseMsixName
|
||||||
|
|
||||||
|
# Clean up existing MSIX file
|
||||||
|
if (Test-Path $msixPath) {
|
||||||
|
Write-BuildLog "Removing existing MSIX file..." -Level Info
|
||||||
|
try {
|
||||||
|
Remove-Item $msixPath -Force -ErrorAction Stop
|
||||||
|
Write-BuildLog "Successfully removed existing MSIX file" -Level Success
|
||||||
|
} catch {
|
||||||
|
Write-BuildLog ("Warning: Could not remove existing MSIX file: {0}" -f $_) -Level Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a clean staging directory to avoid file lock issues
|
||||||
|
$stagingDir = Join-Path $outDir "staging"
|
||||||
|
if (Test-Path $stagingDir) {
|
||||||
|
Remove-Item $stagingDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
New-Item -ItemType Directory -Path $stagingDir -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-BuildLog "Creating clean staging directory for packaging..." -Level Info
|
||||||
|
|
||||||
|
# Copy only essential files to staging directory to avoid file locks
|
||||||
|
$essentialFiles = @(
|
||||||
|
"AppxManifest.xml"
|
||||||
|
"Images\*"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($filePattern in $essentialFiles) {
|
||||||
|
$sourcePath = Join-Path $sparseDir $filePattern
|
||||||
|
$relativePath = $filePattern
|
||||||
|
|
||||||
|
if ($filePattern.Contains('\')) {
|
||||||
|
$targetDir = Join-Path $stagingDir (Split-Path $relativePath -Parent)
|
||||||
|
if (-not (Test-Path $targetDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filePattern.EndsWith('\*')) {
|
||||||
|
# Copy directory contents
|
||||||
|
$sourceDir = $sourcePath.TrimEnd('\*')
|
||||||
|
$targetDir = Join-Path $stagingDir (Split-Path $relativePath.TrimEnd('\*') -Parent)
|
||||||
|
if (Test-Path $sourceDir) {
|
||||||
|
Copy-Item -Path "$sourceDir\*" -Destination $targetDir -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# Copy single file
|
||||||
|
$targetPath = Join-Path $stagingDir $relativePath
|
||||||
|
if (Test-Path $sourcePath) {
|
||||||
|
Copy-Item -Path $sourcePath -Destination $targetPath -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure publisher matches the dev certificate for local builds
|
||||||
|
$manifestStagingPath = Join-Path $stagingDir 'AppxManifest.xml'
|
||||||
|
$shouldUseDevPublisher = -not $isCIBuild
|
||||||
|
if (Test-Path $manifestStagingPath) {
|
||||||
|
try {
|
||||||
|
[xml]$manifestXml = Get-Content -Path $manifestStagingPath -Raw
|
||||||
|
$identityNode = $manifestXml.Package.Identity
|
||||||
|
$manifestChanged = $false
|
||||||
|
if ($identityNode) {
|
||||||
|
$currentPublisherHint = $identityNode.Publisher
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($identityNode) {
|
||||||
|
if ($targetManifestVersion -and $identityNode.Version -ne $targetManifestVersion) {
|
||||||
|
Write-BuildLog "Updating manifest version to $targetManifestVersion" -Level Info
|
||||||
|
$identityNode.SetAttribute('Version', $targetManifestVersion)
|
||||||
|
$manifestChanged = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($shouldUseDevPublisher -and $identityNode.Publisher -ne $script:Config.CertSubject) {
|
||||||
|
Write-BuildLog "Updating manifest publisher for local build" -Level Warning
|
||||||
|
$identityNode.SetAttribute('Publisher', $script:Config.CertSubject)
|
||||||
|
$manifestChanged = $true
|
||||||
|
}
|
||||||
|
$currentPublisherHint = $identityNode.Publisher
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($manifestChanged) {
|
||||||
|
$manifestXml.Save($manifestStagingPath)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-BuildLog ("Unable to adjust manifest metadata: {0}" -f $_) -Level Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-BuildLog "Staging directory prepared with essential files only" -Level Success
|
||||||
|
|
||||||
|
# Pack MSIX using staging directory
|
||||||
|
Write-BuildLog "Packing sparse MSIX ($($script:Config.SparseMsixName)) from staging -> $msixPath" -Level Info
|
||||||
|
|
||||||
|
& $makeAppxPath pack /d $stagingDir /p $msixPath /nv /o
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0 -and (Test-Path $msixPath)) {
|
||||||
|
Write-BuildLog "MSIX packaging completed successfully" -Level Success
|
||||||
|
} else {
|
||||||
|
Write-BuildLog "MakeAppx failed with exit code $LASTEXITCODE" -Level Error
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
# Clean up staging directory
|
||||||
|
if (Test-Path $stagingDir) {
|
||||||
|
try {
|
||||||
|
Remove-Item $stagingDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-BuildLog "Cleaned up staging directory" -Level Info
|
||||||
|
} catch {
|
||||||
|
Write-BuildLog ("Warning: Could not clean up staging directory: {0}" -f $_) -Level Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sign package (skip if NoSign specified for CI scenarios)
|
||||||
|
if ($NoSign) {
|
||||||
|
Write-BuildLog "Skipping signing (NoSign specified for CI build)" -Level Warning
|
||||||
|
} else {
|
||||||
|
# Use certificate thumbprint for signing (safer, no password)
|
||||||
|
$certThumbprint = (Get-Content -Path $CertThumbFile -Raw).Trim()
|
||||||
|
try {
|
||||||
|
$signToolPath = Find-WindowsSDKTool -ToolName "signtool.exe"
|
||||||
|
} catch {
|
||||||
|
Write-Error "SignTool.exe not found. Please ensure Windows SDK is installed."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-BuildLog "Signing sparse MSIX using cert thumbprint $certThumbprint..." -Level Info
|
||||||
|
& $signToolPath sign /fd SHA256 /sha1 $certThumbprint $msixPath
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Warning "SignTool failed (exit $LASTEXITCODE). Ensure the certificate is in CurrentUser\\My and try -ForceCert if needed."
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$publisherHintFile = Join-Path $UserFolder "$($script:Config.CertPrefix).publisher.txt"
|
||||||
|
try {
|
||||||
|
Set-Content -Path $publisherHintFile -Value $currentPublisherHint -Force -NoNewline
|
||||||
|
} catch {
|
||||||
|
Write-BuildLog ("Unable to write publisher hint: {0}" -f $_) -Level Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-BuildLog "`nPackage created: $msixPath" -Level Success
|
||||||
|
|
||||||
|
if ($NoSign) {
|
||||||
|
Write-BuildLog "UNSIGNED package created for CI build. Sign before deployment." -Level Warning
|
||||||
|
} else {
|
||||||
|
Write-BuildLog "Install the dev certificate (once): $CertCerFile" -Level Info
|
||||||
|
Write-BuildLog "Identity Name: $($script:Config.IdentityName)" -Level Info
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-BuildLog "Register sparse package:" -Level Info
|
||||||
|
Write-BuildLog " Add-AppxPackage -Path `"$msixPath`" -ExternalLocation `"$outDir`"" -Level Warning
|
||||||
|
Write-BuildLog "(If already installed and you changed manifest only): Add-AppxPackage -Register `"$manifestPath`" -ExternalLocation `"$outDir`" -ForceApplicationShutdown" -Level Warning
|
||||||
43
src/PackageIdentity/Check-ProcessIdentity.ps1
Normal file
43
src/PackageIdentity/Check-ProcessIdentity.ps1
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Determine whether a given process (by PID) runs with an MSIX/UWP package identity.
|
||||||
|
.DESCRIPTION
|
||||||
|
Calls the Windows API GetPackageFullName to check if the target process executes under an MSIX/Sparse App/UWP package identity.
|
||||||
|
Returns the package full name when identity is present, or "No package identity" otherwise.
|
||||||
|
.PARAMETER ProcessId
|
||||||
|
The process ID to inspect.
|
||||||
|
.EXAMPLE
|
||||||
|
.\Check-ProcessIdentity.ps1 -pid 12345
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[int]$ProcessId
|
||||||
|
)
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition @'
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class P {
|
||||||
|
[DllImport("kernel32.dll", SetLastError=true)]
|
||||||
|
public static extern IntPtr OpenProcess(uint a, bool b, int p);
|
||||||
|
[DllImport("kernel32.dll", SetLastError=true)]
|
||||||
|
public static extern bool CloseHandle(IntPtr h);
|
||||||
|
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
|
||||||
|
public static extern int GetPackageFullName(IntPtr h, ref int l, StringBuilder b);
|
||||||
|
public static string G(int pid) {
|
||||||
|
IntPtr h = OpenProcess(0x1000, false, pid);
|
||||||
|
if (h == IntPtr.Zero) return "Failed to open process";
|
||||||
|
int len = 0;
|
||||||
|
GetPackageFullName(h, ref len, null);
|
||||||
|
if (len == 0) { CloseHandle(h); return "No package identity"; }
|
||||||
|
var sb = new StringBuilder(len);
|
||||||
|
int r = GetPackageFullName(h, ref len, sb);
|
||||||
|
CloseHandle(h);
|
||||||
|
return r == 0 ? sb.ToString() : "Error:" + r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
|
||||||
|
$result = [P]::G($ProcessId)
|
||||||
|
Write-Output $result
|
||||||
BIN
src/PackageIdentity/Images/Square150x150Logo.png
Normal file
BIN
src/PackageIdentity/Images/Square150x150Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
BIN
src/PackageIdentity/Images/Square44x44Logo.png
Normal file
BIN
src/PackageIdentity/Images/Square44x44Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
BIN
src/PackageIdentity/Images/StoreLogo.png
Normal file
BIN
src/PackageIdentity/Images/StoreLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
120
src/PackageIdentity/PackageIdentity.vcxproj
Normal file
120
src/PackageIdentity/PackageIdentity.vcxproj
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<!-- CI Build Configuration -->
|
||||||
|
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
||||||
|
<ForceCIPackaging>true</ForceCIPackaging>
|
||||||
|
<NoSignCI>true</NoSignCI>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Target to generate sparse MSIX package -->
|
||||||
|
<Target Name="GenerateSparsePackage" BeforeTargets="PrepareForBuild">
|
||||||
|
<!-- Use NoSign only for CI builds to avoid certificate issues on hosted agents -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<NoSignParam Condition="'$(NoSignCI)' == 'true'">-NoSign</NoSignParam>
|
||||||
|
<NoSignParam Condition="'$(NoSignCI)' != 'true'"></NoSignParam>
|
||||||
|
<CIBuildParam Condition="'$(CIBuild)' == 'true'">-CIBuild</CIBuildParam>
|
||||||
|
<CIBuildParam Condition="'$(CIBuild)' != 'true'"></CIBuildParam>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Exec Command="powershell -NonInteractive -ExecutionPolicy Bypass -File "$(MSBuildThisFileDirectory)BuildSparsePackage.ps1" -Platform $(Platform) -Configuration $(Configuration) $(NoSignParam) $(CIBuildParam)"
|
||||||
|
ContinueOnError="false"
|
||||||
|
WorkingDirectory="$(MSBuildThisFileDirectory)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|ARM64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>ARM64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|ARM64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>ARM64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>15.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}</ProjectGuid>
|
||||||
|
<RootNamespace>PackageIdentity</RootNamespace>
|
||||||
|
<ProjectName>PackageIdentity</ProjectName>
|
||||||
|
<UseFastUpToDateCheck>false</UseFastUpToDateCheck>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Utility</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Utility</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Utility</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Utility</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
|
||||||
|
<ImportGroup Label="PropertySheets">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
<Import Project="..\Solution.props" />
|
||||||
|
</ImportGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="AppxManifest.xml" />
|
||||||
|
<None Include="BuildSparsePackage.ps1" />
|
||||||
|
<None Include="BuildSparsePackage.cmd" />
|
||||||
|
<None Include="Check-ProcessIdentity.ps1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Image Include="Images\Square150x150Logo.png">
|
||||||
|
<Filter>Images</Filter>
|
||||||
|
</Image>
|
||||||
|
<Image Include="Images\Square44x44Logo.png">
|
||||||
|
<Filter>Images</Filter>
|
||||||
|
</Image>
|
||||||
|
<Image Include="Images\StoreLogo.png">
|
||||||
|
<Filter>Images</Filter>
|
||||||
|
</Image>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
25
src/PackageIdentity/PackageIdentity.vcxproj.filters
Normal file
25
src/PackageIdentity/PackageIdentity.vcxproj.filters
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Images">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>png;jpg;jpeg;gif;bmp;ico</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="AppxManifest.xml" />
|
||||||
|
<None Include="BuildSparsePackage.ps1" />
|
||||||
|
<None Include="BuildSparsePackage.cmd" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Image Include="Images\Square150x150Logo.png">
|
||||||
|
<Filter>Images</Filter>
|
||||||
|
</Image>
|
||||||
|
<Image Include="Images\Square44x44Logo.png">
|
||||||
|
<Filter>Images</Filter>
|
||||||
|
</Image>
|
||||||
|
<Image Include="Images\StoreLogo.png">
|
||||||
|
<Filter>Images</Filter>
|
||||||
|
</Image>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
90
src/PackageIdentity/readme.md
Normal file
90
src/PackageIdentity/readme.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# PowerToys sparse package identity
|
||||||
|
|
||||||
|
This document describes how to build, sign, register, and consume the shared sparse MSIX package that grants package identity to select Win32 components of PowerToys.
|
||||||
|
|
||||||
|
## Package overview
|
||||||
|
|
||||||
|
The sparse package lives under `src/PackageIdentity`. It produces a payload-free MSIX whose `Identity` matches `Microsoft.PowerToys.SparseApp`. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, PowerOCR, Image Resizer).
|
||||||
|
|
||||||
|
> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the output folder that hosts the Win32 binaries (for example `x64\Release`).
|
||||||
|
|
||||||
|
## Building the sparse package locally
|
||||||
|
|
||||||
|
Two options are available:
|
||||||
|
|
||||||
|
- Build the utility project from Visual Studio: `PackageIdentity.vcxproj` defines a `GenerateSparsePackage` target that runs before `PrepareForBuild` and invokes the helper script automatically.
|
||||||
|
- Invoke the helper script directly from PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$repoRoot = "C:/git/PowerToys"
|
||||||
|
pwsh "$repoRoot/src/PackageIdentity/BuildSparsePackage.ps1" -Platform x64 -Configuration Release
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported switches:
|
||||||
|
|
||||||
|
- `-Clean` removes previous `bin`/`obj` outputs and uninstalls existing installation.
|
||||||
|
- `-ForceCert` regenerates the local dev certificate (.pfx/.cer/.pwd/.thumbprint) under `src/PackageIdentity/.user`.
|
||||||
|
- `-NoSign` skips signing. The MSIX still builds but must be signed before deployment.
|
||||||
|
- `-CIBuild` (or setting `$env:CIBuild = 'true'`) keeps the manifest publisher intact and skips the local cert substitution.
|
||||||
|
|
||||||
|
The script determines the proper `makeappx.exe` for the host build machine (x64 on typical developer boxes) and creates `PowerToysSparse.msix` in `{repo}\<Platform>\<Configuration>`.
|
||||||
|
|
||||||
|
> After packaging finishes, the helper also emits `src/PackageIdentity/.user/PowerToysSparse.publisher.txt`. This file mirrors the publisher string Windows will see once the sparse package is registered, which downstream projects can read to stay in sync when generating their own manifests.
|
||||||
|
|
||||||
|
## Local signing basics
|
||||||
|
|
||||||
|
When `-NoSign` is not used the script generates (or reuses) a development certificate and signs the package via `signtool.exe`:
|
||||||
|
|
||||||
|
1. Artifacts are stored in `src/PackageIdentity/.user/PowerToysSparse.certificate.sample.*` (`.cer` and `.thumbprint`).
|
||||||
|
2. Install the `.cer` into `CurrentUser` → `TrustedPeople` (and `TrustedRoot`, if necessary) so Windows trusts the signature:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$repoRoot = "C:/git/PowerToys"
|
||||||
|
Import-Certificate -FilePath "$repoRoot/src/PackageIdentity/.user/PowerToysSparse.certificate.sample.cer" -CertStoreLocation Cert:\CurrentUser\TrustedPeople
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The private key stays in the current user's personal certificate store.
|
||||||
|
|
||||||
|
## Registering or unregistering the package
|
||||||
|
|
||||||
|
After `PowerToysSparse.msix` is generated:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# First time registration
|
||||||
|
$repoRoot = "C:/git/PowerToys"
|
||||||
|
$outputRoot = Join-Path $repoRoot "x64/Release"
|
||||||
|
Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot
|
||||||
|
|
||||||
|
# Re-register after manifest tweaks only
|
||||||
|
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown
|
||||||
|
|
||||||
|
# Remove the sparse identity
|
||||||
|
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
|
||||||
|
```
|
||||||
|
|
||||||
|
`-ExternalLocation` should match the output folder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes.
|
||||||
|
|
||||||
|
## CI-specific guidance
|
||||||
|
|
||||||
|
- Pass `-CIBuild` to `BuildSparsePackage.ps1` (or build with `msbuild PackageIdentity.vcxproj /p:CIBuild=true`). This prevents the script from rewriting the manifest publisher to the local dev certificate subject.
|
||||||
|
- The project automatically adds `-NoSign` only when `$(CIBuild)` is `true`. Local Debug and Release builds are signed with the development certificate.
|
||||||
|
- Make sure the agent trusts whichever certificate signs the package. If the package remains unsigned (`-NoSign`) it cannot be installed on test machines until it is signed.
|
||||||
|
|
||||||
|
## Consuming the identity from other components
|
||||||
|
|
||||||
|
1. Add a new `<Application>` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` root.
|
||||||
|
2. Ensure the binary is copied into the platform/configuration output folder (`x64\Release`, `ARM64\Debug`, etc.) so the sparse package can locate it.
|
||||||
|
3. Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an `<msix>` element with `packageName="Microsoft.PowerToys.SparseApp"`, `applicationId` matching the `<Application Id>`, and a `publisher` that matches the sparse package. Keep the manifest’s publisher in sync with `src/PackageIdentity/.user/PowerToysSparse.publisher.txt` (emitted by `BuildSparsePackage.ps1`). See `src/modules/imageresizer/ui/ImageResizerUI.csproj` for an example that points `ApplicationManifest` to `ImageResizerUI.dev.manifest` for local builds and switches to `ImageResizerUI.prod.manifest` when `$(CIBuild)` is `true`.
|
||||||
|
4. Register or re-register the sparse package so Windows learns about the new application Id.
|
||||||
|
5. To launch the Win32 surface with identity, use the `shell:AppsFolder` activation form (for example: `shell:AppsFolder\Microsoft.PowerToys.SparseApp_<PackageFamilyName>!PowerToys.MyModuleUI`) or activate it via `IApplicationActivationManager::ActivateApplication` using the same AppUserModelID.
|
||||||
|
|
||||||
|
- For locally built packages, resolve the `<PackageFamilyName>` with `Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Select-Object -ExpandProperty PackageFamilyName`.
|
||||||
|
- Store-distributed builds use `Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe`. Local developer builds created by this script typically use a different family name derived from the dev certificate.
|
||||||
|
|
||||||
|
6. Context menu handlers or other launchers should fall back to the unpackaged executable path for environments where the sparse package is not present.
|
||||||
|
|
||||||
|
## Troubleshooting tips
|
||||||
|
|
||||||
|
- `Program 'makeappx.exe' failed to run`: make sure you are running an x64 PowerShell host. The script now chooses the appropriate makeappx automatically; update your repo if the log still points to an ARM64 binary.
|
||||||
|
- `HRESULT 0x800B0109 (trust failure)`: install the development certificate into both `TrustedPeople` and `TrustedRoot` stores for the current user.
|
||||||
|
- Stale registration: remove the package with `Remove-AppxPackage` and re-run the script with `-Clean` to rebuild from scratch.
|
||||||
@@ -24,6 +24,14 @@
|
|||||||
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>ImageResizerUI.dev.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
||||||
|
<ApplicationManifest>ImageResizerUI.prod.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Properties\Resources.resx">
|
<EmbeddedResource Update="Properties\Resources.resx">
|
||||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
@@ -55,4 +63,14 @@
|
|||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Ensure Resources directory and ImageResizer.png are available for dependent projects -->
|
||||||
|
<Target Name="CopyResourcesToSharedLocation" AfterTargets="Build">
|
||||||
|
<ItemGroup>
|
||||||
|
<ResourceFiles Include="$(MSBuildProjectDirectory)\Resources\ImageResizer.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
<MakeDir Directories="$(OutputPath)Resources" Condition="!Exists('$(OutputPath)Resources')" />
|
||||||
|
<Copy SourceFiles="@(ResourceFiles)" DestinationFolder="$(OutputPath)Resources" SkipUnchangedFiles="true" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
8
src/modules/imageresizer/ui/ImageResizerUI.dev.manifest
Normal file
8
src/modules/imageresizer/ui/ImageResizerUI.dev.manifest
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="PowerToys.ImageResizer.app" />
|
||||||
|
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
|
||||||
|
publisher="CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US"
|
||||||
|
packageName="Microsoft.PowerToys.SparseApp"
|
||||||
|
applicationId="PowerToys.ImageResizerUI" />
|
||||||
|
</assembly>
|
||||||
8
src/modules/imageresizer/ui/ImageResizerUI.prod.manifest
Normal file
8
src/modules/imageresizer/ui/ImageResizerUI.prod.manifest
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="PowerToys.ImageResizer.app" />
|
||||||
|
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
|
||||||
|
publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||||
|
packageName="Microsoft.PowerToys.SparseApp"
|
||||||
|
applicationId="PowerToys.ImageResizerUI" />
|
||||||
|
</assembly>
|
||||||
Reference in New Issue
Block a user