Compare commits

...

1 Commits

Author SHA1 Message Date
vanzue
e52f85faec Tidy up install logic to block cases that may introduce problems 2026-02-10 17:30:45 +08:00
3 changed files with 197 additions and 0 deletions

View File

@@ -5,6 +5,8 @@
#include <spdlog/sinks/base_sink.h>
#include <filesystem>
#include <string_view>
#include <tuple>
#include <vector>
#include "../../src/common/logger/logger.h"
#include "../../src/common/utils/gpo.h"
@@ -57,6 +59,135 @@ constexpr inline const wchar_t *DataDiagnosticsRegValueName = L"AllowDataDiagnos
static Shared::Trace::ETWTrace trace{L"PowerToys_Installer"};
namespace
{
struct VersionQuad
{
uint16_t major = 0;
uint16_t minor = 0;
uint16_t patch = 0;
uint16_t revision = 0;
bool operator>(const VersionQuad& other) const
{
return std::tie(major, minor, patch, revision) > std::tie(other.major, other.minor, other.patch, other.revision);
}
};
std::wstring VersionToWString(const VersionQuad& v)
{
return std::to_wstring(v.major) + L"." + std::to_wstring(v.minor) + L"." + std::to_wstring(v.patch) + L"." + std::to_wstring(v.revision);
}
bool TryGetFileVersion(const std::wstring& filePath, VersionQuad& version)
{
DWORD dummyHandle = 0;
DWORD verSize = GetFileVersionInfoSizeW(filePath.c_str(), &dummyHandle);
if (verSize == 0)
{
return false;
}
std::vector<BYTE> verData(verSize);
if (!GetFileVersionInfoW(filePath.c_str(), 0, verSize, verData.data()))
{
return false;
}
VS_FIXEDFILEINFO* verInfo = nullptr;
UINT verInfoSize = 0;
if (!VerQueryValueW(verData.data(), L"\\", reinterpret_cast<LPVOID*>(&verInfo), &verInfoSize) || verInfo == nullptr)
{
return false;
}
version.major = HIWORD(verInfo->dwFileVersionMS);
version.minor = LOWORD(verInfo->dwFileVersionMS);
version.patch = HIWORD(verInfo->dwFileVersionLS);
version.revision = LOWORD(verInfo->dwFileVersionLS);
return true;
}
bool IsPowerToysPerUserProduct(const wchar_t* productCode, const wchar_t* userSid, MSIINSTALLCONTEXT context)
{
if ((context != MSIINSTALLCONTEXT_USERMANAGED) && (context != MSIINSTALLCONTEXT_USERUNMANAGED))
{
return false;
}
wchar_t componentPath[MAX_PATH]{};
DWORD pathLength = MAX_PATH;
INSTALLSTATE state = MsiGetComponentPathExW(productCode, POWERTOYS_EXE_COMPONENT, userSid, context, componentPath, &pathLength);
return state == INSTALLSTATE_LOCAL || state == INSTALLSTATE_SOURCE || state == INSTALLSTATE_DEFAULT;
}
bool IsAnyPowerToysPerUserInstallPresent()
{
static constexpr wchar_t sidAllUsers[] = L"S-1-1-0";
const DWORD contexts = MSIINSTALLCONTEXT_USERMANAGED | MSIINSTALLCONTEXT_USERUNMANAGED;
for (DWORD index = 0;; ++index)
{
WCHAR productCode[39]{};
WCHAR sidBuffer[256]{};
DWORD sidLength = static_cast<DWORD>(std::size(sidBuffer));
MSIINSTALLCONTEXT installedContext = MSIINSTALLCONTEXT_NONE;
UINT enumResult = MsiEnumProductsExW(
nullptr,
sidAllUsers,
contexts,
index,
productCode,
&installedContext,
sidBuffer,
&sidLength);
if (enumResult == ERROR_NO_MORE_ITEMS)
{
break;
}
if (enumResult != ERROR_SUCCESS && enumResult != ERROR_MORE_DATA)
{
continue;
}
std::wstring dynamicSid;
const wchar_t* sidPtr = sidBuffer[0] ? sidBuffer : nullptr;
if (enumResult == ERROR_MORE_DATA)
{
dynamicSid.resize(sidLength + 1);
DWORD retrySidLength = static_cast<DWORD>(dynamicSid.size());
enumResult = MsiEnumProductsExW(
nullptr,
sidAllUsers,
contexts,
index,
productCode,
&installedContext,
dynamicSid.data(),
&retrySidLength);
if (enumResult != ERROR_SUCCESS)
{
continue;
}
dynamicSid.resize(retrySidLength);
sidPtr = dynamicSid.empty() || dynamicSid[0] == L'\0' ? nullptr : dynamicSid.c_str();
}
if (IsPowerToysPerUserProduct(productCode, sidPtr, installedContext))
{
return true;
}
}
return false;
}
}
inline bool isDataDiagnosticEnabled()
{
HKEY key{};
@@ -337,6 +468,69 @@ LExit:
return WcaFinalize(er);
}
UINT __stdcall CheckInstallGuardsCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
LPWSTR currentScope = nullptr;
LPWSTR installFolder = nullptr;
hr = WcaInitialize(hInstall, "CheckInstallGuardsCA");
ExitOnFailure(hr, "Failed to initialize");
hr = WcaGetProperty(L"InstallScope", &currentScope);
ExitOnFailure(hr, "Failed to get InstallScope property");
if (currentScope != nullptr && std::wstring{ currentScope } == L"perMachine" && IsAnyPowerToysPerUserInstallPresent())
{
PMSIHANDLE hRecord = MsiCreateRecord(0);
MsiRecordSetStringW(hRecord, 0, L"PowerToys is already installed per-user for at least one account. Please uninstall all per-user PowerToys installations before installing machine-wide.");
MsiProcessMessage(hInstall, static_cast<INSTALLMESSAGE>(INSTALLMESSAGE_ERROR + MB_OK), hRecord);
hr = E_ABORT;
ExitOnFailure(hr, "Per-user installation detected while attempting machine-wide install");
}
hr = WcaGetProperty(L"INSTALLFOLDER", &installFolder);
ExitOnFailure(hr, "Failed to get INSTALLFOLDER property");
if (installFolder && *installFolder != L'\0')
{
const auto installedExePath = std::filesystem::path(installFolder) / L"PowerToys.exe";
if (std::filesystem::exists(installedExePath))
{
VersionQuad existingVersion;
if (TryGetFileVersion(installedExePath.wstring(), existingVersion))
{
const VersionQuad targetVersion{
static_cast<uint16_t>(VERSION_MAJOR),
static_cast<uint16_t>(VERSION_MINOR),
static_cast<uint16_t>(VERSION_REVISION),
0
};
if (existingVersion > targetVersion)
{
const auto existingVersionText = VersionToWString(existingVersion);
const auto targetVersionText = VersionToWString(targetVersion);
const auto message = L"A newer PowerToys version (" + existingVersionText + L") already exists in the installation folder. The requested installer version (" + targetVersionText + L") is older. Uninstall the newer version first.";
PMSIHANDLE hRecord = MsiCreateRecord(0);
MsiRecordSetStringW(hRecord, 0, message.c_str());
MsiProcessMessage(hInstall, static_cast<INSTALLMESSAGE>(INSTALLMESSAGE_ERROR + MB_OK), hRecord);
hr = E_ABORT;
ExitOnFailure(hr, "A higher PowerToys.exe version already exists");
}
}
}
}
LExit:
ReleaseStr(currentScope);
ReleaseStr(installFolder);
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
// We've deprecated Video Conference Mute. This Custom Action cleans up any stray registry entry for the driver dll.
UINT __stdcall CleanVideoConferenceRegistryCA(MSIHANDLE hInstall)
{

View File

@@ -3,6 +3,7 @@ LIBRARY "PowerToysSetupCustomActionsVNext"
EXPORTS
LaunchPowerToysCA
CheckGPOCA
CheckInstallGuardsCA
CleanVideoConferenceRegistryCA
ApplyModulesRegistryChangeSetsCA
DetectPrevInstallPathCA

View File

@@ -121,6 +121,7 @@
<Custom Action="SetUnApplyModulesRegistryChangeSetsParam" Before="UnApplyModulesRegistryChangeSets" />
<Custom Action="CheckGPO" After="InstallInitialize" Condition="NOT Installed" />
<Custom Action="CheckInstallGuards" After="CheckGPO" Condition="NOT Installed" />
<Custom Action="SetBundleInstallLocationData" Before="SetBundleInstallLocation" Condition="NOT Installed OR WIX_UPGRADE_DETECTED" />
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed OR WIX_UPGRADE_DETECTED" />
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
@@ -258,6 +259,7 @@
<CustomAction Id="UnRegisterCmdPalPackage" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="UnRegisterCmdPalPackageCA" BinaryRef="PTCustomActions" />
<CustomAction Id="CheckGPO" Return="check" Impersonate="yes" DllEntry="CheckGPOCA" BinaryRef="PTCustomActions" />
<CustomAction Id="CheckInstallGuards" Return="check" Impersonate="yes" DllEntry="CheckInstallGuardsCA" BinaryRef="PTCustomActions" />
<CustomAction Id="InstallCmdPalPackage" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="InstallCmdPalPackageCA" BinaryRef="PTCustomActions" />