mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-22 01:58:04 +00:00
<!-- 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 ## 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. <!-- Please review the items on the PR checklist before submitting--> ## 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 <!-- 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 ## 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>
347 lines
9.5 KiB
C++
347 lines
9.5 KiB
C++
#include "pch.h"
|
|
|
|
#include "Generated Files/resource.h"
|
|
#include "PowerRenameConstants.h"
|
|
#include "PowerRenameExt.h"
|
|
|
|
#include <interface/powertoy_module_interface.h>
|
|
#include <settings.h>
|
|
#include <trace.h>
|
|
#include <VersionHelpers.h>
|
|
|
|
#include <common/SettingsAPI/settings_objects.h>
|
|
#include <common/logger/logger.h>
|
|
#include <common/utils/logger_helper.h>
|
|
#include <common/utils/package.h>
|
|
#include <common/utils/process_path.h>
|
|
#include <common/utils/resources.h>
|
|
#include "RuntimeRegistration.h"
|
|
|
|
#include <atomic>
|
|
|
|
std::atomic<DWORD> g_dwModuleRefCount = 0;
|
|
HINSTANCE g_hInst = 0;
|
|
|
|
class CPowerRenameClassFactory : public IClassFactory
|
|
{
|
|
public:
|
|
CPowerRenameClassFactory(_In_ REFCLSID clsid) :
|
|
m_refCount(1),
|
|
m_clsid(clsid)
|
|
{
|
|
ModuleAddRef();
|
|
}
|
|
|
|
// IUnknown methods
|
|
IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppv)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CPowerRenameClassFactory, IClassFactory),
|
|
{ 0 }
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
IFACEMETHODIMP_(ULONG)
|
|
AddRef()
|
|
{
|
|
return ++m_refCount;
|
|
}
|
|
|
|
IFACEMETHODIMP_(ULONG)
|
|
Release()
|
|
{
|
|
LONG refCount = --m_refCount;
|
|
if (refCount == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
return refCount;
|
|
}
|
|
|
|
// IClassFactory methods
|
|
IFACEMETHODIMP CreateInstance(_In_opt_ IUnknown* punkOuter, _In_ REFIID riid, _Outptr_ void** ppv)
|
|
{
|
|
*ppv = NULL;
|
|
HRESULT hr;
|
|
if (punkOuter)
|
|
{
|
|
hr = CLASS_E_NOAGGREGATION;
|
|
}
|
|
else if (m_clsid == CLSID_PowerRenameMenu)
|
|
{
|
|
hr = CPowerRenameMenu::s_CreateInstance(punkOuter, riid, ppv);
|
|
}
|
|
else
|
|
{
|
|
hr = CLASS_E_CLASSNOTAVAILABLE;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
IFACEMETHODIMP LockServer(BOOL bLock)
|
|
{
|
|
if (bLock)
|
|
{
|
|
ModuleAddRef();
|
|
}
|
|
else
|
|
{
|
|
ModuleRelease();
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
private:
|
|
~CPowerRenameClassFactory()
|
|
{
|
|
ModuleRelease();
|
|
}
|
|
|
|
std::atomic<long> m_refCount;
|
|
CLSID m_clsid;
|
|
};
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, void*)
|
|
{
|
|
switch (dwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
g_hInst = hInstance;
|
|
Trace::RegisterProvider();
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
Trace::UnregisterProvider();
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Checks if there are any external references to this module
|
|
//
|
|
STDAPI DllCanUnloadNow(void)
|
|
{
|
|
return (g_dwModuleRefCount == 0) ? S_OK : S_FALSE;
|
|
}
|
|
|
|
//
|
|
// DLL export for creating COM objects
|
|
//
|
|
STDAPI DllGetClassObject(_In_ REFCLSID clsid, _In_ REFIID riid, _Outptr_ void** ppv)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
*ppv = NULL;
|
|
CPowerRenameClassFactory* pClassFactory = new CPowerRenameClassFactory(clsid);
|
|
hr = pClassFactory->QueryInterface(riid, ppv);
|
|
pClassFactory->Release();
|
|
return hr;
|
|
}
|
|
|
|
STDAPI DllRegisterServer()
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
STDAPI DllUnregisterServer()
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
void ModuleAddRef()
|
|
{
|
|
g_dwModuleRefCount++;
|
|
}
|
|
|
|
void ModuleRelease()
|
|
{
|
|
g_dwModuleRefCount--;
|
|
}
|
|
|
|
class PowerRenameModule : public PowertoyModuleIface
|
|
{
|
|
private:
|
|
// Enabled by default
|
|
bool m_enabled = false;
|
|
std::wstring app_name;
|
|
//contains the non localized key of the powertoy
|
|
std::wstring app_key;
|
|
|
|
public:
|
|
// Return the localized display name of the powertoy
|
|
virtual PCWSTR get_name() override
|
|
{
|
|
return app_name.c_str();
|
|
}
|
|
|
|
// Return the non localized key of the powertoy, this will be cached by the runner
|
|
virtual const wchar_t* get_key() override
|
|
{
|
|
return app_key.c_str();
|
|
}
|
|
|
|
// Return the configured status for the gpo policy for the module
|
|
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
|
|
{
|
|
return powertoys_gpo::getConfiguredPowerRenameEnabledValue();
|
|
}
|
|
|
|
// Enable the powertoy
|
|
virtual void enable()
|
|
{
|
|
Logger::info(L"PowerRename enabled");
|
|
m_enabled = true;
|
|
|
|
if (package::IsWin11OrGreater())
|
|
{
|
|
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
|
|
virtual void disable()
|
|
{
|
|
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
|
|
virtual bool is_enabled() override
|
|
{
|
|
return m_enabled;
|
|
}
|
|
|
|
// Return JSON with the configuration options.
|
|
// These are the settings shown on the settings page along with their current values.
|
|
virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override
|
|
{
|
|
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
|
|
|
// Create a Settings object.
|
|
PowerToysSettings::Settings settings(hinstance, get_name());
|
|
settings.set_description(GET_RESOURCE_STRING(IDS_SETTINGS_DESCRIPTION));
|
|
settings.set_icon_key(L"pt-power-rename");
|
|
|
|
// Link to the GitHub PowerRename sub-page
|
|
settings.set_overview_link(GET_RESOURCE_STRING(IDS_OVERVIEW_LINK));
|
|
|
|
settings.add_bool_toggle(
|
|
L"bool_persist_input",
|
|
GET_RESOURCE_STRING(IDS_RESTORE_SEARCH),
|
|
CSettingsInstance().GetPersistState());
|
|
|
|
settings.add_bool_toggle(
|
|
L"bool_mru_enabled",
|
|
GET_RESOURCE_STRING(IDS_ENABLE_AUTO),
|
|
CSettingsInstance().GetMRUEnabled());
|
|
|
|
settings.add_int_spinner(
|
|
L"int_max_mru_size",
|
|
GET_RESOURCE_STRING(IDS_MAX_ITEMS),
|
|
CSettingsInstance().GetMaxMRUSize(),
|
|
0,
|
|
20,
|
|
1);
|
|
|
|
settings.add_bool_toggle(
|
|
L"bool_show_icon_on_menu",
|
|
GET_RESOURCE_STRING(IDS_ICON_CONTEXT_MENU),
|
|
CSettingsInstance().GetShowIconOnMenu());
|
|
|
|
settings.add_bool_toggle(
|
|
L"bool_show_extended_menu",
|
|
GET_RESOURCE_STRING(IDS_EXTENDED_MENU_INFO),
|
|
CSettingsInstance().GetExtendedContextMenuOnly());
|
|
|
|
settings.add_bool_toggle(
|
|
L"bool_use_boost_lib",
|
|
GET_RESOURCE_STRING(IDS_USE_BOOST_LIB),
|
|
CSettingsInstance().GetUseBoostLib());
|
|
|
|
return settings.serialize_to_buffer(buffer, buffer_size);
|
|
}
|
|
|
|
// Passes JSON with the configuration settings for the powertoy.
|
|
// This is called when the user hits Save on the settings page.
|
|
virtual void set_config(PCWSTR config) override
|
|
{
|
|
try
|
|
{
|
|
// Parse the input JSON string.
|
|
PowerToysSettings::PowerToyValues values =
|
|
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
|
|
|
CSettingsInstance().SetPersistState(values.get_bool_value(L"bool_persist_input").value());
|
|
CSettingsInstance().SetMRUEnabled(values.get_bool_value(L"bool_mru_enabled").value());
|
|
CSettingsInstance().SetMaxMRUSize(values.get_int_value(L"int_max_mru_size").value());
|
|
CSettingsInstance().SetShowIconOnMenu(values.get_bool_value(L"bool_show_icon_on_menu").value());
|
|
CSettingsInstance().SetExtendedContextMenuOnly(values.get_bool_value(L"bool_show_extended_menu").value());
|
|
CSettingsInstance().SetUseBoostLib(values.get_bool_value(L"bool_use_boost_lib").value());
|
|
CSettingsInstance().Save();
|
|
|
|
Trace::SettingsChanged();
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
Logger::error("Configuration parsing failed: {}", std::string{ e.what() });
|
|
}
|
|
}
|
|
|
|
// Signal from the Settings editor to call a custom action.
|
|
// This can be used to spawn more complex editors.
|
|
virtual void call_custom_action(const wchar_t* /*action*/) override
|
|
{
|
|
}
|
|
|
|
// Destroy the powertoy and free memory
|
|
virtual void destroy() override
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
void init_settings()
|
|
{
|
|
m_enabled = CSettingsInstance().GetEnabled();
|
|
Trace::EnablePowerRename(m_enabled);
|
|
}
|
|
|
|
void save_settings()
|
|
{
|
|
Trace::EnablePowerRename(m_enabled);
|
|
}
|
|
|
|
PowerRenameModule()
|
|
{
|
|
init_settings();
|
|
app_name = GET_RESOURCE_STRING(IDS_POWERRENAME_APP_NAME);
|
|
app_key = PowerRenameConstants::ModuleKey;
|
|
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::powerRenameLoggerName);
|
|
}
|
|
|
|
~PowerRenameModule(){};
|
|
};
|
|
|
|
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
|
{
|
|
return new PowerRenameModule();
|
|
}
|