mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
Improve install scope detection to prevent mixed user/machine installations (#43931)
<!-- 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 The old implementation checked `HKLM\Software\Classes\powertoys\InstallScope` first. If this key existed (even as a remnant from incomplete uninstall), it would immediately return `PerMachine` without validating the actual installation. ### Fix - Uses Windows standard Uninstall registry (most reliable source of truth) - Identifies PowerToys Bundle by exact `BundleUpgradeCode` GUID match - MSI component entries (always in HKLM) are automatically ignored since they don't have `BundleUpgradeCode` - Checks HKCU first, then HKLM, properly handling the fact that Bundle location reflects true install scope <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #43696 <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **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
This commit is contained in:
@@ -16,9 +16,54 @@
|
|||||||
|
|
||||||
namespace registry
|
namespace registry
|
||||||
{
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
struct on_exit
|
||||||
|
{
|
||||||
|
std::function<void()> f;
|
||||||
|
|
||||||
|
on_exit(std::function<void()> f) :
|
||||||
|
f{ std::move(f) } {}
|
||||||
|
~on_exit() { f(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Ts>
|
||||||
|
struct overloaded : Ts...
|
||||||
|
{
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Ts>
|
||||||
|
overloaded(Ts...) -> overloaded<Ts...>;
|
||||||
|
|
||||||
|
inline const wchar_t* getScopeName(HKEY scope)
|
||||||
|
{
|
||||||
|
if (scope == HKEY_LOCAL_MACHINE)
|
||||||
|
{
|
||||||
|
return L"HKLM";
|
||||||
|
}
|
||||||
|
else if (scope == HKEY_CURRENT_USER)
|
||||||
|
{
|
||||||
|
return L"HKCU";
|
||||||
|
}
|
||||||
|
else if (scope == HKEY_CLASSES_ROOT)
|
||||||
|
{
|
||||||
|
return L"HKCR";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return L"HK??";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace install_scope
|
namespace install_scope
|
||||||
{
|
{
|
||||||
const wchar_t INSTALL_SCOPE_REG_KEY[] = L"Software\\Classes\\powertoys\\";
|
const wchar_t INSTALL_SCOPE_REG_KEY[] = L"Software\\Classes\\powertoys\\";
|
||||||
|
const wchar_t UNINSTALL_REG_KEY[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
|
||||||
|
|
||||||
|
// Bundle UpgradeCode from PowerToys.wxs (with braces as stored in registry)
|
||||||
|
const wchar_t BUNDLE_UPGRADE_CODE[] = L"{6341382D-C0A9-4238-9188-BE9607E3FAB2}";
|
||||||
|
|
||||||
enum class InstallScope
|
enum class InstallScope
|
||||||
{
|
{
|
||||||
@@ -26,8 +71,67 @@ namespace registry
|
|||||||
PerUser,
|
PerUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to find PowerToys bundle in Windows Uninstall registry by BundleUpgradeCode
|
||||||
|
inline bool find_powertoys_bundle_in_uninstall_registry(HKEY rootKey)
|
||||||
|
{
|
||||||
|
HKEY uninstallKey{};
|
||||||
|
if (RegOpenKeyExW(rootKey, UNINSTALL_REG_KEY, 0, KEY_READ, &uninstallKey) != ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
detail::on_exit closeUninstallKey{ [uninstallKey] { RegCloseKey(uninstallKey); } };
|
||||||
|
|
||||||
|
DWORD index = 0;
|
||||||
|
wchar_t subKeyName[256];
|
||||||
|
|
||||||
|
// Enumerate all subkeys under Uninstall
|
||||||
|
while (RegEnumKeyW(uninstallKey, index++, subKeyName, 256) == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
HKEY productKey{};
|
||||||
|
if (RegOpenKeyExW(uninstallKey, subKeyName, 0, KEY_READ, &productKey) != ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
detail::on_exit closeProductKey{ [productKey] { RegCloseKey(productKey); } };
|
||||||
|
|
||||||
|
// Check BundleUpgradeCode value (specific to WiX Bundle installations)
|
||||||
|
wchar_t bundleUpgradeCode[256]{};
|
||||||
|
DWORD bundleUpgradeCodeSize = sizeof(bundleUpgradeCode);
|
||||||
|
|
||||||
|
if (RegQueryValueExW(productKey, L"BundleUpgradeCode", nullptr, nullptr,
|
||||||
|
reinterpret_cast<LPBYTE>(bundleUpgradeCode), &bundleUpgradeCodeSize) == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
if (_wcsicmp(bundleUpgradeCode, BUNDLE_UPGRADE_CODE) == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
inline const InstallScope get_current_install_scope()
|
inline const InstallScope get_current_install_scope()
|
||||||
{
|
{
|
||||||
|
// 1. Check HKCU Uninstall registry first (user-level bundle)
|
||||||
|
// Note: MSI components are always in HKLM regardless of install scope,
|
||||||
|
// but the Bundle entry will be in HKCU for per-user installations
|
||||||
|
if (find_powertoys_bundle_in_uninstall_registry(HKEY_CURRENT_USER))
|
||||||
|
{
|
||||||
|
Logger::info(L"Found user-level PowerToys bundle via BundleUpgradeCode in HKCU");
|
||||||
|
return InstallScope::PerUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check HKLM Uninstall registry (machine-level bundle)
|
||||||
|
if (find_powertoys_bundle_in_uninstall_registry(HKEY_LOCAL_MACHINE))
|
||||||
|
{
|
||||||
|
Logger::info(L"Found machine-level PowerToys bundle via BundleUpgradeCode in HKLM");
|
||||||
|
return InstallScope::PerMachine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fallback to legacy custom registry key detection
|
||||||
|
Logger::info(L"PowerToys bundle not found in Uninstall registry, falling back to legacy detection");
|
||||||
|
|
||||||
// Open HKLM key
|
// Open HKLM key
|
||||||
HKEY perMachineKey{};
|
HKEY perMachineKey{};
|
||||||
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||||
@@ -45,6 +149,7 @@ namespace registry
|
|||||||
&perUserKey) != ERROR_SUCCESS)
|
&perUserKey) != ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
// both keys are missing
|
// both keys are missing
|
||||||
|
Logger::warn(L"No PowerToys installation detected, defaulting to PerMachine");
|
||||||
return InstallScope::PerMachine;
|
return InstallScope::PerMachine;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -96,47 +201,6 @@ namespace registry
|
|||||||
template<class>
|
template<class>
|
||||||
inline constexpr bool always_false_v = false;
|
inline constexpr bool always_false_v = false;
|
||||||
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
struct on_exit
|
|
||||||
{
|
|
||||||
std::function<void()> f;
|
|
||||||
|
|
||||||
on_exit(std::function<void()> f) :
|
|
||||||
f{ std::move(f) } {}
|
|
||||||
~on_exit() { f(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class... Ts>
|
|
||||||
struct overloaded : Ts...
|
|
||||||
{
|
|
||||||
using Ts::operator()...;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class... Ts>
|
|
||||||
overloaded(Ts...) -> overloaded<Ts...>;
|
|
||||||
|
|
||||||
inline const wchar_t* getScopeName(HKEY scope)
|
|
||||||
{
|
|
||||||
if (scope == HKEY_LOCAL_MACHINE)
|
|
||||||
{
|
|
||||||
return L"HKLM";
|
|
||||||
}
|
|
||||||
else if (scope == HKEY_CURRENT_USER)
|
|
||||||
{
|
|
||||||
return L"HKCU";
|
|
||||||
}
|
|
||||||
else if (scope == HKEY_CLASSES_ROOT)
|
|
||||||
{
|
|
||||||
return L"HKCR";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return L"HK??";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ValueChange
|
struct ValueChange
|
||||||
{
|
{
|
||||||
using value_t = std::variant<DWORD, std::wstring>;
|
using value_t = std::variant<DWORD, std::wstring>;
|
||||||
|
|||||||
Reference in New Issue
Block a user