utility_win.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /*
  2. * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
  3. * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #include <cassert>
  20. #include "NCTools.h"
  21. #include "utility.h"
  22. #define ASSERT assert
  23. #define Q_ASSERT assert
  24. namespace NCTools {
  25. // Ported from libsync
  26. registryVariant Utility::registryGetKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName)
  27. {
  28. registryVariant value;
  29. HKEY hKey;
  30. REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
  31. LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
  32. ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
  33. if (result != ERROR_SUCCESS)
  34. return value;
  35. DWORD type = 0, sizeInBytes = 0;
  36. result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, nullptr, &sizeInBytes);
  37. ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
  38. if (result == ERROR_SUCCESS) {
  39. switch (type) {
  40. case REG_DWORD:
  41. DWORD dword;
  42. Q_ASSERT(sizeInBytes == sizeof(dword));
  43. if (RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, reinterpret_cast<LPBYTE>(&dword), &sizeInBytes) == ERROR_SUCCESS) {
  44. value = int(dword);
  45. }
  46. break;
  47. case REG_EXPAND_SZ:
  48. case REG_SZ: {
  49. std::wstring string;
  50. string.resize(sizeInBytes / sizeof(wchar_t));
  51. result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, reinterpret_cast<LPBYTE>(string.data()), &sizeInBytes);
  52. if (result == ERROR_SUCCESS) {
  53. int newCharSize = sizeInBytes / sizeof(wchar_t);
  54. // From the doc:
  55. // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with
  56. // the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS,
  57. // the application should ensure that the string is properly terminated before using it; otherwise, it may overwrite a buffer.
  58. if (string.at(newCharSize - 1) == wchar_t('\0'))
  59. string.resize(newCharSize - 1);
  60. value = string;
  61. }
  62. break;
  63. }
  64. case REG_BINARY: {
  65. std::vector<unsigned char> buffer;
  66. buffer.resize(sizeInBytes);
  67. result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, reinterpret_cast<LPBYTE>(buffer.data()), &sizeInBytes);
  68. if (result == ERROR_SUCCESS) {
  69. value = buffer.at(12);
  70. }
  71. break;
  72. }
  73. default:
  74. break;// Q_UNREACHABLE();
  75. }
  76. }
  77. ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
  78. RegCloseKey(hKey);
  79. return value;
  80. }
  81. bool Utility::registrySetKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName, DWORD type, const registryVariant &value)
  82. {
  83. HKEY hKey;
  84. // KEY_WOW64_64KEY is necessary because CLSIDs are "Redirected and reflected only for CLSIDs that do not specify InprocServer32 or InprocHandler32."
  85. // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253%28v=vs.85%29.aspx#redirected__shared__and_reflected_keys_under_wow64
  86. // This shouldn't be an issue in our case since we use shell32.dll as InprocServer32, so we could write those registry keys for both 32 and 64bit.
  87. // FIXME: Not doing so at the moment means that explorer will show the cloud provider, but 32bit processes' open dialogs (like the ownCloud client itself) won't show it.
  88. REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
  89. LONG result = RegCreateKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, nullptr, 0, sam, nullptr, &hKey, nullptr);
  90. ASSERT(result == ERROR_SUCCESS);
  91. if (result != ERROR_SUCCESS)
  92. return false;
  93. result = -1;
  94. switch (type) {
  95. case REG_DWORD: {
  96. try {
  97. DWORD dword = std::get<int>(value);
  98. result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, type, reinterpret_cast<const BYTE *>(&dword), sizeof(dword));
  99. }
  100. catch (const std::bad_variant_access&) {}
  101. break;
  102. }
  103. case REG_EXPAND_SZ:
  104. case REG_SZ: {
  105. try {
  106. std::wstring string = std::get<std::wstring>(value);
  107. result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, type, reinterpret_cast<const BYTE *>(string.data()), static_cast<DWORD>((string.size() + 1) * sizeof(wchar_t)));
  108. }
  109. catch (const std::bad_variant_access&) {}
  110. break;
  111. }
  112. default:
  113. break;// Q_UNREACHABLE();
  114. }
  115. ASSERT(result == ERROR_SUCCESS);
  116. RegCloseKey(hKey);
  117. return result == ERROR_SUCCESS;
  118. }
  119. bool Utility::registryDeleteKeyTree(HKEY hRootKey, const std::wstring &subKey)
  120. {
  121. HKEY hKey;
  122. REGSAM sam = DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_WOW64_64KEY;
  123. LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
  124. ASSERT(result == ERROR_SUCCESS);
  125. if (result != ERROR_SUCCESS)
  126. return false;
  127. result = RegDeleteTree(hKey, nullptr);
  128. RegCloseKey(hKey);
  129. ASSERT(result == ERROR_SUCCESS);
  130. result |= RegDeleteKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), sam, 0);
  131. ASSERT(result == ERROR_SUCCESS);
  132. return result == ERROR_SUCCESS;
  133. }
  134. bool Utility::registryDeleteKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName)
  135. {
  136. HKEY hKey;
  137. REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
  138. LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
  139. ASSERT(result == ERROR_SUCCESS);
  140. if (result != ERROR_SUCCESS)
  141. return false;
  142. result = RegDeleteValue(hKey, reinterpret_cast<LPCWSTR>(valueName.data()));
  143. ASSERT(result == ERROR_SUCCESS);
  144. RegCloseKey(hKey);
  145. return result == ERROR_SUCCESS;
  146. }
  147. bool Utility::registryWalkSubKeys(HKEY hRootKey, const std::wstring &subKey, const std::function<void(HKEY, const std::wstring&)> &callback)
  148. {
  149. HKEY hKey;
  150. REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
  151. LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
  152. ASSERT(result == ERROR_SUCCESS);
  153. if (result != ERROR_SUCCESS)
  154. return false;
  155. DWORD maxSubKeyNameSize;
  156. // Get the largest keyname size once instead of relying each call on ERROR_MORE_DATA.
  157. result = RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, nullptr, &maxSubKeyNameSize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
  158. ASSERT(result == ERROR_SUCCESS);
  159. if (result != ERROR_SUCCESS) {
  160. RegCloseKey(hKey);
  161. return false;
  162. }
  163. std::wstring subKeyName;
  164. subKeyName.reserve(maxSubKeyNameSize + 1);
  165. DWORD retCode = ERROR_SUCCESS;
  166. for (DWORD i = 0; retCode == ERROR_SUCCESS; ++i) {
  167. Q_ASSERT(unsigned(subKeyName.capacity()) > maxSubKeyNameSize);
  168. // Make the previously reserved capacity official again.
  169. subKeyName.resize(subKeyName.capacity());
  170. DWORD subKeyNameSize = static_cast<DWORD>(subKeyName.size());
  171. retCode = RegEnumKeyEx(hKey, i, reinterpret_cast<LPWSTR>(subKeyName.data()), &subKeyNameSize, nullptr, nullptr, nullptr, nullptr);
  172. ASSERT(result == ERROR_SUCCESS || retCode == ERROR_NO_MORE_ITEMS);
  173. if (retCode == ERROR_SUCCESS) {
  174. // subKeyNameSize excludes the trailing \0
  175. subKeyName.resize(subKeyNameSize);
  176. // Pass only the sub keyname, not the full path.
  177. callback(hKey, subKeyName);
  178. }
  179. }
  180. RegCloseKey(hKey);
  181. return retCode != ERROR_NO_MORE_ITEMS;
  182. }
  183. // Created for Win32
  184. DWORD Utility::execCmd(std::wstring cmd, bool wait)
  185. {
  186. // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-processes
  187. STARTUPINFO si;
  188. PROCESS_INFORMATION pi;
  189. ZeroMemory(&si, sizeof(si));
  190. si.cb = sizeof(si);
  191. ZeroMemory(&pi, sizeof(pi));
  192. // Start the child process.
  193. if (!CreateProcess(nullptr, // No module name (use command line)
  194. cmd.data(), // Command line
  195. nullptr, // Process handle not inheritable
  196. nullptr, // Thread handle not inheritable
  197. FALSE, // Set handle inheritance to FALSE
  198. 0, // No creation flags
  199. nullptr, // Use parent's environment block
  200. nullptr, // Use parent's starting directory
  201. &si, // Pointer to STARTUPINFO structure
  202. &pi) // Pointer to PROCESS_INFORMATION structure
  203. )
  204. {
  205. return ERROR_INVALID_FUNCTION;
  206. }
  207. DWORD exitCode = 0;
  208. if (wait) {
  209. // Wait until child process exits.
  210. WaitForSingleObject(pi.hProcess, INFINITE);
  211. GetExitCodeProcess(pi.hProcess, &exitCode);
  212. }
  213. // Close process and thread handles.
  214. CloseHandle(pi.hProcess);
  215. CloseHandle(pi.hThread);
  216. return exitCode;
  217. }
  218. bool Utility::killProcess(const std::wstring &exePath)
  219. {
  220. // https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes
  221. // Get the list of process identifiers.
  222. DWORD aProcesses[1024], cbNeeded, cProcesses, i;
  223. if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) {
  224. return false;
  225. }
  226. // Calculate how many process identifiers were returned.
  227. cProcesses = cbNeeded / sizeof(DWORD);
  228. std::wstring tmpMatch = exePath;
  229. std::transform(tmpMatch.begin(), tmpMatch.end(), tmpMatch.begin(), std::tolower);
  230. for (i = 0; i < cProcesses; i++) {
  231. if (aProcesses[i] != 0) {
  232. // Get a handle to the process.
  233. HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, FALSE, aProcesses[i]);
  234. // Get the process name.
  235. if (hProcess) {
  236. TCHAR szProcessName[MAX_PATH] = {0};
  237. DWORD cbSize = sizeof(szProcessName) / sizeof(TCHAR);
  238. if (QueryFullProcessImageName(hProcess, 0, szProcessName, &cbSize) == TRUE && cbSize > 0) {
  239. std::wstring procName = szProcessName;
  240. std::transform(procName.begin(), procName.end(), procName.begin(), std::tolower);
  241. if (procName == tmpMatch) {
  242. if (TerminateProcess(hProcess, 0) == TRUE) {
  243. WaitForSingleObject(hProcess, INFINITE);
  244. CloseHandle(hProcess);
  245. return true;
  246. }
  247. }
  248. }
  249. CloseHandle(hProcess);
  250. }
  251. }
  252. }
  253. return false;
  254. }
  255. bool Utility::isValidDirectory(const std::wstring &path)
  256. {
  257. auto attrib = GetFileAttributes(path.data());
  258. if (attrib == INVALID_FILE_ATTRIBUTES || GetLastError() == ERROR_FILE_NOT_FOUND) {
  259. return false;
  260. }
  261. return (attrib & FILE_ATTRIBUTE_DIRECTORY);
  262. }
  263. std::wstring Utility::getAppRegistryString(const std::wstring &appVendor, const std::wstring &appName, const std::wstring &valueName)
  264. {
  265. std::wstring appKey = std::wstring(LR"(SOFTWARE\)") + appVendor + L'\\' + appName;
  266. std::wstring appKeyWow64 = std::wstring(LR"(SOFTWARE\WOW6432Node\)") + appVendor + L'\\' + appName;
  267. std::vector<std::wstring> appKeys = { appKey, appKeyWow64 };
  268. for (auto &key : appKeys) {
  269. try {
  270. return std::get<std::wstring>(Utility::registryGetKeyValue(HKEY_LOCAL_MACHINE,
  271. key,
  272. valueName));
  273. }
  274. catch (const std::bad_variant_access&) {}
  275. }
  276. return {};
  277. }
  278. std::wstring Utility::getAppPath(const std::wstring &appVendor, const std::wstring &appName)
  279. {
  280. return getAppRegistryString(appVendor, appName, L""); // intentionally left empty to get the key's "(default)" value
  281. }
  282. std::wstring Utility::getConfigPath(const std::wstring &appName)
  283. {
  284. // On Windows, use AppDataLocation, that's where the roaming data is and where we should store the config file
  285. PWSTR pszPath = nullptr;
  286. if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pszPath)) || !pszPath) {
  287. return {};
  288. }
  289. std::wstring path = pszPath + PathSeparator + appName + PathSeparator;
  290. CoTaskMemFree(pszPath);
  291. auto newLocation = path;
  292. return newLocation;
  293. }
  294. void Utility::waitForNsisUninstaller(const std::wstring &appShortName)
  295. {
  296. // Can't WaitForSingleObject because NSIS Uninstall.exe copies itself to a TEMP directory and creates a new process,
  297. // so we do sort of a hack and wait for its mutex (see nextcloud.nsi).
  298. HANDLE hMutex;
  299. DWORD lastError = ERROR_SUCCESS;
  300. std::wstring name = appShortName + std::wstring(L"Uninstaller");
  301. // Give the process enough time to start, to wait for the NSIS mutex.
  302. Sleep(1500);
  303. do {
  304. hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, name.data());
  305. lastError = GetLastError();
  306. if (hMutex) {
  307. CloseHandle(hMutex);
  308. }
  309. // This is sort of a hack because WaitForSingleObject immediately returns for the NSIS mutex.
  310. Sleep(500);
  311. } while (lastError != ERROR_FILE_NOT_FOUND);
  312. }
  313. void Utility::removeNavigationPaneEntries(const std::wstring &appName)
  314. {
  315. if (appName.empty()) {
  316. return;
  317. }
  318. // Start by looking at every registered namespace extension for the sidebar, and look for an "ApplicationName" value
  319. // that matches ours when we saved.
  320. std::vector<std::wstring> entriesToRemove;
  321. Utility::registryWalkSubKeys(
  322. HKEY_CURRENT_USER,
  323. LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace)",
  324. [&entriesToRemove, &appName](HKEY key, const std::wstring &subKey) {
  325. try {
  326. auto curAppName = std::get<std::wstring>(Utility::registryGetKeyValue(key, subKey, L"ApplicationName"));
  327. if (curAppName == appName) {
  328. entriesToRemove.push_back(subKey);
  329. }
  330. }
  331. catch (const std::bad_variant_access&) {}
  332. });
  333. for (auto &clsid : entriesToRemove) {
  334. std::wstring clsidStr = clsid;
  335. std::wstring clsidPath = std::wstring(LR"(Software\Classes\CLSID\)") + clsidStr;
  336. std::wstring clsidPathWow64 = std::wstring(LR"(Software\Classes\Wow6432Node\CLSID\)") + clsidStr;
  337. std::wstring namespacePath = std::wstring(LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\)") + clsidStr;
  338. Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPath);
  339. Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPathWow64);
  340. Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, namespacePath);
  341. Utility::registryDeleteKeyValue(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel)", clsidStr);
  342. }
  343. }
  344. // Ported from gui, modified to optionally rename matching files
  345. bool Utility::copy_dir_recursive(std::wstring from_dir, std::wstring to_dir, copy_dir_recursive_callback *callbackFileNameMatchReplace)
  346. {
  347. WIN32_FIND_DATA fileData;
  348. if (from_dir.empty() || to_dir.empty()) {
  349. return false;
  350. }
  351. if (from_dir.back() != PathSeparator.front())
  352. from_dir.append(PathSeparator);
  353. if (to_dir.back() != PathSeparator.front())
  354. to_dir.append(PathSeparator);
  355. std::wstring startDir = from_dir;
  356. startDir.append(L"*.*");
  357. auto hFind = FindFirstFile(startDir.data(), &fileData);
  358. if (hFind == INVALID_HANDLE_VALUE) {
  359. return false;
  360. }
  361. bool success = true;
  362. do {
  363. if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  364. if (std::wstring(fileData.cFileName) == L"." || std::wstring(fileData.cFileName) == L"..") {
  365. continue;
  366. }
  367. std::wstring from = from_dir + fileData.cFileName;
  368. std::wstring to = to_dir + fileData.cFileName;
  369. if (CreateDirectoryEx(from.data(), to.data(), nullptr) == FALSE) {
  370. success = false;
  371. break;
  372. }
  373. if (copy_dir_recursive(from, to, callbackFileNameMatchReplace) == false) {
  374. success = false;
  375. break;
  376. }
  377. } else {
  378. std::wstring newFilename = fileData.cFileName;
  379. if (callbackFileNameMatchReplace) {
  380. (*callbackFileNameMatchReplace)(std::wstring(fileData.cFileName), newFilename);
  381. }
  382. std::wstring from = from_dir + fileData.cFileName;
  383. std::wstring to = to_dir + newFilename;
  384. if (CopyFile(from.data(), to.data(), TRUE) == FALSE) {
  385. success = false;
  386. break;
  387. }
  388. }
  389. } while (FindNextFile(hFind, &fileData));
  390. FindClose(hFind);
  391. return success;
  392. }
  393. } // namespace NCTools