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