Main.cpp
  1  #include <filesystem>
  2  #include <fstream>
  3  #include <string>
  4  #include <vector>
  5  #include <Shlobj.h>
  6  #include <winrt/Windows.Data.Json.h>
  7  #include <winrt/Windows.Foundation.Collections.h>
  8  #include <winrt/Windows.System.UserProfile.h>
  9  #include <winrt/Windows.Globalization.h>
 10  
 11  #include "ZipTools/ZipFolder.h"
 12  #include <common/SettingsAPI/settings_helpers.h>
 13  #include <common/utils/json.h>
 14  #include <common/utils/timeutil.h>
 15  #include <common/utils/exec.h>
 16  
 17  #include "Package.h"
 18  #include "ReportMonitorInfo.h"
 19  #include "RegistryUtils.h"
 20  #include "EventViewer.h"
 21  #include "InstallationFolder.h"
 22  #include "ReportGPOValues.h"
 23  
 24  using namespace std;
 25  using namespace std::filesystem;
 26  using namespace winrt::Windows::Data::Json;
 27  
 28  map<wstring, vector<wstring>> escapeInfo = {
 29      { L"FancyZones\\app-zone-history.json", { L"app-zone-history/app-path" } },
 30      { L"FancyZones\\settings.json", { L"properties/fancyzones_excluded_apps" } },
 31      { L"MouseWithoutBorders\\settings.json", { L"properties/SecurityKey" } }, // avoid leaking connection key
 32      { L"Keyboard Manager\\default.json", {
 33          L"remapKeysToText",
 34          L"remapShortcutsToText",
 35          L"remapShortcuts/global/runProgramFilePath",
 36          L"remapShortcuts/global/runProgramArgs",
 37          L"remapShortcuts/global/runProgramStartInDir",
 38          L"remapShortcuts/global/openUri",
 39          L"remapShortcuts/appSpecific/runProgramFilePath",
 40          L"remapShortcuts/appSpecific/runProgramArgs",
 41          L"remapShortcuts/appSpecific/runProgramStartInDir",
 42          L"remapShortcuts/appSpecific/openUri",
 43          } }, // avoid leaking personal information from text, URI or application mappings
 44      { L"Workspaces/workspaces.json", { L"workspaces/applications/command-line-arguments" } },
 45      { L"AdvancedPaste/settings.json", {
 46          L"properties/custom-actions/value/name",
 47          L"properties/custom-actions/value/prompt"
 48          } },
 49  };
 50  
 51  vector<wstring> filesToDelete = {
 52      L"AdvancedPaste\\lastQuery.json",
 53      L"AdvancedPaste\\kernelQueryCache.json",
 54      L"PowerToys Run\\Cache",
 55      L"PowerRename\\replace-mru.json",
 56      L"PowerRename\\search-mru.json",
 57      L"PowerToys Run\\Settings\\UserSelectedRecord.json",
 58      L"PowerToys Run\\Settings\\QueryHistory.json",
 59      L"NewPlus\\Templates",
 60      L"etw",
 61  };
 62  
 63  vector<wstring> GetXpathArray(wstring xpath)
 64  {
 65      vector<wstring> result;
 66      wstring cur = L"";
 67      for (auto ch : xpath)
 68      {
 69          if (ch == L'/')
 70          {
 71              result.push_back(cur);
 72              cur = L"";
 73              continue;
 74          }
 75  
 76          cur += ch;
 77      }
 78  
 79      if (!cur.empty())
 80      {
 81          result.push_back(cur);
 82      }
 83  
 84      return result;
 85  }
 86  
 87  void HideByXPath(IJsonValue& val, vector<wstring>& xpathArray, int p)
 88  {
 89      if (val.ValueType() == JsonValueType::Array)
 90      {
 91          for (auto it : val.GetArray())
 92          {
 93              HideByXPath(it, xpathArray, p);
 94          }
 95  
 96          return;
 97      }
 98  
 99      if (p == xpathArray.size() - 1)
100      {
101          if (val.ValueType() == JsonValueType::Object)
102          {
103              auto obj = val.GetObjectW();
104              if (obj.HasKey(xpathArray[p]))
105              {
106                  auto privateDatavalue = JsonValue::CreateStringValue(L"<private_data>");
107                  obj.SetNamedValue(xpathArray[p], privateDatavalue);
108              }
109          }
110  
111          return;
112      }
113  
114      if (val.ValueType() == JsonValueType::Object)
115      {
116          IJsonValue newVal;
117          try
118          {
119              newVal = val.GetObjectW().GetNamedValue(xpathArray[p]);
120          }
121          catch (...)
122          {
123              return;
124          }
125  
126          HideByXPath(newVal, xpathArray, p + 1);
127      }
128  }
129  
130  void HideForFile(const path& dir, const wstring& relativePath)
131  {
132      path jsonPath = dir;
133      jsonPath.append(relativePath);
134      auto jObject = json::from_file(jsonPath.wstring());
135      if (!jObject.has_value())
136      {
137          wprintf(L"Failed to parse file %s\n", jsonPath.c_str());
138          return;
139      }
140  
141      JsonValue jValue = json::value(jObject.value());
142      for (auto xpath : escapeInfo[relativePath])
143      {
144          vector<wstring> xpathArray = GetXpathArray(xpath);
145          HideByXPath(jValue, xpathArray, 0);
146      }
147  
148      json::to_file(jsonPath.wstring(), jObject.value());
149  }
150  
151  bool DeleteFolder(wstring path)
152  {
153      error_code err;
154      remove_all(path, err);
155      if (err.value() != 0)
156      {
157          wprintf_s(L"Failed to delete %s. Error code: %d\n", path.c_str(), err.value());
158          return false;
159      }
160  
161      return true;
162  }
163  
164  void HideUserPrivateInfo(const filesystem::path& dir)
165  {
166      // Replace data in json files
167      for (auto& it : escapeInfo)
168      {
169          HideForFile(dir, it.first);
170      }
171  
172      // Delete files
173      for (auto it : filesToDelete)
174      {
175          auto path = dir;
176          path = path.append(it);
177          DeleteFolder(path);
178      }
179  }
180  
181  void ReportWindowsVersion(const filesystem::path& tmpDir)
182  {
183      auto versionReportPath = tmpDir;
184      versionReportPath = versionReportPath.append("windows-version.txt");
185      OSVERSIONINFOEXW osInfo{};
186  
187      try
188      {
189          NTSTATUS(WINAPI * RtlGetVersion)
190          (LPOSVERSIONINFOEXW) = nullptr;
191          *reinterpret_cast<FARPROC*>(& RtlGetVersion) = GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion");
192          if (RtlGetVersion)
193          {
194              osInfo.dwOSVersionInfoSize = sizeof(osInfo);
195              RtlGetVersion(&osInfo);
196          }
197      }
198      catch (...)
199      {
200          printf("Failed to get windows version info\n");
201          return;
202      }
203  
204      try
205      {
206          wofstream versionReport(versionReportPath);
207          versionReport << "MajorVersion: " << osInfo.dwMajorVersion << endl;
208          versionReport << "MinorVersion: " << osInfo.dwMinorVersion << endl;
209          versionReport << "BuildNumber: " << osInfo.dwBuildNumber << endl;
210      }
211      catch (...)
212      {
213          printf("Failed to write to %s\n", versionReportPath.string().c_str());
214      }
215  }
216  
217  void ReportWindowsSettings(const filesystem::path& tmpDir)
218  {
219      std::wstring userLanguage;
220      std::wstring userLocale;
221      try
222      {
223          const auto lang = winrt::Windows::System::UserProfile::GlobalizationPreferences::Languages().GetAt(0);
224          userLanguage = winrt::Windows::Globalization::Language{ lang }.DisplayName().c_str();
225          wchar_t localeName[LOCALE_NAME_MAX_LENGTH]{};
226          if (!LCIDToLocaleName(GetThreadLocale(), localeName, LOCALE_NAME_MAX_LENGTH, 0))
227          {
228              throw -1;
229          }
230          userLocale = localeName;
231      }
232      catch (...)
233      {
234          printf("Failed to get windows settings\n");
235          return;
236      }
237  
238      try
239      {
240          wofstream settingsReport(tmpDir / "windows-settings.txt");
241          settingsReport << "Preferred user language: " << userLanguage << endl;
242          settingsReport << "User locale: " << userLocale << endl;
243      }
244      catch (...)
245      {
246          printf("Failed to write windows settings\n");
247      }
248  }
249  
250  void ReportDotNetInstallationInfo(const filesystem::path& tmpDir)
251  {
252      auto dotnetInfoPath = tmpDir;
253      dotnetInfoPath.append("dotnet-installation-info.txt");
254      try
255      {
256          wofstream dotnetReport(dotnetInfoPath);
257          auto dotnetInfo = exec_and_read_output(LR"(dotnet --list-runtimes)");
258          if (!dotnetInfo.has_value())
259          {
260              printf("Failed to get dotnet installation information\n");
261              return;
262          }
263  
264          dotnetReport << dotnetInfo.value().c_str();
265      }
266      catch (...)
267      {
268          printf("Failed to report dotnet installation information");
269      }
270  }
271  
272  void ReportInstallerLogs(const filesystem::path& tmpDir, const filesystem::path& reportDir)
273  {
274      const char* bootstrapperLogFilePrefix = "powertoys-bootstrapper-msi-";
275      const char* PTLogFilePrefix = "PowerToysMSIInstaller_";
276  
277      for (auto& entry : directory_iterator{ tmpDir })
278      {
279          std::error_code ec;
280          if (entry.is_directory(ec) || !entry.path().has_filename())
281          {
282              continue;
283          }
284  
285          const auto fileName = entry.path().filename().string();
286          if (!fileName.starts_with(bootstrapperLogFilePrefix) && !fileName.starts_with(PTLogFilePrefix))
287          {
288              continue;
289          }
290          copy(entry.path(), reportDir / fileName, ec);
291      }
292  }
293  
294  int wmain(int argc, wchar_t* argv[], wchar_t*)
295  {
296      // Get path to save zip
297      wstring saveZipPath;
298      if (argc > 1)
299      {
300          saveZipPath = argv[1];
301      }
302      else
303      {
304          wchar_t buffer[MAX_PATH];
305          if (SHGetSpecialFolderPath(HWND_DESKTOP, buffer, CSIDL_DESKTOP, FALSE))
306          {
307              saveZipPath = buffer;
308          }
309          else
310          {
311              printf("Failed to retrieve the desktop path. Error code: %d\n", GetLastError());
312              return 1;
313          }
314      }
315  
316      auto settingsRootPath = PTSettingsHelper::get_root_save_folder_location();
317      settingsRootPath += L"\\";
318  
319      auto localLowPath = PTSettingsHelper::get_local_low_folder_location();
320      localLowPath += L"\\logs\\";
321  
322      const auto tempDir = temp_directory_path();
323      auto reportDir = temp_directory_path() / "PowerToys\\";
324      if (!DeleteFolder(reportDir))
325      {
326          printf("Failed to delete temp folder\n");
327      }
328  
329      try
330      {
331          copy(settingsRootPath, reportDir, copy_options::recursive);
332  
333          // Remove updates folder contents
334          DeleteFolder(reportDir / "Updates");
335      }
336  	
337      catch (...)
338      {
339          printf("Failed to copy PowerToys folder\n");
340          return 1;
341      }
342  
343      try
344      {
345          copy(localLowPath, reportDir, copy_options::recursive);
346      }
347  
348      catch (...)
349      {
350          printf("Failed to copy logs saved in LocalLow\n");
351      }
352  
353  #ifndef _DEBUG
354      InstallationFolder::ReportStructure(reportDir);
355  #endif
356  
357      // Hide sensitive information
358      HideUserPrivateInfo(reportDir);
359  
360      // Write windows settings to the temporary folder
361      ReportWindowsSettings(reportDir);
362  
363      // Write monitors info to the temporary folder
364      ReportMonitorInfo(reportDir);
365  
366      // Write windows version info to the temporary folder
367      ReportWindowsVersion(reportDir);
368  
369      // Write dotnet installation info to the temporary folder
370      ReportDotNetInstallationInfo(reportDir);
371  
372      // Write registry to the temporary folder
373      ReportRegistry(reportDir);
374  
375      // Write gpo policies to the temporary folder
376      ReportGPOValues(reportDir);
377  
378      // Write compatibility tab info to the temporary folder
379      ReportCompatibilityTab(reportDir);
380  
381      // Write event viewer logs info to the temporary folder
382      EventViewer::ReportEventViewerInfo(reportDir);
383  
384      // Write AppXDeployment-Server event logs to the temporary folder
385      EventViewer::ReportAppXDeploymentLogs(reportDir);
386  
387      ReportInstallerLogs(tempDir, reportDir);
388  
389      ReportInstalledContextMenuPackages(reportDir);
390  
391      // Zip folder
392      auto zipPath = path::path(saveZipPath);
393  
394      try
395      {
396          ZipFolder(zipPath, reportDir);
397      }
398      catch (...)
399      {
400          printf("Failed to zip folder\n");
401          return 1;
402      }
403  
404      DeleteFolder(reportDir);
405      return 0;
406  }