dllmain.cpp
1 // dllmain.cpp : Defines the entry point for the DLL application. 2 #include "pch.h" 3 4 #include <interface/powertoy_module_interface.h> 5 6 #include <atomic> 7 #include <common/logger/logger.h> 8 #include <common/utils/logger_helper.h> 9 #include <common/SettingsAPI/settings_helpers.h> 10 #include <common/SettingsAPI/settings_objects.h> 11 #include <common/utils/resources.h> 12 #include <common/utils/package.h> 13 #include <common/utils/process_path.h> 14 #include <common/utils/winapi_error.h> 15 #include <common/interop/shared_constants.h> 16 #include <Psapi.h> 17 #include <TlHelp32.h> 18 #include <thread> 19 20 HINSTANCE g_hInst_cmdPal = 0; 21 22 BOOL APIENTRY DllMain(HMODULE hInstance, 23 DWORD ul_reason_for_call, 24 LPVOID) 25 { 26 switch (ul_reason_for_call) 27 { 28 case DLL_PROCESS_ATTACH: 29 g_hInst_cmdPal = hInstance; 30 break; 31 case DLL_THREAD_ATTACH: 32 case DLL_THREAD_DETACH: 33 case DLL_PROCESS_DETACH: 34 break; 35 } 36 return TRUE; 37 } 38 39 class CmdPal : public PowertoyModuleIface 40 { 41 private: 42 std::wstring app_name; 43 44 //contains the non localized key of the powertoy 45 std::wstring app_key; 46 47 HANDLE m_hTerminateEvent; 48 49 // Track if this is the first call to enable 50 bool firstEnableCall = true; 51 52 static bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, bool silentFail) 53 { 54 std::wstring dir = std::filesystem::path(appPath).parent_path(); 55 56 SHELLEXECUTEINFO sei = { 0 }; 57 sei.cbSize = sizeof(SHELLEXECUTEINFO); 58 sei.hwnd = nullptr; 59 sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE; 60 if (silentFail) 61 { 62 sei.fMask = sei.fMask | SEE_MASK_FLAG_NO_UI; 63 } 64 sei.lpVerb = elevated ? L"runas" : L"open"; 65 sei.lpFile = appPath.c_str(); 66 sei.lpParameters = commandLineArgs.c_str(); 67 sei.lpDirectory = dir.c_str(); 68 sei.nShow = SW_SHOWNORMAL; 69 70 if (!ShellExecuteEx(&sei)) 71 { 72 std::wstring error = get_last_error_or_default(GetLastError()); 73 Logger::error(L"Failed to launch process. {}", error); 74 return false; 75 } 76 77 m_launched.store(true); 78 return true; 79 } 80 81 std::vector<DWORD> GetProcessesIdByName(const std::wstring& processName) 82 { 83 std::vector<DWORD> processIds; 84 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 85 86 if (snapshot != INVALID_HANDLE_VALUE) 87 { 88 PROCESSENTRY32 processEntry; 89 processEntry.dwSize = sizeof(PROCESSENTRY32); 90 91 if (Process32First(snapshot, &processEntry)) 92 { 93 do 94 { 95 if (_wcsicmp(processEntry.szExeFile, processName.c_str()) == 0) 96 { 97 processIds.push_back(processEntry.th32ProcessID); 98 } 99 } while (Process32Next(snapshot, &processEntry)); 100 } 101 102 CloseHandle(snapshot); 103 } 104 105 return processIds; 106 } 107 108 void TerminateCmdPal() 109 { 110 auto processIds = GetProcessesIdByName(L"Microsoft.CmdPal.UI.exe"); 111 112 if (processIds.size() == 0) 113 { 114 Logger::trace(L"Nothing To PROCESS_TERMINATE"); 115 return; 116 } 117 118 for (DWORD pid : processIds) 119 { 120 HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, pid); 121 122 if (hProcess != NULL) 123 { 124 SetEvent(m_hTerminateEvent); 125 126 // Wait for 1.5 seconds for the process to end correctly, allowing time for ETW tracer and extensions to stop 127 if (WaitForSingleObject(hProcess, 1500) == WAIT_TIMEOUT) 128 { 129 TerminateProcess(hProcess, 0); 130 } 131 132 CloseHandle(hProcess); 133 } 134 } 135 } 136 137 public: 138 static std::atomic<bool> m_enabled; 139 static std::atomic<bool> m_launched; 140 141 CmdPal() 142 { 143 app_name = L"CmdPal"; 144 app_key = L"CmdPal"; 145 LoggerHelpers::init_logger(app_key, L"ModuleInterface", "CmdPal"); 146 147 m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::CMDPAL_EXIT_EVENT); 148 } 149 150 ~CmdPal() 151 { 152 CmdPal::m_enabled.store(false); 153 } 154 155 // Destroy the powertoy and free memory 156 virtual void destroy() override 157 { 158 Logger::trace("CmdPal::destroy()"); 159 TerminateCmdPal(); 160 delete this; 161 } 162 163 // Return the localized display name of the powertoy 164 virtual const wchar_t* get_name() override 165 { 166 return app_name.c_str(); 167 } 168 169 // Return the non localized key of the powertoy, this will be cached by the runner 170 virtual const wchar_t* get_key() override 171 { 172 return app_key.c_str(); 173 } 174 175 // Return the configured status for the gpo policy for the module 176 virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override 177 { 178 return powertoys_gpo::getConfiguredCmdPalEnabledValue(); 179 } 180 181 virtual bool get_config(wchar_t* buffer, int* buffer_size) override 182 { 183 HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase); 184 185 // Create a Settings object. 186 PowerToysSettings::Settings settings(hinstance, get_name()); 187 188 return settings.serialize_to_buffer(buffer, buffer_size); 189 } 190 191 virtual void call_custom_action(const wchar_t* /*action*/) override 192 { 193 } 194 195 virtual void set_config(const wchar_t* config) override 196 { 197 try 198 { 199 // Parse the input JSON string. 200 PowerToysSettings::PowerToyValues values = 201 PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); 202 203 // If you don't need to do any custom processing of the settings, proceed 204 // to persists the values calling: 205 values.save_to_settings_file(); 206 // Otherwise call a custom function to process the settings before saving them to disk: 207 // save_settings(); 208 } 209 catch (std::exception&) 210 { 211 // Improper JSON. 212 } 213 } 214 215 virtual void enable() 216 { 217 Logger::trace("CmdPal::enable()"); 218 219 CmdPal::m_enabled.store(true); 220 221 std::wstring packageName = L"Microsoft.CommandPalette"; 222 // Launch CmdPal as normal user using explorer 223 std::wstring launchPath = L"explorer.exe"; 224 std::wstring launchArgs = L"x-cmdpal://background"; 225 #ifdef IS_DEV_BRANDING 226 packageName = L"Microsoft.CommandPalette.Dev"; 227 #endif 228 229 if (!package::GetRegisteredPackage(packageName, false).has_value()) 230 { 231 try 232 { 233 Logger::info(L"CmdPal not installed. Installing..."); 234 235 std::wstring installationFolder = get_module_folderpath(); 236 #ifdef _DEBUG 237 std::wstring archSubdir = L"x64"; 238 #ifdef _M_ARM64 239 archSubdir = L"ARM64"; 240 #endif 241 auto msix = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\AppPackages\\Microsoft.CmdPal.UI_0.0.1.0_Debug_Test\\", false); 242 auto dependencies = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\AppPackages\\Microsoft.CmdPal.UI_0.0.1.0_Debug_Test\\Dependencies\\" + archSubdir + L"\\", true); 243 #else 244 auto msix = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\", false); 245 auto dependencies = package::FindMsixFile(installationFolder + L"\\WinUI3Apps\\CmdPal\\Dependencies\\", true); 246 #endif 247 248 if (!msix.empty()) 249 { 250 auto msixPath = msix[0]; 251 252 if (!package::RegisterPackage(msixPath, dependencies)) 253 { 254 Logger::error(L"Failed to install CmdPal package"); 255 } 256 } 257 } 258 catch (std::exception& e) 259 { 260 std::string errorMessage{ "Exception thrown while trying to install CmdPal package: " }; 261 errorMessage += e.what(); 262 Logger::error(errorMessage); 263 } 264 } 265 266 if (!package::GetRegisteredPackage(packageName, false).has_value()) 267 { 268 Logger::error("Cmdpal is not registered, quit.."); 269 return; 270 } 271 272 if (!firstEnableCall) 273 { 274 Logger::trace("Not first attempt, try to launch"); 275 LaunchApp(launchPath, launchArgs, false /*no elevated*/, false /*error pop up*/); 276 } 277 else 278 { 279 // If first time enable, do retry launch. 280 Logger::trace("First attempt, try to launch"); 281 std::thread launchThread(&CmdPal::RetryLaunch, launchPath, launchArgs); 282 launchThread.detach(); 283 } 284 285 firstEnableCall = false; 286 } 287 288 virtual void disable() 289 { 290 Logger::trace("CmdPal::disable()"); 291 TerminateCmdPal(); 292 293 CmdPal::m_enabled.store(false); 294 } 295 296 static void RetryLaunch(std::wstring path, std::wstring cmdArgs) 297 { 298 const int base_delay_milliseconds = 1000; 299 int max_retry = 9; // 2**9 - 1 seconds. Control total wait time within 10 min. 300 int retry = 0; 301 do 302 { 303 auto launch_result = LaunchApp(path, cmdArgs, false, retry < max_retry); 304 if (launch_result) 305 { 306 Logger::info(L"CmdPal launched successfully after {} retries.", retry); 307 return; 308 } 309 else 310 { 311 Logger::error(L"Retry {} launch CmdPal launch failed.", retry); 312 } 313 314 // When we got max retry, we don't need to wait for the next retry. 315 if (retry < max_retry) 316 { 317 int delay = base_delay_milliseconds * (1 << (retry)); 318 std::this_thread::sleep_for(std::chrono::milliseconds(delay)); 319 } 320 ++retry; 321 } while (retry <= max_retry && m_enabled.load() && !m_launched.load()); 322 323 if (!m_enabled.load() || m_launched.load()) 324 { 325 Logger::error(L"Retry cancelled. CmdPal is disabled or already launched."); 326 } 327 else 328 { 329 Logger::error(L"CmdPal launch failed after {} attempts.", retry); 330 } 331 } 332 333 virtual bool on_hotkey(size_t) override 334 { 335 return false; 336 } 337 338 virtual size_t get_hotkeys(Hotkey*, size_t) override 339 { 340 return 0; 341 } 342 343 virtual bool is_enabled() override 344 { 345 return CmdPal::m_enabled.load(); 346 } 347 }; 348 349 std::atomic<bool> CmdPal::m_enabled{ false }; 350 std::atomic<bool> CmdPal::m_launched{ false }; 351 352 extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() 353 { 354 return new CmdPal(); 355 }