From 8cb2e4eaf7d6bf07b2cacc3d67df60061287edac Mon Sep 17 00:00:00 2001 From: leileizhang Date: Thu, 21 Aug 2025 18:10:30 +0800 Subject: [PATCH] refactor: Replace WiX-based registration with conditional runtime registration for Win10 context menu modules (#41275) ## Summary of the Pull Request ## Root Cause WiX-based registration creates persistent Shell Extension entries that: 1. Load DLLs even when the module is disabled 2. Cause cross-OS version conflicts (Win11 loading Win10 extensions) ## Changes Made 1. Removed static Shell Extension registration from PowerToys installer 2. Modified modules to register Shell Extensions during Runner startup ### Modified Modules: - **PowerRename** (`src/modules/powerrename/dll/dllmain.cpp`) - **NewPlus** (`src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp`) - **ImageResizer** (`src/modules/imageresizer/dll/dllmain.cpp`) - **FileLocksmith** (`src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp`) ## Known Migration Issue **Machine-level installer registry residue**: win10 with machine-level installers may have residual Shell Extension registry entries that persist with this change. ## PR Checklist - [x] Closes: #40036 - [ ] **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 ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed ## AI Summary This pull request refactors how shell extension registry keys are managed during installation and uninstallation for several PowerToys modules. The main change is moving registry key cleanup logic for context menu shell extensions (ImageResizer, FileLocksmith, PowerRename, NewPlus) from static installer definitions to new custom uninstall actions, ensuring more reliable removal and future extensibility. **Installer and Uninstall Refactoring** * Added new custom actions (`CleanImageResizerRuntimeRegistryCA`, `CleanFileLocksmithRuntimeRegistryCA`, `CleanPowerRenameRuntimeRegistryCA`, `CleanNewPlusRuntimeRegistryCA`) to programmatically clean up registry keys for each shell extension during uninstall, implemented in `CustomAction.cpp` and exported in `CustomAction.def`. [[1]](diffhunk://#diff-c502a81cdf8afa7a38f0f462709abcdbdfcc44beaa6227a1e64a26566c7e8876R1156-R1262) [[2]](diffhunk://#diff-f941d599be5fe41667eda00338af694c0f2e65709d497a66487402f13e408200R31-R34) * Registered these custom actions in `Product.wxs` and ensured they run before file removal during uninstall. [[1]](diffhunk://#diff-668b4388b55bb934d7ceccbfdd172f69257c9c607ca19cb9752d4a4940b69886R179-R190) [[2]](diffhunk://#diff-668b4388b55bb934d7ceccbfdd172f69257c9c607ca19cb9752d4a4940b69886R454-R482) **Removal of Static Registry Key Definitions** * Removed static registry key and component definitions for context menu shell extensions from their respective installer `.wxs` files (`FileLocksmith.wxs`, `ImageResizer.wxs`, `PowerRename.wxs`, `NewPlus.wxs`), relying on custom actions for cleanup instead. [[1]](diffhunk://#diff-7cf9797f8cb6609049763b3b830f6c4a7a02ba5705eb090f7e06fb9c270ca74fL17-L31) [[2]](diffhunk://#diff-7cf9797f8cb6609049763b3b830f6c4a7a02ba5705eb090f7e06fb9c270ca74fL41) [[3]](diffhunk://#diff-c6d00805ce9de0eb3f4d42874dccac17be62f36c35d57e8f863b928b5f955d3aL19-L83) [[4]](diffhunk://#diff-c6d00805ce9de0eb3f4d42874dccac17be62f36c35d57e8f863b928b5f955d3aL93) [[5]](diffhunk://#diff-d0d69eff3f2d7982679465972b7d3c46dd8006314fb28f0e3a2371e2d5ccedb0L21-L33) [[6]](diffhunk://#diff-d0d69eff3f2d7982679465972b7d3c46dd8006314fb28f0e3a2371e2d5ccedb0L43) [[7]](diffhunk://#diff-4fd109f66b896577cad2860a829617ca902b33551afaaa8840372035ade2d3f3L17-L32) [[8]](diffhunk://#diff-4fd109f66b896577cad2860a829617ca902b33551afaaa8840372035ade2d3f3L42) **Project File Update** * Added `shell_ext_registration.h` to the solution file, possibly for future shell extension registration logic. These changes improve uninstall reliability and centralize registry cleanup logic, making future maintenance and extension of shell extension registration much simpler. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- PowerToys.sln | 1 + installer/PowerToysSetup/FileLocksmith.wxs | 16 -- installer/PowerToysSetup/ImageResizer.wxs | 66 ----- installer/PowerToysSetup/NewPlus.wxs | 18 +- installer/PowerToysSetup/PowerRename.wxs | 17 -- installer/PowerToysSetup/Product.wxs | 41 +++ .../CustomAction.cpp | 107 +++++++ .../CustomAction.def | 4 + src/common/utils/shell_ext_registration.h | 266 ++++++++++++++++++ .../FileLocksmithExt/FileLocksmithExt.vcxproj | 1 + .../FileLocksmithExt.vcxproj.filters | 3 + .../FileLocksmithExt/PowerToysModule.cpp | 15 +- .../FileLocksmithExt/RuntimeRegistration.h | 36 +++ .../NewShellExtensionContextMenu.vcxproj | 1 + ...wShellExtensionContextMenu.vcxproj.filters | 3 + .../RuntimeRegistration.h | 36 +++ .../powertoys_module.cpp | 20 +- .../imageresizer/dll/ImageResizerExt.vcxproj | 1 + .../dll/ImageResizerExt.vcxproj.filters | 3 + .../imageresizer/dll/RuntimeRegistration.h | 38 +++ src/modules/imageresizer/dll/dllmain.cpp | 15 +- .../powerrename/dll/PowerRenameExt.vcxproj | 1 + .../dll/PowerRenameExt.vcxproj.filters | 3 + .../powerrename/dll/RuntimeRegistration.h | 37 +++ src/modules/powerrename/dll/dllmain.cpp | 15 +- 25 files changed, 644 insertions(+), 120 deletions(-) create mode 100644 src/common/utils/shell_ext_registration.h create mode 100644 src/modules/FileLocksmith/FileLocksmithExt/RuntimeRegistration.h create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu/RuntimeRegistration.h create mode 100644 src/modules/imageresizer/dll/RuntimeRegistration.h create mode 100644 src/modules/powerrename/dll/RuntimeRegistration.h diff --git a/PowerToys.sln b/PowerToys.sln index 6033ca1481..e6ae7896fe 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -262,6 +262,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643 src\common\utils\EventLocker.h = src\common\utils\EventLocker.h src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h + src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h src\common\utils\exec.h = src\common\utils\exec.h src\common\utils\game_mode.h = src\common\utils\game_mode.h src\common\utils\gpo.h = src\common\utils\gpo.h diff --git a/installer/PowerToysSetup/FileLocksmith.wxs b/installer/PowerToysSetup/FileLocksmith.wxs index 085e60eaa7..5943ab4147 100644 --- a/installer/PowerToysSetup/FileLocksmith.wxs +++ b/installer/PowerToysSetup/FileLocksmith.wxs @@ -14,21 +14,6 @@ - - - - - - - - - - - - - - - @@ -38,7 +23,6 @@ - diff --git a/installer/PowerToysSetup/ImageResizer.wxs b/installer/PowerToysSetup/ImageResizer.wxs index 17da272014..67b5acf198 100644 --- a/installer/PowerToysSetup/ImageResizer.wxs +++ b/installer/PowerToysSetup/ImageResizer.wxs @@ -16,71 +16,6 @@ - - - - - - - - - - - - - - - - - - - - - - - @@ -90,7 +25,6 @@ - diff --git a/installer/PowerToysSetup/NewPlus.wxs b/installer/PowerToysSetup/NewPlus.wxs index 4dd1c67701..624c01fca2 100644 --- a/installer/PowerToysSetup/NewPlus.wxs +++ b/installer/PowerToysSetup/NewPlus.wxs @@ -18,19 +18,6 @@ - - - - - - - - - - - - - @@ -40,8 +27,7 @@ - - + @@ -81,7 +67,7 @@ - + diff --git a/installer/PowerToysSetup/PowerRename.wxs b/installer/PowerToysSetup/PowerRename.wxs index 1e722d9334..7aa357e207 100644 --- a/installer/PowerToysSetup/PowerRename.wxs +++ b/installer/PowerToysSetup/PowerRename.wxs @@ -14,22 +14,6 @@ - - - - - - - - - - - - - - - - @@ -39,7 +23,6 @@ - diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index f15b8a4714..77ffad8483 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -176,6 +176,18 @@ Installed AND (REMOVE="ALL") + + Installed AND (REMOVE="ALL") + + + Installed AND (REMOVE="ALL") + + + Installed AND (REMOVE="ALL") + + + Installed AND (REMOVE="ALL") + Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") @@ -437,6 +449,35 @@ Execute="deferred" BinaryKey="PTCustomActions" DllEntry="UnRegisterContextMenuPackagesCA" + /> + + + + + +#include +#include +#include + +#include "../logger/logger.h" + +namespace runtime_shell_ext +{ + struct Spec + { + // Mandatory + std::wstring clsid; // e.g. {GUID} + std::wstring sentinelKey; // e.g. Software\\Microsoft\\PowerToys\\ModuleName + std::wstring sentinelValue; // e.g. ContextMenuRegistered + std::vector dllFileCandidates; // relative filenames (pick first existing) + std::vector contextMenuHandlerKeyPaths; // full HKCU relative paths where default value = CLSID + + // Optional + std::wstring friendlyName; // if non-empty written as default under CLSID root + bool writeOptInEmptyValue = true; // write ContextMenuOptIn="" under CLSID root (legacy pattern) + bool writeThreadingModel = true; // write Apartment threading model + std::vector extraAssociationPaths; // additional key paths (DragDropHandlers etc.) default=CLSID + std::vector systemFileAssocExtensions; // e.g. .png -> Software\\Classes\\SystemFileAssociations\\.png\\ShellEx\\ContextMenuHandlers\\ + std::wstring systemFileAssocHandlerName; // e.g. ImageResizer + std::wstring representativeSystemExt; // used to decide if associations need repair (.png) + bool logRepairs = true; + }; + + namespace detail + { + // Minimal RAII wrapper for HKEY + struct unique_hkey + { + HKEY h{ nullptr }; + unique_hkey() = default; + explicit unique_hkey(HKEY handle) : h(handle) {} + ~unique_hkey() { if (h) RegCloseKey(h); } + unique_hkey(const unique_hkey&) = delete; + unique_hkey& operator=(const unique_hkey&) = delete; + unique_hkey(unique_hkey&& other) noexcept : h(other.h) { other.h = nullptr; } + unique_hkey& operator=(unique_hkey&& other) noexcept { if (this != &other) { if (h) RegCloseKey(h); h = other.h; other.h = nullptr; } return *this; } + HKEY get() const { return h; } + HKEY* put() { if (h) { RegCloseKey(h); h = nullptr; } return &h; } + }; + inline std::wstring base_dir_from_module(HMODULE h) + { + wchar_t buf[MAX_PATH]; + if (GetModuleFileNameW(h, buf, MAX_PATH)) + { + PathRemoveFileSpecW(buf); + return buf; + } + return L""; + } + + inline std::wstring pick_existing_dll(const std::wstring& base, const std::vector& candidates) + { + for (const auto& rel : candidates) + { + std::wstring full = base + L"\\" + rel; + if (GetFileAttributesW(full.c_str()) != INVALID_FILE_ATTRIBUTES) + { + return full; + } + } + if (!candidates.empty()) + { + return base + L"\\" + candidates.front(); + } + return L""; + } + + inline bool sentinel_exists(const Spec& spec) + { + unique_hkey key; + if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS) + return false; + DWORD v = 0; DWORD sz = sizeof(v); + return RegQueryValueExW(key.get(), spec.sentinelValue.c_str(), nullptr, nullptr, reinterpret_cast(&v), &sz) == ERROR_SUCCESS && v == 1; + } + + inline void write_sentinel(const Spec& spec) + { + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + DWORD one = 1; + RegSetValueExW(key.get(), spec.sentinelValue.c_str(), 0, REG_DWORD, reinterpret_cast(&one), sizeof(one)); + } + } + + inline void write_inproc_server(const Spec& spec, const std::wstring& dllPath) + { + using namespace std::string_literals; + std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid; + std::wstring inprocKey = clsidRoot + L"\\InprocServer32"; + { + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, clsidRoot.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + if (!spec.friendlyName.empty()) + { + RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast(spec.friendlyName.c_str()), static_cast((spec.friendlyName.size() + 1) * sizeof(wchar_t))); + } + if (spec.writeOptInEmptyValue) + { + const wchar_t* optIn = L"ContextMenuOptIn"; + const wchar_t empty = L'\0'; + RegSetValueExW(key.get(), optIn, 0, REG_SZ, reinterpret_cast(&empty), sizeof(empty)); + } + } + } + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast(dllPath.c_str()), static_cast((dllPath.size() + 1) * sizeof(wchar_t))); + if (spec.writeThreadingModel) + { + const wchar_t* tm = L"Apartment"; + RegSetValueExW(key.get(), L"ThreadingModel", 0, REG_SZ, reinterpret_cast(tm), static_cast((wcslen(tm) + 1) * sizeof(wchar_t))); + } + } + } + + inline std::wstring read_inproc_server(const Spec& spec) + { + using namespace std::string_literals; + std::wstring inprocKey = L"Software\\Classes\\CLSID\\"s + spec.clsid + L"\\InprocServer32"; + unique_hkey key; + if (RegOpenKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS) + return L""; + wchar_t buf[MAX_PATH]; DWORD sz = sizeof(buf); + if (RegQueryValueExW(key.get(), nullptr, nullptr, nullptr, reinterpret_cast(buf), &sz) == ERROR_SUCCESS) + return std::wstring(buf); + return L""; + } + + inline void write_default_value_key(const std::wstring& keyPath, const std::wstring& value) + { + unique_hkey key; + if (RegCreateKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS) + { + RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast(value.c_str()), static_cast((value.size() + 1) * sizeof(wchar_t))); + } + } + + inline bool representative_association_exists(const Spec& spec) + { + using namespace std::string_literals; + if (spec.representativeSystemExt.empty() || spec.systemFileAssocHandlerName.empty()) + return true; + std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + spec.representativeSystemExt + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName; + unique_hkey key; + return RegOpenKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, KEY_READ, key.put()) == ERROR_SUCCESS; + } + } + + inline bool EnsureRegistered(const Spec& spec, HMODULE moduleInstance) + { + using namespace std::string_literals; + auto base = detail::base_dir_from_module(moduleInstance); + auto dllPath = detail::pick_existing_dll(base, spec.dllFileCandidates); + if (dllPath.empty()) + { + Logger::error(L"Runtime registration: cannot locate dll path for CLSID {}", spec.clsid); + return false; + } + bool exists = detail::sentinel_exists(spec); + bool repaired = false; + if (exists) + { + auto current = detail::read_inproc_server(spec); + if (_wcsicmp(current.c_str(), dllPath.c_str()) != 0) + { + detail::write_inproc_server(spec, dllPath); + repaired = true; + } + if (!detail::representative_association_exists(spec)) + { + repaired = true; + } + } + if (!exists) + { + detail::write_inproc_server(spec, dllPath); + } + if (!exists || repaired) + { + for (const auto& path : spec.contextMenuHandlerKeyPaths) + { + detail::write_default_value_key(path, spec.clsid); + } + for (const auto& path : spec.extraAssociationPaths) + { + detail::write_default_value_key(path, spec.clsid); + } + if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty()) + { + for (const auto& ext : spec.systemFileAssocExtensions) + { + std::wstring path = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName; + detail::write_default_value_key(path, spec.clsid); + } + } + } + if (!exists) + { + detail::write_sentinel(spec); + Logger::info(L"Runtime registration completed for CLSID {}", spec.clsid); + } + else if (repaired && spec.logRepairs) + { + Logger::info(L"Runtime registration repaired for CLSID {}", spec.clsid); + } + return true; + } + + inline bool Unregister(const Spec& spec) + { + using namespace std::string_literals; + // Remove handler key paths + for (const auto& path : spec.contextMenuHandlerKeyPaths) + { + RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str()); + } + // Remove extra association paths (e.g., drag & drop handlers) + for (const auto& path : spec.extraAssociationPaths) + { + RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str()); + } + // Remove per-extension system file association handler keys + if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty()) + { + for (const auto& ext : spec.systemFileAssocExtensions) + { + std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName; + RegDeleteTreeW(HKEY_CURRENT_USER, keyPath.c_str()); + } + } + // Remove CLSID branch + if (!spec.clsid.empty()) + { + std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid; + RegDeleteTreeW(HKEY_CURRENT_USER, clsidRoot.c_str()); + } + // Remove sentinel value (not deleting entire key to avoid disturbing other values) + if (!spec.sentinelKey.empty() && !spec.sentinelValue.empty()) + { + HKEY hKey{}; + if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) + { + RegDeleteValueW(hKey, spec.sentinelValue.c_str()); + RegCloseKey(hKey); + } + } + Logger::info(L"Successfully unregistered CLSID {}", spec.clsid); + return true; + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj index 0c285a8bfa..c67119808f 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj @@ -73,6 +73,7 @@ + diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters index c3b4f47ebc..49bf0e21a9 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters @@ -27,6 +27,9 @@ Header Files + + Header Files + diff --git a/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp b/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp index ec755d99a3..7d188a2010 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp +++ b/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp @@ -12,6 +12,7 @@ #include "FileLocksmithLib/Constants.h" #include "FileLocksmithLib/Settings.h" #include "FileLocksmithLib/Trace.h" +#include "RuntimeRegistration.h" #include "dllmain.h" #include "Generated Files/resource.h" @@ -82,12 +83,17 @@ public: { std::wstring path = get_module_folderpath(globals::instance); std::wstring packageUri = path + L"\\FileLocksmithContextMenuPackage.msix"; - if (!package::IsPackageRegisteredWithPowerToysVersion(constants::nonlocalizable::ContextMenuPackageName)) { package::RegisterSparsePackage(path, packageUri); } } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + FileLocksmithRuntimeRegistration::EnsureRegistered(); +#endif + } m_enabled = true; } @@ -95,6 +101,13 @@ public: virtual void disable() override { Logger::info(L"File Locksmith disabled"); + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + FileLocksmithRuntimeRegistration::Unregister(); + Logger::info(L"File Locksmith context menu unregistered (Win10)"); +#endif + } m_enabled = false; } diff --git a/src/modules/FileLocksmith/FileLocksmithExt/RuntimeRegistration.h b/src/modules/FileLocksmith/FileLocksmithExt/RuntimeRegistration.h new file mode 100644 index 0000000000..4dd0d34bea --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/RuntimeRegistration.h @@ -0,0 +1,36 @@ +// Header-only runtime registration for FileLocksmith context menu extension. +#pragma once + +#include + +namespace globals { extern HMODULE instance; } + +namespace FileLocksmithRuntimeRegistration +{ + namespace + { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\FileLocksmith"; + spec.sentinelValue = L"ContextMenuRegistered"; + spec.dllFileCandidates = { L"PowerToys.FileLocksmithExt.dll" }; + spec.contextMenuHandlerKeyPaths = { + L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt", + L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt" }; + spec.friendlyName = L"File Locksmith Shell Extension"; + return spec; + } + } + + inline bool EnsureRegistered() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), globals::instance); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj index 9ac251c8ec..90058a503e 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj @@ -123,6 +123,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters index 7d014eb00f..d8b2eaf9ea 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters @@ -84,6 +84,9 @@ Header Files + + Header Files + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/RuntimeRegistration.h b/src/modules/NewPlus/NewShellExtensionContextMenu/RuntimeRegistration.h new file mode 100644 index 0000000000..91596d9e3d --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/RuntimeRegistration.h @@ -0,0 +1,36 @@ +// Header-only runtime registration for New+ Win10 context menu. +#pragma once + +#include +#include +#include + +// Provided by dll_main.cpp +extern HMODULE module_instance_handle; + +namespace NewPlusRuntimeRegistration +{ + namespace { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\NewPlus"; + spec.sentinelValue = L"ContextMenuRegisteredWin10"; + spec.dllFileCandidates = { L"PowerToys.NewPlus.ShellExtension.win10.dll" }; + spec.contextMenuHandlerKeyPaths = { L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10" }; + spec.friendlyName = L"NewPlus Shell Extension Win10"; + return spec; + } + } + + inline bool EnsureRegisteredWin10() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), module_instance_handle); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp index 303f072e3b..ad94431953 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp @@ -16,6 +16,7 @@ #include "trace.h" #include "new_utilities.h" #include "Generated Files/resource.h" +#include "RuntimeRegistration.h" // Note: Settings are managed via Settings and UI Settings class NewModule : public PowertoyModuleIface @@ -93,8 +94,16 @@ public: // Log telemetry Trace::EventToggleOnOff(true); - - newplus::utilities::register_msix_package(); + if (package::IsWin11OrGreater()) + { + newplus::utilities::register_msix_package(); + } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + NewPlusRuntimeRegistration::EnsureRegisteredWin10(); +#endif + } powertoy_new_enabled = true; } @@ -141,6 +150,13 @@ private: { Trace::EventToggleOnOff(false); } + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + NewPlusRuntimeRegistration::Unregister(); + Logger::info(L"New+ context menu unregistered (Win10)"); +#endif + } powertoy_new_enabled = false; } diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj index c76a8c6cc2..51d3bc5522 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj @@ -100,6 +100,7 @@ + diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters index 8ec4d09d40..5d58153793 100644 --- a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters @@ -54,6 +54,9 @@ Generated Files + + Header Files + diff --git a/src/modules/imageresizer/dll/RuntimeRegistration.h b/src/modules/imageresizer/dll/RuntimeRegistration.h new file mode 100644 index 0000000000..f9369da1c4 --- /dev/null +++ b/src/modules/imageresizer/dll/RuntimeRegistration.h @@ -0,0 +1,38 @@ +// Header-only runtime registration for ImageResizer shell extension. +#pragma once + +#include + +extern "C" IMAGE_DOS_HEADER __ImageBase; // provided by linker + +namespace ImageResizerRuntimeRegistration +{ + namespace + { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\ImageResizer"; + spec.sentinelValue = L"ContextMenuRegistered"; + spec.dllFileCandidates = { L"PowerToys.ImageResizerExt.dll" }; + spec.contextMenuHandlerKeyPaths = { }; + spec.systemFileAssocHandlerName = L"ImageResizer"; + spec.systemFileAssocExtensions = { L".bmp", L".dib", L".gif", L".jfif", L".jpe", L".jpeg", L".jpg", L".jxr", L".png", L".rle", L".tif", L".tiff", L".wdp" }; + spec.representativeSystemExt = L".png"; // probe for repair + spec.extraAssociationPaths = { L"Software\\Classes\\Directory\\ShellEx\\DragDropHandlers\\ImageResizer" }; + spec.friendlyName = L"ImageResizer Shell Extension"; + return spec; + } + } + + inline bool EnsureRegistered() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), reinterpret_cast(&__ImageBase)); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/imageresizer/dll/dllmain.cpp b/src/modules/imageresizer/dll/dllmain.cpp index 4786388f06..ef07566b7e 100644 --- a/src/modules/imageresizer/dll/dllmain.cpp +++ b/src/modules/imageresizer/dll/dllmain.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "RuntimeRegistration.h" CImageResizerExtModule _AtlModule; HINSTANCE g_hInst_imageResizer = 0; @@ -106,12 +107,17 @@ public: { std::wstring path = get_module_folderpath(g_hInst_imageResizer); std::wstring packageUri = path + L"\\ImageResizerContextMenuPackage.msix"; - if (!package::IsPackageRegisteredWithPowerToysVersion(ImageResizerConstants::ModulePackageDisplayName)) { package::RegisterSparsePackage(path, packageUri); } } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + ImageResizerRuntimeRegistration::EnsureRegistered(); +#endif + } Trace::EnableImageResizer(m_enabled); } @@ -121,6 +127,13 @@ public: { m_enabled = false; Trace::EnableImageResizer(m_enabled); + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + ImageResizerRuntimeRegistration::Unregister(); + Logger::info(L"ImageResizer context menu unregistered (Win10)"); +#endif + } } // Returns if the powertoys is enabled diff --git a/src/modules/powerrename/dll/PowerRenameExt.vcxproj b/src/modules/powerrename/dll/PowerRenameExt.vcxproj index 32484c75b6..9c612a08ff 100644 --- a/src/modules/powerrename/dll/PowerRenameExt.vcxproj +++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj @@ -34,6 +34,7 @@ + diff --git a/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters b/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters index ffd2177829..f4f0ffcbe5 100644 --- a/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters +++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj.filters @@ -36,6 +36,9 @@ Header Files + + Header Files + diff --git a/src/modules/powerrename/dll/RuntimeRegistration.h b/src/modules/powerrename/dll/RuntimeRegistration.h new file mode 100644 index 0000000000..3cb06d5876 --- /dev/null +++ b/src/modules/powerrename/dll/RuntimeRegistration.h @@ -0,0 +1,37 @@ +// Header-only runtime registration for PowerRename context menu extension. +#pragma once + +#include + +// Provided by dllmain.cpp +extern HINSTANCE g_hInst; + +namespace PowerRenameRuntimeRegistration +{ + namespace + { + inline runtime_shell_ext::Spec BuildSpec() + { + runtime_shell_ext::Spec spec; + spec.clsid = L"{0440049F-D1DC-4E46-B27B-98393D79486B}"; + spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\PowerRename"; + spec.sentinelValue = L"ContextMenuRegistered"; + spec.dllFileCandidates = { L"PowerToys.PowerRenameExt.dll" }; + spec.contextMenuHandlerKeyPaths = { + L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\PowerRenameExt", + L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\PowerRenameExt" }; + spec.friendlyName = L"PowerRename Shell Extension"; + return spec; + } + } + + inline bool EnsureRegistered() + { + return runtime_shell_ext::EnsureRegistered(BuildSpec(), g_hInst); + } + + inline void Unregister() + { + runtime_shell_ext::Unregister(BuildSpec()); + } +} diff --git a/src/modules/powerrename/dll/dllmain.cpp b/src/modules/powerrename/dll/dllmain.cpp index 4f9c918fb6..18c612a304 100644 --- a/src/modules/powerrename/dll/dllmain.cpp +++ b/src/modules/powerrename/dll/dllmain.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "RuntimeRegistration.h" #include @@ -196,12 +197,17 @@ public: { std::wstring path = get_module_folderpath(g_hInst); std::wstring packageUri = path + L"\\PowerRenameContextMenuPackage.msix"; - if (!package::IsPackageRegisteredWithPowerToysVersion(PowerRenameConstants::ModulePackageDisplayName)) { package::RegisterSparsePackage(path, packageUri); } } + else + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + PowerRenameRuntimeRegistration::EnsureRegistered(); +#endif + } } // Disable the powertoy @@ -209,6 +215,13 @@ public: { m_enabled = false; Logger::info(L"PowerRename disabled"); + if (!package::IsWin11OrGreater()) + { +#if defined(ENABLE_REGISTRATION) || defined(NDEBUG) + PowerRenameRuntimeRegistration::Unregister(); + Logger::info(L"PowerRename context menu unregistered (Win10)"); +#endif + } } // Returns if the powertoy is enabled