Files
PowerToys/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp
Leilei Zhang be3d296663 fix installer
2025-11-14 15:26:58 +08:00

1798 lines
59 KiB
C++

#include "pch.h"
#include "resource.h"
#include "RcResource.h"
#include <ProjectTelemetry.h>
#include <spdlog/sinks/base_sink.h>
#include <filesystem>
#include <string_view>
#include "../../src/common/logger/logger.h"
#include "../../src/common/utils/gpo.h"
#include "../../src/common/utils/MsiUtils.h"
#include "../../src/common/utils/modulesRegistry.h"
#include "../../src/common/updating/installer.h"
#include "../../src/common/version/version.h"
#include "../../src/common/Telemetry/EtwTrace/EtwTrace.h"
#include "../../src/common/utils/package.h"
#include "../../src/common/utils/clean_video_conference.h"
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.Security.Credentials.h>
#include <wtsapi32.h>
#include <processthreadsapi.h>
#include <UserEnv.h>
#include <winnt.h>
using namespace std;
HINSTANCE DLL_HANDLE = nullptr;
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0'
const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0'
static const wchar_t *POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}";
static const wchar_t *POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
constexpr inline const wchar_t *DataDiagnosticsRegKey = L"Software\\Classes\\PowerToys";
constexpr inline const wchar_t *DataDiagnosticsRegValueName = L"AllowDataDiagnostics";
#define TraceLoggingWriteWrapper(provider, eventName, ...) \
if (isDataDiagnosticEnabled()) \
{ \
trace.UpdateState(true); \
TraceLoggingWrite(provider, eventName, __VA_ARGS__); \
trace.Flush(); \
trace.UpdateState(false); \
}
static Shared::Trace::ETWTrace trace{L"PowerToys_Installer"};
inline bool isDataDiagnosticEnabled()
{
HKEY key{};
if (RegOpenKeyExW(HKEY_CURRENT_USER,
DataDiagnosticsRegKey,
0,
KEY_READ,
&key) != ERROR_SUCCESS)
{
return false;
}
DWORD isDataDiagnosticsEnabled = 0;
DWORD size = sizeof(isDataDiagnosticsEnabled);
if (RegGetValueW(
HKEY_CURRENT_USER,
DataDiagnosticsRegKey,
DataDiagnosticsRegValueName,
RRF_RT_REG_DWORD,
nullptr,
&isDataDiagnosticsEnabled,
&size) != ERROR_SUCCESS)
{
RegCloseKey(key);
return false;
}
RegCloseKey(key);
return isDataDiagnosticsEnabled == 1;
}
HRESULT getInstallFolder(MSIHANDLE hInstall, std::wstring &installationDir)
{
DWORD len = 0;
wchar_t _[1];
MsiGetPropertyW(hInstall, L"CustomActionData", _, &len);
len += 1;
installationDir.resize(len);
HRESULT hr = MsiGetPropertyW(hInstall, L"CustomActionData", installationDir.data(), &len);
if (installationDir.length())
{
installationDir.resize(installationDir.length() - 1);
}
ExitOnFailure(hr, "Failed to get INSTALLFOLDER property.");
LExit:
return hr;
}
BOOL IsLocalSystem()
{
HANDLE hToken;
UCHAR bTokenUser[sizeof(TOKEN_USER) + 8 + 4 * SID_MAX_SUB_AUTHORITIES];
PTOKEN_USER pTokenUser = (PTOKEN_USER)bTokenUser;
ULONG cbTokenUser;
SID_IDENTIFIER_AUTHORITY siaNT = SECURITY_NT_AUTHORITY;
PSID pSystemSid;
BOOL bSystem;
// open process token
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY,
&hToken))
return FALSE;
// retrieve user SID
if (!GetTokenInformation(hToken, TokenUser, pTokenUser,
sizeof(bTokenUser), &cbTokenUser))
{
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
// allocate LocalSystem well-known SID
if (!AllocateAndInitializeSid(&siaNT, 1, SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0, &pSystemSid))
return FALSE;
// compare the user SID from the token with the LocalSystem SID
bSystem = EqualSid(pTokenUser->User.Sid, pSystemSid);
FreeSid(pSystemSid);
return bSystem;
}
BOOL ImpersonateLoggedInUserAndDoSomething(std::function<bool(HANDLE userToken)> action)
{
HRESULT hr = S_OK;
HANDLE hUserToken = NULL;
DWORD dwSessionId;
ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId);
auto rv = WTSQueryUserToken(dwSessionId, &hUserToken);
if (rv == 0)
{
hr = E_ABORT;
ExitOnFailure(hr, "Failed to query user token");
}
HANDLE hUserTokenDup;
if (DuplicateTokenEx(hUserToken, TOKEN_ALL_ACCESS, NULL, SECURITY_IMPERSONATION_LEVEL::SecurityImpersonation, TOKEN_TYPE::TokenPrimary, &hUserTokenDup) == 0)
{
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
hr = E_ABORT;
ExitOnFailure(hr, "Failed to duplicate user token");
}
if (ImpersonateLoggedOnUser(hUserTokenDup))
{
if (!action(hUserTokenDup))
{
hr = E_ABORT;
ExitOnFailure(hr, "Failed to execute action");
}
RevertToSelf();
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
}
else
{
hr = E_ABORT;
ExitOnFailure(hr, "Failed to duplicate user token");
}
LExit:
return SUCCEEDED(hr);
}
static std::filesystem::path GetUserPowerShellModulesPath()
{
PWSTR myDocumentsBlockPtr;
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &myDocumentsBlockPtr)))
{
const std::wstring myDocuments{myDocumentsBlockPtr};
CoTaskMemFree(myDocumentsBlockPtr);
return std::filesystem::path(myDocuments) / "PowerShell" / "Modules";
}
else
{
CoTaskMemFree(myDocumentsBlockPtr);
return {};
}
}
UINT __stdcall LaunchPowerToysCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder, path, args;
std::wstring commandLine;
hr = WcaInitialize(hInstall, "LaunchPowerToys");
ExitOnFailure(hr, "Failed to initialize");
hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get installFolder.");
path = installationFolder;
path += L"\\PowerToys.exe";
args = L"--dont-elevate";
commandLine = L"\"" + path + L"\" ";
commandLine += args;
BOOL isSystemUser = IsLocalSystem();
if (isSystemUser)
{
auto action = [&commandLine](HANDLE userToken)
{
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.wShowWindow = SW_SHOWNORMAL;
PROCESS_INFORMATION processInformation;
PVOID lpEnvironment = NULL;
CreateEnvironmentBlock(&lpEnvironment, userToken, FALSE);
CreateProcessAsUser(
userToken,
NULL,
commandLine.data(),
NULL,
NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment,
NULL,
&startupInfo,
&processInformation);
if (!CloseHandle(processInformation.hProcess))
{
return false;
}
if (!CloseHandle(processInformation.hThread))
{
return false;
}
return true;
};
if (!ImpersonateLoggedInUserAndDoSomething(action))
{
hr = E_ABORT;
ExitOnFailure(hr, "ImpersonateLoggedInUserAndDoSomething failed");
}
}
else
{
STARTUPINFO startupInfo = { 0 };
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.wShowWindow = SW_SHOWNORMAL;
PROCESS_INFORMATION processInformation;
// Start the resizer
CreateProcess(
NULL,
commandLine.data(),
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startupInfo,
&processInformation);
if (!CloseHandle(processInformation.hProcess))
{
hr = E_ABORT;
ExitOnFailure(hr, "Failed to close process handle");
}
if (!CloseHandle(processInformation.hThread))
{
hr = E_ABORT;
ExitOnFailure(hr, "Failed to close thread handle");
}
}
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall CheckGPOCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
hr = WcaInitialize(hInstall, "CheckGPOCA");
ExitOnFailure(hr, "Failed to initialize");
LPWSTR currentScope = nullptr;
hr = WcaGetProperty(L"InstallScope", &currentScope);
if (std::wstring{currentScope} == L"perUser")
{
if (powertoys_gpo::getDisablePerUserInstallationValue() == powertoys_gpo::gpo_rule_configured_enabled)
{
PMSIHANDLE hRecord = MsiCreateRecord(0);
MsiRecordSetString(hRecord, 0, TEXT("The system administrator has disabled per-user installation."));
MsiProcessMessage(hInstall, static_cast<INSTALLMESSAGE>(INSTALLMESSAGE_ERROR + MB_OK), hRecord);
hr = E_ABORT;
}
}
LExit:
UINT 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)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanVideoConferenceRegistry");
ExitOnFailure(hr, "Failed to initialize");
clean_video_conference();
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall ApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
bool failedToApply = false;
hr = WcaInitialize(hInstall, "ApplyModulesRegistryChangeSets");
ExitOnFailure(hr, "Failed to initialize");
hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get installFolder.");
for (const auto &changeSet : getAllOnByDefaultModulesChangeSets(installationFolder))
{
if (!changeSet.apply())
{
Logger::error(L"Couldn't apply registry changeSet");
failedToApply = true;
}
}
if (!failedToApply)
{
Logger::info(L"All registry changeSets applied successfully");
}
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UnApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
hr = WcaInitialize(hInstall, "UndoModulesRegistryChangeSets"); // original func name is too long
ExitOnFailure(hr, "Failed to initialize");
hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get installFolder.");
for (const auto &changeSet : getAllModulesChangeSets(installationFolder))
{
changeSet.unApply();
}
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
ExitOnFailure(hr, "Failed to extract msix");
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
const wchar_t *DSC_CONFIGURE_PSD1_NAME = L"Microsoft.PowerToys.Configure.psd1";
const wchar_t *DSC_CONFIGURE_PSM1_NAME = L"Microsoft.PowerToys.Configure.psm1";
UINT __stdcall InstallDSCModuleCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
hr = WcaInitialize(hInstall, "InstallDSCModuleCA");
ExitOnFailure(hr, "Failed to initialize");
hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get installFolder.");
{
const auto baseModulesPath = GetUserPowerShellModulesPath();
if (baseModulesPath.empty())
{
hr = E_FAIL;
ExitOnFailure(hr, "Unable to determine Powershell modules path");
}
const auto modulesPath = baseModulesPath / L"Microsoft.PowerToys.Configure" / (get_product_version(false) + L".0");
std::error_code errorCode;
std::filesystem::create_directories(modulesPath, errorCode);
if (errorCode)
{
hr = E_FAIL;
ExitOnFailure(hr, "Unable to create Powershell modules folder");
}
for (const auto *filename : {DSC_CONFIGURE_PSD1_NAME, DSC_CONFIGURE_PSM1_NAME})
{
std::filesystem::copy_file(std::filesystem::path(installationFolder) / "DSCModules" / filename, modulesPath / filename, std::filesystem::copy_options::overwrite_existing, errorCode);
if (errorCode)
{
hr = E_FAIL;
ExitOnFailure(hr, "Unable to copy Powershell modules file");
}
}
}
LExit:
if (SUCCEEDED(hr))
{
er = ERROR_SUCCESS;
Logger::info(L"DSC module was installed!");
}
else
{
er = ERROR_INSTALL_FAILURE;
Logger::error(L"Couldn't install DSC module!");
}
return WcaFinalize(er);
}
UINT __stdcall UninstallDSCModuleCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "UninstallDSCModuleCA");
ExitOnFailure(hr, "Failed to initialize");
{
const auto baseModulesPath = GetUserPowerShellModulesPath();
if (baseModulesPath.empty())
{
hr = E_FAIL;
ExitOnFailure(hr, "Unable to determine Powershell modules path");
}
const auto powerToysModulePath = baseModulesPath / L"Microsoft.PowerToys.Configure";
const auto versionedModulePath = powerToysModulePath / (get_product_version(false) + L".0");
std::error_code errorCode;
for (const auto *filename : {DSC_CONFIGURE_PSD1_NAME, DSC_CONFIGURE_PSM1_NAME})
{
std::filesystem::remove(versionedModulePath / filename, errorCode);
if (errorCode)
{
hr = E_FAIL;
ExitOnFailure(hr, "Unable to delete DSC file");
}
}
for (const auto *modulePath : {&versionedModulePath, &powerToysModulePath})
{
std::filesystem::remove(*modulePath, errorCode);
if (errorCode)
{
hr = E_FAIL;
ExitOnFailure(hr, "Unable to delete DSC folder");
}
}
}
LExit:
if (SUCCEEDED(hr))
{
er = ERROR_SUCCESS;
Logger::info(L"DSC module was uninstalled!");
}
else
{
er = ERROR_INSTALL_FAILURE;
Logger::error(L"Couldn't uninstall DSC module!");
}
return WcaFinalize(er);
}
UINT __stdcall InstallEmbeddedMSIXCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "InstallEmbeddedMSIXCA");
ExitOnFailure(hr, "Failed to initialize");
if (auto msix = RcResource::create(IDR_BIN_MSIX_HELLO_PACKAGE, L"BIN", DLL_HANDLE))
{
Logger::info(L"Extracted MSIX");
// TODO: Use to activate embedded MSIX
const auto msix_path = std::filesystem::temp_directory_path() / "hello_package.msix";
if (!msix->saveAsFile(msix_path))
{
ExitOnFailure(hr, "Failed to save msix");
}
Logger::info(L"Saved MSIX");
using namespace winrt::Windows::Management::Deployment;
using namespace winrt::Windows::Foundation;
Uri msix_uri{msix_path.wstring()};
PackageManager pm;
auto result = pm.AddPackageAsync(msix_uri, nullptr, DeploymentOptions::None).get();
if (!result)
{
ExitOnFailure(hr, "Failed to AddPackage");
}
Logger::info(L"MSIX[s] were installed!");
}
else
{
ExitOnFailure(hr, "Failed to extract msix");
}
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UninstallEmbeddedMSIXCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
using namespace winrt::Windows::Management::Deployment;
using namespace winrt::Windows::Foundation;
// TODO: This must be replaced with the actual publisher and package name
const wchar_t package_name[] = L"46b35c25-b593-48d5-aeb1-d3e9c3b796e9";
const wchar_t publisher[] = L"CN=yuyoyuppe";
PackageManager pm;
hr = WcaInitialize(hInstall, "UninstallEmbeddedMSIXCA");
ExitOnFailure(hr, "Failed to initialize");
for (const auto &p : pm.FindPackagesForUser({}, package_name, publisher))
{
auto result = pm.RemovePackageAsync(p.Id().FullName()).get();
if (result)
{
Logger::info(L"MSIX was uninstalled!");
}
else
{
Logger::error(L"Couldn't uninstall MSIX!");
}
}
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
LPWSTR customActionData = nullptr;
std::wstring installFolderPath;
std::wstring installScope;
std::wstring msixPath;
std::wstring data;
size_t delimiterPos;
bool isMachineLevel = false;
hr = WcaInitialize(hInstall, "InstallPackageIdentityMSIXCA");
ExitOnFailure(hr, "Failed to initialize");
hr = WcaGetProperty(L"CustomActionData", &customActionData);
ExitOnFailure(hr, "Failed to get CustomActionData property");
// Parse CustomActionData: "[INSTALLFOLDER];[InstallScope]"
data = customActionData;
delimiterPos = data.find(L';');
installFolderPath = data.substr(0, delimiterPos);
installScope = data.substr(delimiterPos + 1);
// Check if this is a machine-level installation
if (installScope == L"perMachine")
{
isMachineLevel = true;
}
Logger::info(L"Installing PackageIdentity MSIX - perUser: {}", !isMachineLevel);
// Construct path to PackageIdentity MSIX
msixPath = installFolderPath;
msixPath += L"PowerToysSparse.msix";
if (std::filesystem::exists(msixPath))
{
using namespace winrt::Windows::Management::Deployment;
using namespace winrt::Windows::Foundation;
try
{
std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder)
Uri externalUri{ externalLocation }; // External location URI for sparse package content
Uri packageUri{ msixPath }; // The MSIX file URI
PackageManager packageManager;
if (isMachineLevel)
{
// Machine-level installation
StagePackageOptions stageOptions;
stageOptions.ExternalLocationUri(externalUri);
auto stageResult = packageManager.StagePackageByUriAsync(packageUri, stageOptions).get();
uint32_t stageErrorCode = static_cast<uint32_t>(stageResult.ExtendedErrorCode());
if (stageErrorCode == 0)
{
std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe";
try
{
auto provisionResult = packageManager.ProvisionPackageForAllUsersAsync(packageFamilyName).get();
uint32_t provisionErrorCode = static_cast<uint32_t>(provisionResult.ExtendedErrorCode());
if (provisionErrorCode != 0)
{
Logger::error(L"Machine-level provisioning failed: 0x{:08X}", provisionErrorCode);
}
}
catch (const winrt::hresult_error& ex)
{
Logger::error(L"Provisioning exception: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
}
}
else
{
Logger::error(L"Package staging failed: 0x{:08X}", stageErrorCode);
}
}
else
{
AddPackageOptions addOptions;
addOptions.ExternalLocationUri(externalUri);
auto addResult = packageManager.AddPackageByUriAsync(packageUri, addOptions).get();
if (!addResult.IsRegistered())
{
uint32_t errorCode = static_cast<uint32_t>(addResult.ExtendedErrorCode());
Logger::error(L"Per-user installation failed: 0x{:08X}", errorCode);
}
}
}
catch (const std::exception& ex)
{
Logger::error(L"PackageIdentity MSIX installation failed - Exception: {}",
winrt::to_hstring(ex.what()).c_str());
}
}
else
{
Logger::error(L"PackageIdentity MSIX not found: " + msixPath);
}
LExit:
ReleaseStr(customActionData);
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UninstallPackageIdentityMSIXCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
using namespace winrt::Windows::Management::Deployment;
using namespace winrt::Windows::Foundation;
LPWSTR installScope = nullptr;
bool isMachineLevel = false;
PackageManager pm;
hr = WcaInitialize(hInstall, "UninstallPackageIdentityMSIXCA");
ExitOnFailure(hr, "Failed to initialize");
// Check if this was a machine-level installation
hr = WcaGetProperty(L"InstallScope", &installScope);
if (SUCCEEDED(hr) && installScope && wcscmp(installScope, L"perMachine") == 0)
{
isMachineLevel = true;
}
Logger::info(L"Uninstalling PackageIdentity MSIX - perUser: {}", !isMachineLevel);
try
{
std::wstring packageFamilyName = L"Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe";
if (isMachineLevel)
{
// Machine-level uninstallation: deprovision + remove for all users
// First deprovision the package
try
{
auto deprovisionResult = pm.DeprovisionPackageForAllUsersAsync(packageFamilyName).get();
if (deprovisionResult.IsRegistered())
{
Logger::warn(L"Machine-level deprovisioning completed with warnings");
}
}
catch (const winrt::hresult_error& ex)
{
Logger::warn(L"Machine-level deprovisioning failed: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
}
// Then remove packages for all users
auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main);
for (const auto& package : packages)
{
try
{
auto machineResult = pm.RemovePackageAsync(package.Id().FullName(), RemovalOptions::RemoveForAllUsers).get();
if (machineResult.IsRegistered())
{
uint32_t errorCode = static_cast<uint32_t>(machineResult.ExtendedErrorCode());
Logger::error(L"Machine-level removal failed: 0x{:08X} - {}", errorCode, machineResult.ErrorText());
}
}
catch (const winrt::hresult_error& ex)
{
Logger::error(L"Machine-level removal exception: HRESULT 0x{:08X}", static_cast<uint32_t>(ex.code()));
}
}
}
else
{
// Per-user uninstallation: standard removal
auto packages = pm.FindPackagesForUserWithPackageTypes({}, packageFamilyName, PackageTypes::Main);
for (const auto& package : packages)
{
auto userResult = pm.RemovePackageAsync(package.Id().FullName()).get();
if (userResult.IsRegistered())
{
uint32_t errorCode = static_cast<uint32_t>(userResult.ExtendedErrorCode());
Logger::error(L"Per-user removal failed: 0x{:08X} - {}", errorCode, userResult.ErrorText());
}
}
}
}
catch (const std::exception& ex)
{
std::string errorMsg = "Failed to uninstall PackageIdentity MSIX: " + std::string(ex.what());
Logger::error(errorMsg);
// Don't fail the entire uninstallation if PackageIdentity fails
Logger::warn(L"Continuing uninstallation despite PackageIdentity MSIX error");
}
LExit:
ReleaseStr(installScope);
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName)
{
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCManager)
{
return ERROR_INSTALL_FAILURE;
}
SC_HANDLE hService = OpenService(hSCManager, serviceName.c_str(), SERVICE_STOP | DELETE);
if (!hService)
{
CloseServiceHandle(hSCManager);
return ERROR_INSTALL_FAILURE;
}
SERVICE_STATUS ss;
if (ControlService(hService, SERVICE_CONTROL_STOP, &ss))
{
Sleep(1000);
while (QueryServiceStatus(hService, &ss))
{
if (ss.dwCurrentState == SERVICE_STOP_PENDING)
{
Sleep(1000);
}
else
{
break;
}
}
}
BOOL deleteResult = DeleteService(hService);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
if (!deleteResult)
{
return ERROR_INSTALL_FAILURE;
}
return ERROR_SUCCESS;
}
UINT __stdcall UnsetAdvancedPasteAPIKeyCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
try
{
hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey");
ExitOnFailure(hr, "Failed to initialize");
winrt::Windows::Security::Credentials::PasswordVault vault;
auto hasPrefix = [](std::wstring_view value, wchar_t const* prefix) {
std::wstring_view prefixView{ prefix };
return value.compare(0, prefixView.size(), prefixView) == 0;
};
const wchar_t* resourcePrefixes[] = {
L"https://platform.openai.com/api-keys",
L"https://azure.microsoft.com/products/ai-services/openai-service",
L"https://azure.microsoft.com/products/ai-services/ai-inference",
L"https://console.mistral.ai/account/api-keys",
L"https://ai.google.dev/",
};
const wchar_t* usernamePrefixes[] = {
L"PowerToys_AdvancedPaste_",
};
auto credentials = vault.RetrieveAll();
for (auto const& credential : credentials)
{
bool shouldRemove = false;
std::wstring resource{ credential.Resource() };
for (auto const prefix : resourcePrefixes)
{
if (hasPrefix(resource, prefix))
{
shouldRemove = true;
break;
}
}
if (!shouldRemove)
{
std::wstring username{ credential.UserName() };
for (auto const prefix : usernamePrefixes)
{
if (hasPrefix(username, prefix))
{
shouldRemove = true;
break;
}
}
}
if (!shouldRemove)
{
continue;
}
try
{
vault.Remove(credential);
}
catch (...)
{
}
}
}
catch (...)
{
}
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UninstallCommandNotFoundModuleCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
std::string command;
hr = WcaInitialize(hInstall, "UninstallCommandNotFoundModule");
ExitOnFailure(hr, "Failed to initialize");
hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get installFolder.");
#ifdef _M_ARM64
command = "powershell.exe";
command += " ";
command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted";
command += " -Command ";
command += "\"[Environment]::SetEnvironmentVariable('PATH', [Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('PATH', 'User'), 'Process');";
command += "pwsh.exe -NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File '" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "'\"";
#else
command = "pwsh.exe";
command += " ";
command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "\"";
#endif
system(command.c_str());
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UpgradeCommandNotFoundModuleCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
std::string command;
hr = WcaInitialize(hInstall, "UpgradeCommandNotFoundModule");
ExitOnFailure(hr, "Failed to initialize");
hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get installFolder.");
command = "pwsh.exe";
command += " ";
command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\UpgradeModule.ps1" + "\"";
system(command.c_str());
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall UninstallServicesCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "UninstallServicesCA");
ExitOnFailure(hr, "Failed to initialize");
hr = RemoveWindowsServiceByName(L"PowerToys.MWB.Service");
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
// Removes all Scheduled Tasks in the PowerToys folder and deletes the folder afterwards.
// Based on the Task Scheduler Displaying Task Names and State example:
// https://learn.microsoft.com/windows/desktop/TaskSchd/displaying-task-names-and-state--c---/
UINT __stdcall RemoveScheduledTasksCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
ITaskService *pService = nullptr;
ITaskFolder *pTaskFolder = nullptr;
IRegisteredTaskCollection *pTaskCollection = nullptr;
ITaskFolder *pRootFolder = nullptr;
LONG numTasks = 0;
hr = WcaInitialize(hInstall, "RemoveScheduledTasksCA");
ExitOnFailure(hr, "Failed to initialize");
Logger::info(L"RemoveScheduledTasksCA Initialized.");
// COM and Security Initialization is expected to have been done by the MSI.
// It couldn't be done in the DLL, anyway.
// ------------------------------------------------------
// Create an instance of the Task Service.
hr = CoCreateInstance(CLSID_TaskScheduler,
nullptr,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
reinterpret_cast<void **>(&pService));
ExitOnFailure(hr, "Failed to create an instance of ITaskService: %x", hr);
// Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
ExitOnFailure(hr, "ITaskService::Connect failed: %x", hr);
// ------------------------------------------------------
// Get the PowerToys task folder.
hr = pService->GetFolder(_bstr_t(L"\\PowerToys"), &pTaskFolder);
if (FAILED(hr))
{
// Folder doesn't exist. No need to delete anything.
Logger::info(L"The PowerToys scheduled task folder wasn't found. Nothing to delete.");
hr = S_OK;
ExitFunction();
}
// -------------------------------------------------------
// Get the registered tasks in the folder.
hr = pTaskFolder->GetTasks(TASK_ENUM_HIDDEN, &pTaskCollection);
ExitOnFailure(hr, "Cannot get the registered tasks: %x", hr);
hr = pTaskCollection->get_Count(&numTasks);
for (LONG i = 0; i < numTasks; i++)
{
// Delete all the tasks found.
// If some tasks can't be deleted, the folder won't be deleted later and the user will still be notified.
IRegisteredTask *pRegisteredTask = nullptr;
hr = pTaskCollection->get_Item(_variant_t(i + 1), &pRegisteredTask);
if (SUCCEEDED(hr))
{
BSTR taskName = nullptr;
hr = pRegisteredTask->get_Name(&taskName);
if (SUCCEEDED(hr))
{
hr = pTaskFolder->DeleteTask(taskName, 0);
if (FAILED(hr))
{
Logger::error(L"Cannot delete the {} task: {}", taskName, hr);
}
SysFreeString(taskName);
}
else
{
Logger::error(L"Cannot get the registered task name: {}", hr);
}
pRegisteredTask->Release();
}
else
{
Logger::error(L"Cannot get the registered task item at index={}: {}", i + 1, hr);
}
}
// ------------------------------------------------------
// Get the pointer to the root task folder and delete the PowerToys subfolder.
hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
ExitOnFailure(hr, "Cannot get Root Folder pointer: %x", hr);
hr = pRootFolder->DeleteFolder(_bstr_t(L"PowerToys"), 0);
pRootFolder->Release();
ExitOnFailure(hr, "Cannot delete the PowerToys folder: %x", hr);
Logger::info(L"Deleted the PowerToys Task Scheduler folder.");
LExit:
if (pService)
{
pService->Release();
}
if (pTaskFolder)
{
pTaskFolder->Release();
}
if (pTaskCollection)
{
pTaskCollection->Release();
}
if (!SUCCEEDED(hr))
{
PMSIHANDLE hRecord = MsiCreateRecord(0);
MsiRecordSetString(hRecord, 0, TEXT("Failed to remove the PowerToys folder from the scheduled task. These can be removed manually later."));
MsiProcessMessage(hInstall, static_cast<INSTALLMESSAGE>(INSTALLMESSAGE_WARNING + MB_OK), hRecord);
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogInstallSuccessCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogInstallSuccessCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"Install_Success",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogInstallCancelCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogInstallCancelCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"Install_Cancel",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogInstallFailCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogInstallFailCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"Install_Fail",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogUninstallSuccessCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogUninstallSuccessCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"UnInstall_Success",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogUninstallCancelCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogUninstallCancelCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"UnInstall_Cancel",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogUninstallFailCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogUninstallFailCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"UnInstall_Fail",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogRepairCancelCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogRepairCancelCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"Repair_Cancel",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TelemetryLogRepairFailCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TelemetryLogRepairFailCA");
ExitOnFailure(hr, "Failed to initialize");
TraceLoggingWriteWrapper(
g_hProvider,
"Repair_Fail",
TraceLoggingWideString(get_product_version().c_str(), "Version"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "DetectPrevInstallPathCA");
MsiSetPropertyW(hInstall, L"PREVIOUSINSTALLFOLDER", L"");
LPWSTR currentScope = nullptr;
hr = WcaGetProperty(L"InstallScope", &currentScope);
try
{
if (auto install_path = GetMsiPackageInstalledPath(std::wstring{currentScope} == L"perUser"))
{
MsiSetPropertyW(hInstall, L"PREVIOUSINSTALLFOLDER", install_path->data());
}
}
catch (...)
{
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall InstallCmdPalPackageCA(MSIHANDLE hInstall)
{
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Management::Deployment;
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
hr = WcaInitialize(hInstall, "InstallCmdPalPackage");
hr = getInstallFolder(hInstall, installationFolder);
try
{
auto msix = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\", false);
auto dependencies = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\Dependencies\\", true);
if (!msix.empty())
{
auto msixPath = msix[0];
if (!package::RegisterPackage(msixPath, dependencies))
{
Logger::error(L"Failed to install CmdPal package");
er = ERROR_INSTALL_FAILURE;
}
}
}
catch (std::exception &e)
{
std::string errorMessage{"Exception thrown while trying to install CmdPal package: "};
errorMessage += e.what();
Logger::error(errorMessage);
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall UnRegisterCmdPalPackageCA(MSIHANDLE hInstall)
{
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Management::Deployment;
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "UnRegisterCmdPalPackageCA");
try
{
// Packages to unregister
std::wstring packageToRemoveDisplayName {L"Microsoft.CommandPalette"};
if (!package::UnRegisterPackage(packageToRemoveDisplayName))
{
Logger::error(L"Failed to unregister package: " + packageToRemoveDisplayName);
er = ERROR_INSTALL_FAILURE;
}
}
catch (std::exception &e)
{
std::string errorMessage{"Exception thrown while trying to unregister the CmdPal package: "};
errorMessage += e.what();
Logger::error(errorMessage);
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
{
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Management::Deployment;
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "UnRegisterContextMenuPackagesCA"); // original func name is too long
try
{
// Packages to unregister
const std::vector<std::wstring> packagesToRemoveDisplayName{{L"PowerRenameContextMenu"}, {L"ImageResizerContextMenu"}, {L"FileLocksmithContextMenu"}, {L"NewPlusContextMenu"}};
for (auto const &package : packagesToRemoveDisplayName)
{
if (!package::UnRegisterPackage(package))
{
Logger::error(L"Failed to unregister package: " + package);
er = ERROR_INSTALL_FAILURE;
}
}
}
catch (std::exception &e)
{
std::string errorMessage{"Exception thrown while trying to unregister sparse packages: "};
errorMessage += e.what();
Logger::error(errorMessage);
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanImageResizerRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanImageResizerRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{51B4D7E5-7568-4234-B4BB-47FB3C016A69}";
const wchar_t* exts[] = { 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" };
auto deleteKeyRecursive = [](HKEY root, const std::wstring &path) {
RegDeleteTreeW(root, path.c_str());
};
// InprocServer32 chain root CLSID
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
// DragDrop handler
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\ShellEx\\DragDropHandlers\\ImageResizer");
// Extensions
for (auto ext : exts)
{
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\SystemFileAssociations\\" + std::wstring(ext) + L"\\ShellEx\\ContextMenuHandlers\\ImageResizer");
}
// Sentinel
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\ImageResizer");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanFileLocksmithRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanFileLocksmithRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt");
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\FileLocksmith");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanPowerRenameRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanPowerRenameRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{0440049F-D1DC-4E46-B27B-98393D79486B}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\PowerRenameExt");
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\PowerRenameExt");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\PowerRename");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanNewPlusRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanNewPlusRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\NewPlus");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "TerminateProcessesCA");
std::vector<DWORD> processes;
const size_t maxProcesses = 4096;
DWORD bytes = maxProcesses * sizeof(processes[0]);
processes.resize(maxProcesses);
if (!EnumProcesses(processes.data(), bytes, &bytes))
{
return 1;
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 42> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.AdvancedPaste.exe",
L"PowerToys.Awake.exe",
L"PowerToys.FancyZones.exe",
L"PowerToys.FancyZonesEditor.exe",
L"PowerToys.FileLocksmithUI.exe",
L"PowerToys.MouseJumpUI.exe",
L"PowerToys.ColorPickerUI.exe",
L"PowerToys.AlwaysOnTop.exe",
L"PowerToys.RegistryPreview.exe",
L"PowerToys.Hosts.exe",
L"PowerToys.PowerRename.exe",
L"PowerToys.ImageResizer.exe",
L"PowerToys.LightSwitchService.exe",
L"PowerToys.GcodeThumbnailProvider.exe",
L"PowerToys.BgcodeThumbnailProvider.exe",
L"PowerToys.PdfThumbnailProvider.exe",
L"PowerToys.MonacoPreviewHandler.exe",
L"PowerToys.MarkdownPreviewHandler.exe",
L"PowerToys.StlThumbnailProvider.exe",
L"PowerToys.SvgThumbnailProvider.exe",
L"PowerToys.GcodePreviewHandler.exe",
L"PowerToys.BgcodePreviewHandler.exe",
L"PowerToys.QoiPreviewHandler.exe",
L"PowerToys.PdfPreviewHandler.exe",
L"PowerToys.QoiThumbnailProvider.exe",
L"PowerToys.SvgPreviewHandler.exe",
L"PowerToys.Peek.UI.exe",
L"PowerToys.MouseWithoutBorders.exe",
L"PowerToys.MouseWithoutBordersHelper.exe",
L"PowerToys.MouseWithoutBordersService.exe",
L"PowerToys.CropAndLock.exe",
L"PowerToys.EnvironmentVariables.exe",
L"PowerToys.WorkspacesSnapshotTool.exe",
L"PowerToys.WorkspacesLauncher.exe",
L"PowerToys.WorkspacesLauncherUI.exe",
L"PowerToys.WorkspacesEditor.exe",
L"PowerToys.WorkspacesWindowArranger.exe",
L"Microsoft.CmdPal.UI.exe",
L"PowerToys.ZoomIt.exe",
L"PowerToys.exe",
};
for (const auto procID : processes)
{
if (!procID)
{
continue;
}
wchar_t processName[MAX_PATH] = L"<unknown>";
HANDLE hProcess{OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, procID)};
if (!hProcess)
{
continue;
}
HMODULE hMod;
DWORD cbNeeded;
if (!EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
{
CloseHandle(hProcess);
continue;
}
GetModuleBaseNameW(hProcess, hMod, processName, sizeof(processName) / sizeof(wchar_t));
for (const auto processToTerminate : processesToTerminate)
{
if (processName == processToTerminate)
{
const DWORD timeout = 500;
auto windowEnumerator = [](HWND hwnd, LPARAM procIDPtr) -> BOOL
{
auto targetProcID = *reinterpret_cast<const DWORD *>(procIDPtr);
DWORD windowProcID = 0;
GetWindowThreadProcessId(hwnd, &windowProcID);
if (windowProcID == targetProcID)
{
DWORD_PTR _{};
SendMessageTimeoutA(hwnd, WM_CLOSE, 0, 0, SMTO_BLOCK, timeout, &_);
}
return TRUE;
};
EnumWindows(windowEnumerator, reinterpret_cast<LPARAM>(&procID));
Sleep(timeout);
TerminateProcess(hProcess, 0);
break;
}
}
CloseHandle(hProcess);
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall SetBundleInstallLocationCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
// Declare all variables at the beginning to avoid goto issues
std::wstring customActionData;
std::wstring installationFolder;
std::wstring bundleUpgradeCode;
std::wstring installScope;
bool isPerUser = false;
size_t pos1 = std::wstring::npos;
size_t pos2 = std::wstring::npos;
std::vector<HKEY> keysToTry;
hr = WcaInitialize(hInstall, "SetBundleInstallLocationCA");
ExitOnFailure(hr, "Failed to initialize");
// Parse CustomActionData: "installFolder;upgradeCode;installScope"
hr = getInstallFolder(hInstall, customActionData);
ExitOnFailure(hr, "Failed to get CustomActionData.");
pos1 = customActionData.find(L';');
if (pos1 == std::wstring::npos)
{
hr = E_INVALIDARG;
ExitOnFailure(hr, "Invalid CustomActionData format - missing first semicolon");
}
pos2 = customActionData.find(L';', pos1 + 1);
if (pos2 == std::wstring::npos)
{
hr = E_INVALIDARG;
ExitOnFailure(hr, "Invalid CustomActionData format - missing second semicolon");
}
installationFolder = customActionData.substr(0, pos1);
bundleUpgradeCode = customActionData.substr(pos1 + 1, pos2 - pos1 - 1);
installScope = customActionData.substr(pos2 + 1);
isPerUser = (installScope == L"perUser");
// Use the appropriate registry based on install scope
HKEY targetKey = isPerUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
const wchar_t* keyName = isPerUser ? L"HKCU" : L"HKLM";
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: Searching for Bundle in %ls registry", keyName);
HKEY uninstallKey;
LONG openResult = RegOpenKeyExW(targetKey, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", 0, KEY_READ | KEY_ENUMERATE_SUB_KEYS, &uninstallKey);
if (openResult != ERROR_SUCCESS)
{
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: Failed to open uninstall key, error: %ld", openResult);
goto LExit;
}
DWORD index = 0;
wchar_t subKeyName[256];
DWORD subKeyNameSize = sizeof(subKeyName) / sizeof(wchar_t);
while (RegEnumKeyExW(uninstallKey, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS)
{
HKEY productKey;
if (RegOpenKeyExW(uninstallKey, subKeyName, 0, KEY_READ | KEY_WRITE, &productKey) == ERROR_SUCCESS)
{
wchar_t upgradeCode[256];
DWORD upgradeCodeSize = sizeof(upgradeCode);
DWORD valueType;
if (RegQueryValueExW(productKey, L"BundleUpgradeCode", nullptr, &valueType,
reinterpret_cast<LPBYTE>(upgradeCode), &upgradeCodeSize) == ERROR_SUCCESS)
{
// Remove brackets from registry upgradeCode for comparison (bundleUpgradeCode doesn't have brackets)
std::wstring regUpgradeCode = upgradeCode;
if (!regUpgradeCode.empty() && regUpgradeCode.front() == L'{' && regUpgradeCode.back() == L'}')
{
regUpgradeCode = regUpgradeCode.substr(1, regUpgradeCode.length() - 2);
}
if (_wcsicmp(regUpgradeCode.c_str(), bundleUpgradeCode.c_str()) == 0)
{
// Found matching Bundle, set InstallLocation
LONG setResult = RegSetValueExW(productKey, L"InstallLocation", 0, REG_SZ,
reinterpret_cast<const BYTE*>(installationFolder.c_str()),
static_cast<DWORD>((installationFolder.length() + 1) * sizeof(wchar_t)));
if (setResult == ERROR_SUCCESS)
{
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: InstallLocation set successfully");
}
else
{
WcaLog(LOGMSG_STANDARD, "SetBundleInstallLocationCA: Failed to set InstallLocation, error: %ld", setResult);
}
RegCloseKey(productKey);
RegCloseKey(uninstallKey);
goto LExit;
}
}
RegCloseKey(productKey);
}
index++;
subKeyNameSize = sizeof(subKeyName) / sizeof(wchar_t);
}
RegCloseKey(uninstallKey);
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
void initSystemLogger()
{
static std::once_flag initLoggerFlag;
std::call_once(initLoggerFlag, []()
{
WCHAR temp_path[MAX_PATH];
auto ret = GetTempPath(MAX_PATH, temp_path);
if (ret)
{
Logger::init("PowerToysMSI", std::wstring{ temp_path } + L"\\PowerToysMSIInstaller", L"");
} });
}
// DllMain - Initialize and cleanup WiX custom action utils.
extern "C" BOOL WINAPI DllMain(__in HINSTANCE hInst, __in ULONG ulReason, __in LPVOID)
{
switch (ulReason)
{
case DLL_PROCESS_ATTACH:
WcaGlobalInitialize(hInst);
initSystemLogger();
TraceLoggingRegister(g_hProvider);
DLL_HANDLE = hInst;
break;
case DLL_PROCESS_DETACH:
TraceLoggingUnregister(g_hProvider);
WcaGlobalFinalize();
break;
}
return TRUE;
}