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  }