PowerToys/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp
Gordon Lam 0e9c5a82dd
Fix Cmdpal launch without admin mode - both runner and setting. (#39494)
* Change to path and args

* Fix both Setting launch and runner launch
2025-05-16 19:11:57 +08:00

354 lines
10 KiB
C++

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <atomic>
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/resources.h>
#include <common/utils/package.h>
#include <common/utils/process_path.h>
#include <common/utils/winapi_error.h>
#include <common/interop/shared_constants.h>
#include <Psapi.h>
#include <TlHelp32.h>
#include <thread>
HINSTANCE g_hInst_cmdPal = 0;
BOOL APIENTRY DllMain(HMODULE hInstance,
DWORD ul_reason_for_call,
LPVOID)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hInst_cmdPal = hInstance;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
class CmdPal : public PowertoyModuleIface
{
private:
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
HANDLE m_hTerminateEvent;
// Track if this is the first call to enable
bool firstEnableCall = true;
static bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, bool silentFail)
{
std::wstring dir = std::filesystem::path(appPath).parent_path();
SHELLEXECUTEINFO sei = { 0 };
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.hwnd = nullptr;
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
if (silentFail)
{
sei.fMask = sei.fMask | SEE_MASK_FLAG_NO_UI;
}
sei.lpVerb = elevated ? L"runas" : L"open";
sei.lpFile = appPath.c_str();
sei.lpParameters = commandLineArgs.c_str();
sei.lpDirectory = dir.c_str();
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&sei))
{
std::wstring error = get_last_error_or_default(GetLastError());
Logger::error(L"Failed to launch process. {}", error);
return false;
}
m_launched.store(true);
return true;
}
std::vector<DWORD> GetProcessesIdByName(const std::wstring& processName)
{
std::vector<DWORD> processIds;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(snapshot, &processEntry))
{
do
{
if (_wcsicmp(processEntry.szExeFile, processName.c_str()) == 0)
{
processIds.push_back(processEntry.th32ProcessID);
}
} while (Process32Next(snapshot, &processEntry));
}
CloseHandle(snapshot);
}
return processIds;
}
void TerminateCmdPal()
{
auto processIds = GetProcessesIdByName(L"Microsoft.CmdPal.UI.exe");
if (processIds.size() == 0)
{
Logger::trace(L"Nothing To PROCESS_TERMINATE");
return;
}
for (DWORD pid : processIds)
{
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (hProcess != NULL)
{
SetEvent(m_hTerminateEvent);
// Wait for 1.5 seconds for the process to end correctly and stop etw tracer
WaitForSingleObject(hProcess, 1500);
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
}
}
}
public:
static std::atomic<bool> m_enabled;
static std::atomic<bool> m_launched;
CmdPal()
{
app_name = L"CmdPal";
app_key = L"CmdPal";
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "CmdPal");
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::CMDPAL_EXIT_EVENT);
}
~CmdPal()
{
CmdPal::m_enabled.store(false);
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
Logger::trace("CmdPal::destroy()");
TerminateCmdPal();
delete this;
}
// Return the localized display name of the powertoy
virtual const wchar_t* 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::getConfiguredCmdPalEnabledValue();
}
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
return settings.serialize_to_buffer(buffer, buffer_size);
}
virtual void call_custom_action(const wchar_t* /*action*/) override
{
}
virtual void set_config(const wchar_t* config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
// If you don't need to do any custom processing of the settings, proceed
// to persists the values calling:
values.save_to_settings_file();
// Otherwise call a custom function to process the settings before saving them to disk:
// save_settings();
}
catch (std::exception&)
{
// Improper JSON.
}
}
virtual void enable()
{
Logger::trace("CmdPal::enable()");
CmdPal::m_enabled.store(true);
std::wstring packageName = L"Microsoft.CommandPalette";
// Launch CmdPal as normal user using explorer
std::wstring launchPath = L"explorer.exe";
std::wstring launchArgs = L"x-cmdpal://background";
#ifdef IS_DEV_BRANDING
packageName = L"Microsoft.CommandPalette.Dev";
#endif
if (!package::GetRegisteredPackage(packageName, false).has_value())
{
try
{
Logger::info(L"CmdPal not installed. Installing...");
std::wstring installationFolder = get_module_folderpath();
#ifdef _DEBUG
std::wstring archSubdir = L"x64";
#ifdef _M_ARM64
archSubdir = L"ARM64";
#endif
auto msix = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\AppPackages\\Microsoft.CmdPal.UI_0.0.1.0_Debug_Test\\", false);
auto dependencies = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\AppPackages\\Microsoft.CmdPal.UI_0.0.1.0_Debug_Test\\Dependencies\\" + archSubdir + L"\\", true);
#else
auto msix = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\", false);
auto dependencies = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\Dependencies\\", true);
#endif
if (!msix.empty())
{
auto msixPath = msix[0];
if (!package::RegisterPackage(msixPath, dependencies))
{
Logger::error(L"Failed to install CmdPal package");
}
}
}
catch (std::exception& e)
{
std::string errorMessage{ "Exception thrown while trying to install CmdPal package: " };
errorMessage += e.what();
Logger::error(errorMessage);
}
}
if (!package::GetRegisteredPackage(packageName, false).has_value())
{
Logger::error("Cmdpal is not registered, quit..");
return;
}
if (!firstEnableCall)
{
Logger::trace("Not first attempt, try to launch");
LaunchApp(launchPath, launchArgs, false /*no elevated*/, false /*error pop up*/);
}
else
{
// If first time enable, do retry launch.
Logger::trace("First attempt, try to launch");
std::thread launchThread(&CmdPal::RetryLaunch, launchPath, launchArgs);
launchThread.detach();
}
firstEnableCall = false;
}
virtual void disable()
{
Logger::trace("CmdPal::disable()");
TerminateCmdPal();
CmdPal::m_enabled.store(false);
}
static void RetryLaunch(std::wstring path, std::wstring cmdArgs)
{
const int base_delay_milliseconds = 1000;
int max_retry = 9; // 2**9 - 1 seconds. Control total wait time within 10 min.
int retry = 0;
do
{
auto launch_result = LaunchApp(path, cmdArgs, false, retry < max_retry);
if (launch_result)
{
Logger::info(L"CmdPal launched successfully after {} retries.", retry);
return;
}
else
{
Logger::error(L"Retry {} launch CmdPal launch failed.", retry);
}
// When we got max retry, we don't need to wait for the next retry.
if (retry < max_retry)
{
int delay = base_delay_milliseconds * (1 << (retry));
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
}
++retry;
} while (retry <= max_retry && m_enabled.load() && !m_launched.load());
if (!m_enabled.load() || m_launched.load())
{
Logger::error(L"Retry cancelled. CmdPal is disabled or already launched.");
}
else
{
Logger::error(L"CmdPal launch failed after {} attempts.", retry);
}
}
virtual bool on_hotkey(size_t) override
{
return false;
}
virtual size_t get_hotkeys(Hotkey*, size_t) override
{
return 0;
}
virtual bool is_enabled() override
{
return CmdPal::m_enabled.load();
}
};
std::atomic<bool> CmdPal::m_enabled{ false };
std::atomic<bool> CmdPal::m_launched{ false };
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new CmdPal();
}