CustomActions.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. // CustomAction.cpp : Defines the entry point for the custom action.
  2. #include "pch.h"
  3. #include <stdlib.h>
  4. #include <strutil.h>
  5. #include <shellapi.h>
  6. #include <tlhelp32.h>
  7. #include <winternl.h>
  8. #include <netfw.h>
  9. #include <shlwapi.h>
  10. #include "./Common.h"
  11. #pragma comment(lib, "Shlwapi.lib")
  12. UINT __stdcall CustomActionHello(
  13. __in MSIHANDLE hInstall)
  14. {
  15. HRESULT hr = S_OK;
  16. DWORD er = ERROR_SUCCESS;
  17. hr = WcaInitialize(hInstall, "CustomActionHello");
  18. ExitOnFailure(hr, "Failed to initialize");
  19. WcaLog(LOGMSG_STANDARD, "Initialized.");
  20. // TODO: Add your custom action code here.
  21. WcaLog(LOGMSG_STANDARD, "================= Example CustomAction Hello");
  22. LExit:
  23. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  24. return WcaFinalize(er);
  25. }
  26. // CAUTION: We can't simply remove the install folder here, because silent repair/upgrade will fail.
  27. // `RemoveInstallFolder()` is a deferred custom action, it will be executed after the files are copied.
  28. // `msiexec /i package.msi /qn`
  29. //
  30. // So we need to delete the files separately in install folder.
  31. UINT __stdcall RemoveInstallFolder(
  32. __in MSIHANDLE hInstall)
  33. {
  34. HRESULT hr = S_OK;
  35. DWORD er = ERROR_SUCCESS;
  36. int nResult = 0;
  37. LPWSTR installFolder = NULL;
  38. LPWSTR pwz = NULL;
  39. LPWSTR pwzData = NULL;
  40. WCHAR runtimeBroker[1024] = { 0, };
  41. hr = WcaInitialize(hInstall, "RemoveInstallFolder");
  42. ExitOnFailure(hr, "Failed to initialize");
  43. hr = WcaGetProperty(L"CustomActionData", &pwzData);
  44. ExitOnFailure(hr, "failed to get CustomActionData");
  45. pwz = pwzData;
  46. hr = WcaReadStringFromCaData(&pwz, &installFolder);
  47. ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
  48. StringCchPrintfW(runtimeBroker, sizeof(runtimeBroker) / sizeof(runtimeBroker[0]), L"%ls\\RuntimeBroker_rustdesk.exe", installFolder);
  49. SHFILEOPSTRUCTW fileOp;
  50. ZeroMemory(&fileOp, sizeof(SHFILEOPSTRUCT));
  51. fileOp.wFunc = FO_DELETE;
  52. fileOp.pFrom = runtimeBroker;
  53. fileOp.fFlags = FOF_NOCONFIRMATION | FOF_SILENT;
  54. nResult = SHFileOperationW(&fileOp);
  55. if (nResult == 0)
  56. {
  57. WcaLog(LOGMSG_STANDARD, "The external file \"%ls\" has been deleted.", runtimeBroker);
  58. }
  59. else
  60. {
  61. WcaLog(LOGMSG_STANDARD, "The external file \"%ls\" has not been deleted, error code: 0x%02X. Please refer to https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shfileoperationa for the error codes.", runtimeBroker, nResult);
  62. }
  63. LExit:
  64. ReleaseStr(pwzData);
  65. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  66. return WcaFinalize(er);
  67. }
  68. // https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
  69. // **NtQueryInformationProcess** may be altered or unavailable in future versions of Windows.
  70. // Applications should use the alternate functions listed in this topic.
  71. // But I do not find the alternate functions.
  72. // https://github.com/heim-rs/heim/issues/105#issuecomment-683647573
  73. typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
  74. bool TerminateProcessIfNotContainsParam(pfnNtQueryInformationProcess NtQueryInformationProcess, HANDLE process, LPCWSTR excludeParam)
  75. {
  76. bool processClosed = false;
  77. PROCESS_BASIC_INFORMATION processInfo;
  78. NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &processInfo, sizeof(processInfo), NULL);
  79. if (status == 0 && processInfo.PebBaseAddress != NULL)
  80. {
  81. PEB peb;
  82. SIZE_T dwBytesRead;
  83. if (ReadProcessMemory(process, processInfo.PebBaseAddress, &peb, sizeof(peb), &dwBytesRead))
  84. {
  85. RTL_USER_PROCESS_PARAMETERS pebUpp;
  86. if (ReadProcessMemory(process,
  87. peb.ProcessParameters,
  88. &pebUpp,
  89. sizeof(RTL_USER_PROCESS_PARAMETERS),
  90. &dwBytesRead))
  91. {
  92. if (pebUpp.CommandLine.Length > 0)
  93. {
  94. WCHAR *commandLine = (WCHAR *)malloc(pebUpp.CommandLine.Length);
  95. if (commandLine != NULL)
  96. {
  97. if (ReadProcessMemory(process, pebUpp.CommandLine.Buffer,
  98. commandLine, pebUpp.CommandLine.Length, &dwBytesRead))
  99. {
  100. if (wcsstr(commandLine, excludeParam) == NULL)
  101. {
  102. WcaLog(LOGMSG_STANDARD, "Terminate process : %ls", commandLine);
  103. TerminateProcess(process, 0);
  104. processClosed = true;
  105. }
  106. }
  107. free(commandLine);
  108. }
  109. }
  110. }
  111. }
  112. }
  113. return processClosed;
  114. }
  115. // Terminate processes that do not have parameter [excludeParam]
  116. // Note. This function relies on "NtQueryInformationProcess",
  117. // which may not be found.
  118. // Then all processes of [processName] will be terminated.
  119. bool TerminateProcessesByNameW(LPCWSTR processName, LPCWSTR excludeParam)
  120. {
  121. HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
  122. if (hntdll == NULL)
  123. {
  124. WcaLog(LOGMSG_STANDARD, "Failed to load ntdll.");
  125. }
  126. pfnNtQueryInformationProcess NtQueryInformationProcess = NULL;
  127. if (hntdll != NULL)
  128. {
  129. NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(
  130. hntdll, "NtQueryInformationProcess");
  131. }
  132. if (NtQueryInformationProcess == NULL)
  133. {
  134. WcaLog(LOGMSG_STANDARD, "Failed to get address of NtQueryInformationProcess.");
  135. }
  136. bool processClosed = false;
  137. // Create a snapshot of the current system processes
  138. HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  139. if (snapshot != INVALID_HANDLE_VALUE)
  140. {
  141. PROCESSENTRY32W processEntry;
  142. processEntry.dwSize = sizeof(PROCESSENTRY32W);
  143. if (Process32FirstW(snapshot, &processEntry))
  144. {
  145. do
  146. {
  147. if (lstrcmpW(processName, processEntry.szExeFile) == 0)
  148. {
  149. HANDLE process = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processEntry.th32ProcessID);
  150. if (process != NULL)
  151. {
  152. if (NtQueryInformationProcess == NULL)
  153. {
  154. WcaLog(LOGMSG_STANDARD, "Terminate process : %ls, while NtQueryInformationProcess is NULL", processName);
  155. TerminateProcess(process, 0);
  156. processClosed = true;
  157. }
  158. else
  159. {
  160. processClosed = TerminateProcessIfNotContainsParam(
  161. NtQueryInformationProcess,
  162. process,
  163. excludeParam);
  164. }
  165. CloseHandle(process);
  166. }
  167. }
  168. } while (Process32NextW(snapshot, &processEntry));
  169. }
  170. CloseHandle(snapshot);
  171. }
  172. if (hntdll != NULL)
  173. {
  174. CloseHandle(hntdll);
  175. }
  176. return processClosed;
  177. }
  178. UINT __stdcall TerminateProcesses(
  179. __in MSIHANDLE hInstall)
  180. {
  181. HRESULT hr = S_OK;
  182. DWORD er = ERROR_SUCCESS;
  183. int nResult = 0;
  184. wchar_t szProcess[256] = {0};
  185. DWORD cchProcess = sizeof(szProcess) / sizeof(szProcess[0]);
  186. hr = WcaInitialize(hInstall, "TerminateProcesses");
  187. ExitOnFailure(hr, "Failed to initialize");
  188. MsiGetPropertyW(hInstall, L"TerminateProcesses", szProcess, &cchProcess);
  189. WcaLog(LOGMSG_STANDARD, "Try terminate processes : %ls", szProcess);
  190. TerminateProcessesByNameW(szProcess, L"--install");
  191. LExit:
  192. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  193. return WcaFinalize(er);
  194. }
  195. // No use for now, it can be refer as an example of ShellExecuteW.
  196. void AddFirewallRuleCmdline(LPWSTR exeName, LPWSTR exeFile, LPCWSTR dir)
  197. {
  198. HRESULT hr = S_OK;
  199. HINSTANCE hi = 0;
  200. WCHAR cmdline[1024] = { 0, };
  201. WCHAR rulename[500] = { 0, };
  202. StringCchPrintfW(rulename, sizeof(rulename) / sizeof(rulename[0]), L"%ls Service", exeName);
  203. if (FAILED(hr)) {
  204. WcaLog(LOGMSG_STANDARD, "Failed to make rulename: %ls", exeName);
  205. return;
  206. }
  207. StringCchPrintfW(cmdline, sizeof(cmdline) / sizeof(cmdline[0]), L"advfirewall firewall add rule name=\"%ls\" dir=%ls action=allow program=\"%ls\" enable=yes", rulename, dir, exeFile);
  208. if (FAILED(hr)) {
  209. WcaLog(LOGMSG_STANDARD, "Failed to make cmdline: %ls", exeName);
  210. return;
  211. }
  212. hi = ShellExecuteW(NULL, L"open", L"netsh", cmdline, NULL, SW_HIDE);
  213. // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
  214. if ((int)hi <= 32) {
  215. WcaLog(LOGMSG_STANDARD, "Failed to change firewall rule : %d, last error: %d", (int)hi, GetLastError());
  216. }
  217. else {
  218. WcaLog(LOGMSG_STANDARD, "Firewall rule \"%ls\" (%ls) is added", rulename, dir);
  219. }
  220. }
  221. // No use for now, it can be refer as an example of ShellExecuteW.
  222. void RemoveFirewallRuleCmdline(LPWSTR exeName)
  223. {
  224. HRESULT hr = S_OK;
  225. HINSTANCE hi = 0;
  226. WCHAR cmdline[1024] = { 0, };
  227. WCHAR rulename[500] = { 0, };
  228. StringCchPrintfW(rulename, sizeof(rulename) / sizeof(rulename[0]), L"%ls Service", exeName);
  229. if (FAILED(hr)) {
  230. WcaLog(LOGMSG_STANDARD, "Failed to make rulename: %ls", exeName);
  231. return;
  232. }
  233. StringCchPrintfW(cmdline, sizeof(cmdline) / sizeof(cmdline[0]), L"advfirewall firewall delete rule name=\"%ls\"", rulename);
  234. if (FAILED(hr)) {
  235. WcaLog(LOGMSG_STANDARD, "Failed to make cmdline: %ls", exeName);
  236. return;
  237. }
  238. hi = ShellExecuteW(NULL, L"open", L"netsh", cmdline, NULL, SW_HIDE);
  239. // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
  240. if ((int)hi <= 32) {
  241. WcaLog(LOGMSG_STANDARD, "Failed to change firewall rule \"%ls\" : %d, last error: %d", rulename, (int)hi, GetLastError());
  242. }
  243. else {
  244. WcaLog(LOGMSG_STANDARD, "Firewall rule \"%ls\" is removed", rulename);
  245. }
  246. }
  247. UINT __stdcall AddFirewallRules(
  248. __in MSIHANDLE hInstall)
  249. {
  250. HRESULT hr = S_OK;
  251. DWORD er = ERROR_SUCCESS;
  252. int nResult = 0;
  253. LPWSTR exeFile = NULL;
  254. LPWSTR exeName = NULL;
  255. WCHAR exeNameNoExt[500] = { 0, };
  256. LPWSTR pwz = NULL;
  257. LPWSTR pwzData = NULL;
  258. size_t szNameLen = 0;
  259. hr = WcaInitialize(hInstall, "AddFirewallRules");
  260. ExitOnFailure(hr, "Failed to initialize");
  261. hr = WcaGetProperty(L"CustomActionData", &pwzData);
  262. ExitOnFailure(hr, "failed to get CustomActionData");
  263. pwz = pwzData;
  264. hr = WcaReadStringFromCaData(&pwz, &exeFile);
  265. ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
  266. WcaLog(LOGMSG_STANDARD, "Try add firewall exceptions for file : %ls", exeFile);
  267. exeName = PathFindFileNameW(exeFile + 1);
  268. hr = StringCchPrintfW(exeNameNoExt, 500, exeName);
  269. ExitOnFailure(hr, "Failed to copy exe name: %ls", exeName);
  270. szNameLen = wcslen(exeNameNoExt);
  271. if (szNameLen >= 4 && wcscmp(exeNameNoExt + szNameLen - 4, L".exe") == 0) {
  272. exeNameNoExt[szNameLen - 4] = L'\0';
  273. }
  274. //if (exeFile[0] == L'1') {
  275. // AddFirewallRuleCmdline(exeNameNoExt, exeFile, L"in");
  276. // AddFirewallRuleCmdline(exeNameNoExt, exeFile, L"out");
  277. //}
  278. //else {
  279. // RemoveFirewallRuleCmdline(exeNameNoExt);
  280. //}
  281. AddFirewallRule(exeFile[0] == L'1', exeNameNoExt, exeFile + 1);
  282. LExit:
  283. if (pwzData) {
  284. ReleaseStr(pwzData);
  285. }
  286. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  287. return WcaFinalize(er);
  288. }
  289. UINT __stdcall SetPropertyIsServiceRunning(__in MSIHANDLE hInstall)
  290. {
  291. HRESULT hr = S_OK;
  292. DWORD er = ERROR_SUCCESS;
  293. wchar_t szAppName[500] = { 0 };
  294. DWORD cchAppName = sizeof(szAppName) / sizeof(szAppName[0]);
  295. wchar_t szPropertyName[500] = { 0 };
  296. DWORD cchPropertyName = sizeof(szPropertyName) / sizeof(szPropertyName[0]);
  297. bool isRunning = false;
  298. hr = WcaInitialize(hInstall, "SetPropertyIsServiceRunning");
  299. ExitOnFailure(hr, "Failed to initialize");
  300. MsiGetPropertyW(hInstall, L"AppName", szAppName, &cchAppName);
  301. WcaLog(LOGMSG_STANDARD, "Try query service of : \"%ls\"", szAppName);
  302. MsiGetPropertyW(hInstall, L"PropertyName", szPropertyName, &cchPropertyName);
  303. WcaLog(LOGMSG_STANDARD, "Try set is service running, property name : \"%ls\"", szPropertyName);
  304. isRunning = IsServiceRunningW(szAppName);
  305. MsiSetPropertyW(hInstall, szPropertyName, isRunning ? L"'N'" : L"'Y'");
  306. LExit:
  307. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  308. return WcaFinalize(er);
  309. }
  310. void TryCreateStartServiceByShell(LPWSTR svcName, LPWSTR svcBinary, LPWSTR szSvcDisplayName);
  311. UINT __stdcall CreateStartService(__in MSIHANDLE hInstall)
  312. {
  313. HRESULT hr = S_OK;
  314. DWORD er = ERROR_SUCCESS;
  315. LPWSTR svcParams = NULL;
  316. LPWSTR pwz = NULL;
  317. LPWSTR pwzData = NULL;
  318. LPWSTR svcName = NULL;
  319. LPWSTR svcBinary = NULL;
  320. wchar_t szSvcDisplayName[500] = { 0 };
  321. DWORD cchSvcDisplayName = sizeof(szSvcDisplayName) / sizeof(szSvcDisplayName[0]);
  322. hr = WcaInitialize(hInstall, "CreateStartService");
  323. ExitOnFailure(hr, "Failed to initialize");
  324. hr = WcaGetProperty(L"CustomActionData", &pwzData);
  325. ExitOnFailure(hr, "failed to get CustomActionData");
  326. pwz = pwzData;
  327. hr = WcaReadStringFromCaData(&pwz, &svcParams);
  328. ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
  329. WcaLog(LOGMSG_STANDARD, "Try create start service : %ls", svcParams);
  330. svcName = svcParams;
  331. svcBinary = wcschr(svcParams, L';');
  332. if (svcBinary == NULL) {
  333. WcaLog(LOGMSG_STANDARD, "Failed to find binary : %ls", svcParams);
  334. goto LExit;
  335. }
  336. svcBinary[0] = L'\0';
  337. svcBinary += 1;
  338. hr = StringCchPrintfW(szSvcDisplayName, cchSvcDisplayName, L"%ls Service", svcName);
  339. ExitOnFailure(hr, "Failed to compose a resource identifier string");
  340. if (MyCreateServiceW(svcName, szSvcDisplayName, svcBinary)) {
  341. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is created.", svcName);
  342. if (MyStartServiceW(svcName)) {
  343. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is started.", svcName);
  344. }
  345. else {
  346. WcaLog(LOGMSG_STANDARD, "Failed to start service: \"%ls\"", svcName);
  347. }
  348. }
  349. else {
  350. WcaLog(LOGMSG_STANDARD, "Failed to create service: \"%ls\"", svcName);
  351. }
  352. if (IsServiceRunningW(svcName)) {
  353. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is running.", svcName);
  354. }
  355. else {
  356. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is not running, try create and start service by shell", svcName);
  357. TryCreateStartServiceByShell(svcName, svcBinary, szSvcDisplayName);
  358. }
  359. LExit:
  360. if (pwzData) {
  361. ReleaseStr(pwzData);
  362. }
  363. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  364. return WcaFinalize(er);
  365. }
  366. void TryStopDeleteServiceByShell(LPWSTR svcName);
  367. UINT __stdcall TryStopDeleteService(__in MSIHANDLE hInstall)
  368. {
  369. HRESULT hr = S_OK;
  370. DWORD er = ERROR_SUCCESS;
  371. int nResult = 0;
  372. LPWSTR svcName = NULL;
  373. LPWSTR pwz = NULL;
  374. LPWSTR pwzData = NULL;
  375. wchar_t szExeFile[500] = { 0 };
  376. DWORD cchExeFile = sizeof(szExeFile) / sizeof(szExeFile[0]);
  377. SERVICE_STATUS_PROCESS svcStatus;
  378. DWORD lastErrorCode = 0;
  379. hr = WcaInitialize(hInstall, "TryStopDeleteService");
  380. ExitOnFailure(hr, "Failed to initialize");
  381. hr = WcaGetProperty(L"CustomActionData", &pwzData);
  382. ExitOnFailure(hr, "failed to get CustomActionData");
  383. pwz = pwzData;
  384. hr = WcaReadStringFromCaData(&pwz, &svcName);
  385. ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
  386. WcaLog(LOGMSG_STANDARD, "Try stop and delete service : %ls", svcName);
  387. if (MyStopServiceW(svcName)) {
  388. for (int i = 0; i < 10; i++) {
  389. if (IsServiceRunningW(svcName)) {
  390. Sleep(100);
  391. }
  392. else {
  393. break;
  394. }
  395. }
  396. }
  397. else {
  398. WcaLog(LOGMSG_STANDARD, "Failed to stop service: \"%ls\", error: 0x%02X.", svcName, GetLastError());
  399. }
  400. if (IsServiceRunningW(svcName)) {
  401. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is not stoped after 1000 ms.", svcName);
  402. }
  403. else {
  404. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is stoped.", svcName);
  405. }
  406. if (MyDeleteServiceW(svcName)) {
  407. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" deletion is completed without errors.", svcName);
  408. }
  409. else {
  410. WcaLog(LOGMSG_STANDARD, "Failed to delete service: \"%ls\", error: 0x%02X.", svcName, GetLastError());
  411. }
  412. if (QueryServiceStatusExW(svcName, &svcStatus)) {
  413. WcaLog(LOGMSG_STANDARD, "Failed to delete service: \"%ls\", current status: %d.", svcName, svcStatus.dwCurrentState);
  414. TryStopDeleteServiceByShell(svcName);
  415. }
  416. else {
  417. lastErrorCode = GetLastError();
  418. if (lastErrorCode == ERROR_SERVICE_DOES_NOT_EXIST) {
  419. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is deleted.", svcName);
  420. }
  421. else {
  422. WcaLog(LOGMSG_STANDARD, "Failed to query service status: \"%ls\", error: 0x%02X.", svcName, lastErrorCode);
  423. TryStopDeleteServiceByShell(svcName);
  424. }
  425. }
  426. // It's really strange that we need sleep here.
  427. // But the upgrading may be stucked at "copying new files" because the file is in using.
  428. // Steps to reproduce: Install -> stop service in tray --> start service -> upgrade
  429. // Sleep(300);
  430. // Or we can terminate the process
  431. hr = StringCchPrintfW(szExeFile, cchExeFile, L"%ls.exe", svcName);
  432. ExitOnFailure(hr, "Failed to compose a resource identifier string");
  433. TerminateProcessesByNameW(szExeFile, L"--not-in-use");
  434. LExit:
  435. if (pwzData) {
  436. ReleaseStr(pwzData);
  437. }
  438. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  439. return WcaFinalize(er);
  440. }
  441. UINT __stdcall TryDeleteStartupShortcut(__in MSIHANDLE hInstall)
  442. {
  443. HRESULT hr = S_OK;
  444. DWORD er = ERROR_SUCCESS;
  445. wchar_t szShortcut[500] = { 0 };
  446. DWORD cchShortcut = sizeof(szShortcut) / sizeof(szShortcut[0]);
  447. wchar_t szStartupDir[500] = { 0 };
  448. DWORD cchStartupDir = sizeof(szStartupDir) / sizeof(szStartupDir[0]);
  449. WCHAR pwszTemp[1024] = L"";
  450. hr = WcaInitialize(hInstall, "DeleteStartupShortcut");
  451. ExitOnFailure(hr, "Failed to initialize");
  452. MsiGetPropertyW(hInstall, L"StartupFolder", szStartupDir, &cchStartupDir);
  453. MsiGetPropertyW(hInstall, L"ShortcutName", szShortcut, &cchShortcut);
  454. WcaLog(LOGMSG_STANDARD, "Try delete startup shortcut of : \"%ls\"", szShortcut);
  455. hr = StringCchPrintfW(pwszTemp, 1024, L"%ls%ls.lnk", szStartupDir, szShortcut);
  456. ExitOnFailure(hr, "Failed to compose a resource identifier string");
  457. if (DeleteFileW(pwszTemp)) {
  458. WcaLog(LOGMSG_STANDARD, "Failed to delete startup shortcut of : \"%ls\"", pwszTemp);
  459. }
  460. else {
  461. WcaLog(LOGMSG_STANDARD, "Startup shortcut is deleted : \"%ls\"", pwszTemp);
  462. }
  463. LExit:
  464. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  465. return WcaFinalize(er);
  466. }
  467. UINT __stdcall SetPropertyFromConfig(__in MSIHANDLE hInstall)
  468. {
  469. HRESULT hr = S_OK;
  470. DWORD er = ERROR_SUCCESS;
  471. wchar_t szConfigFile[1024] = { 0 };
  472. DWORD cchConfigFile = sizeof(szConfigFile) / sizeof(szConfigFile[0]);
  473. wchar_t szConfigKey[500] = { 0 };
  474. DWORD cchConfigKey = sizeof(szConfigKey) / sizeof(szConfigKey[0]);
  475. wchar_t szPropertyName[500] = { 0 };
  476. DWORD cchPropertyName = sizeof(szPropertyName) / sizeof(szPropertyName[0]);
  477. std::wstring configValue;
  478. hr = WcaInitialize(hInstall, "SetPropertyFromConfig");
  479. ExitOnFailure(hr, "Failed to initialize");
  480. MsiGetPropertyW(hInstall, L"ConfigFile", szConfigFile, &cchConfigFile);
  481. WcaLog(LOGMSG_STANDARD, "Try read config file of : \"%ls\"", szConfigFile);
  482. MsiGetPropertyW(hInstall, L"ConfigKey", szConfigKey, &cchConfigKey);
  483. WcaLog(LOGMSG_STANDARD, "Try read configuration, config key : \"%ls\"", szConfigKey);
  484. MsiGetPropertyW(hInstall, L"PropertyName", szPropertyName, &cchPropertyName);
  485. WcaLog(LOGMSG_STANDARD, "Try read configuration, property name : \"%ls\"", szPropertyName);
  486. configValue = ReadConfig(szConfigFile, szConfigKey);
  487. MsiSetPropertyW(hInstall, szPropertyName, configValue.c_str());
  488. LExit:
  489. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  490. return WcaFinalize(er);
  491. }
  492. UINT __stdcall AddRegSoftwareSASGeneration(__in MSIHANDLE hInstall)
  493. {
  494. HRESULT hr = S_OK;
  495. DWORD er = ERROR_SUCCESS;
  496. LSTATUS result = 0;
  497. HKEY hKey;
  498. LPCWSTR subKey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
  499. LPCWSTR valueName = L"SoftwareSASGeneration";
  500. DWORD valueType = REG_DWORD;
  501. DWORD valueData = 1;
  502. DWORD valueDataSize = sizeof(DWORD);
  503. HINSTANCE hi = 0;
  504. hr = WcaInitialize(hInstall, "AddRegSoftwareSASGeneration");
  505. ExitOnFailure(hr, "Failed to initialize");
  506. hi = ShellExecuteW(NULL, L"open", L"reg", L" add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /f /v SoftwareSASGeneration /t REG_DWORD /d 1", NULL, SW_HIDE);
  507. // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
  508. if ((int)hi <= 32) {
  509. WcaLog(LOGMSG_STANDARD, "Failed to add registry name \"%ls\", %d, %d", valueName, (int)hi, GetLastError());
  510. }
  511. else {
  512. WcaLog(LOGMSG_STANDARD, "Registry name \"%ls\" is added", valueName);
  513. }
  514. // Why RegSetValueExW always return 998?
  515. //
  516. result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
  517. if (result != ERROR_SUCCESS) {
  518. WcaLog(LOGMSG_STANDARD, "Failed to create or open registry key: %d", result);
  519. goto LExit;
  520. }
  521. result = RegSetValueExW(hKey, valueName, 0, valueType, reinterpret_cast<const BYTE*>(valueData), valueDataSize);
  522. if (result != ERROR_SUCCESS) {
  523. WcaLog(LOGMSG_STANDARD, "Failed to set registry value: %d", result);
  524. RegCloseKey(hKey);
  525. goto LExit;
  526. }
  527. WcaLog(LOGMSG_STANDARD, "Registry value has been successfully set.");
  528. RegCloseKey(hKey);
  529. LExit:
  530. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  531. return WcaFinalize(er);
  532. }
  533. UINT __stdcall RemoveAmyuniIdd(
  534. __in MSIHANDLE hInstall)
  535. {
  536. HRESULT hr = S_OK;
  537. DWORD er = ERROR_SUCCESS;
  538. int nResult = 0;
  539. LPWSTR installFolder = NULL;
  540. LPWSTR pwz = NULL;
  541. LPWSTR pwzData = NULL;
  542. WCHAR workDir[1024] = L"";
  543. DWORD fileAttributes = 0;
  544. HINSTANCE hi = 0;
  545. SYSTEM_INFO si;
  546. LPCWSTR exe = L"deviceinstaller64.exe";
  547. WCHAR exePath[1024] = L"";
  548. BOOL rebootRequired = FALSE;
  549. hr = WcaInitialize(hInstall, "RemoveAmyuniIdd");
  550. ExitOnFailure(hr, "Failed to initialize");
  551. UninstallDriver(L"usbmmidd", rebootRequired);
  552. // Only for x86 app on x64
  553. GetNativeSystemInfo(&si);
  554. if (si.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_AMD64) {
  555. goto LExit;
  556. }
  557. hr = WcaGetProperty(L"CustomActionData", &pwzData);
  558. ExitOnFailure(hr, "failed to get CustomActionData");
  559. pwz = pwzData;
  560. hr = WcaReadStringFromCaData(&pwz, &installFolder);
  561. ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
  562. hr = StringCchPrintfW(workDir, 1024, L"%lsusbmmidd_v2", installFolder);
  563. ExitOnFailure(hr, "Failed to compose a resource identifier string");
  564. fileAttributes = GetFileAttributesW(workDir);
  565. if (fileAttributes == INVALID_FILE_ATTRIBUTES) {
  566. WcaLog(LOGMSG_STANDARD, "Amyuni idd dir \"%ls\" is not found, %d", workDir, fileAttributes);
  567. goto LExit;
  568. }
  569. hr = StringCchPrintfW(exePath, 1024, L"%ls\\%ls", workDir, exe);
  570. ExitOnFailure(hr, "Failed to compose a resource identifier string");
  571. fileAttributes = GetFileAttributesW(exePath);
  572. if (fileAttributes == INVALID_FILE_ATTRIBUTES) {
  573. goto LExit;
  574. }
  575. WcaLog(LOGMSG_STANDARD, "Remove amyuni idd %ls in %ls", exe, workDir);
  576. hi = ShellExecuteW(NULL, L"open", exe, L"remove usbmmidd", workDir, SW_HIDE);
  577. // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
  578. if ((int)hi <= 32) {
  579. WcaLog(LOGMSG_STANDARD, "Failed to remove amyuni idd : %d, last error: %d", (int)hi, GetLastError());
  580. }
  581. else {
  582. WcaLog(LOGMSG_STANDARD, "Amyuni idd is removed");
  583. }
  584. LExit:
  585. if (pwzData) {
  586. ReleaseStr(pwzData);
  587. }
  588. er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
  589. return WcaFinalize(er);
  590. }
  591. void TryCreateStartServiceByShell(LPWSTR svcName, LPWSTR svcBinary, LPWSTR szSvcDisplayName)
  592. {
  593. HRESULT hr = S_OK;
  594. HINSTANCE hi = 0;
  595. wchar_t szNewBin[500] = { 0 };
  596. DWORD cchNewBin = sizeof(szNewBin) / sizeof(szNewBin[0]);
  597. wchar_t szCmd[800] = { 0 };
  598. DWORD cchCmd = sizeof(szCmd) / sizeof(szCmd[0]);
  599. SERVICE_STATUS_PROCESS svcStatus;
  600. DWORD lastErrorCode = 0;
  601. int i = 0;
  602. int j = 0;
  603. WcaLog(LOGMSG_STANDARD, "TryCreateStartServiceByShell, service: %ls", svcName);
  604. TryStopDeleteServiceByShell(svcName);
  605. // Do not check the result here
  606. i = 0;
  607. j = 0;
  608. // svcBinary is a string with double quotes, we need to escape it for shell arguments.
  609. // It is orignal used for `CreateServiceW`.
  610. // eg. "C:\Program Files\MyApp\MyApp.exe" --service -> \"C:\Program Files\MyApp\MyApp.exe\" --service
  611. while (true) {
  612. if (svcBinary[j] == L'"') {
  613. szNewBin[i] = L'\\';
  614. i += 1;
  615. if (i >= cchNewBin) {
  616. WcaLog(LOGMSG_STANDARD, "Failed to copy bin for service: %ls, buffer is not enough", svcName);
  617. return;
  618. }
  619. szNewBin[i] = L'"';
  620. }
  621. else {
  622. szNewBin[i] = svcBinary[j];
  623. }
  624. if (svcBinary[j] == L'\0') {
  625. break;
  626. }
  627. i += 1;
  628. j += 1;
  629. if (i >= cchNewBin) {
  630. WcaLog(LOGMSG_STANDARD, "Failed to copy bin for service: %ls, buffer is not enough", svcName);
  631. return;
  632. }
  633. }
  634. hr = StringCchPrintfW(szCmd, cchCmd, L"create %ls binpath= \"%ls\" start= auto DisplayName= \"%ls\"", svcName, szNewBin, szSvcDisplayName);
  635. if (FAILED(hr)) {
  636. WcaLog(LOGMSG_STANDARD, "Failed to make command: %ls", svcName);
  637. return;
  638. }
  639. hi = ShellExecuteW(NULL, L"open", L"sc", szCmd, NULL, SW_HIDE);
  640. if ((int)hi <= 32) {
  641. WcaLog(LOGMSG_STANDARD, "Failed to create service with shell : %d, last error: 0x%02X.", (int)hi, GetLastError());
  642. }
  643. else {
  644. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is created with shell.", svcName);
  645. }
  646. // Query and log if the service is running.
  647. for (int k = 0; k < 10; ++k) {
  648. if (!QueryServiceStatusExW(svcName, &svcStatus)) {
  649. lastErrorCode = GetLastError();
  650. if (lastErrorCode == ERROR_SERVICE_DOES_NOT_EXIST) {
  651. if (k == 29) {
  652. WcaLog(LOGMSG_STANDARD, "Failed to query service status: \"%ls\", service is not found.", svcName);
  653. return;
  654. }
  655. else {
  656. Sleep(100);
  657. continue;
  658. }
  659. }
  660. // Break if the service exists.
  661. WcaLog(LOGMSG_STANDARD, "Failed to query service status: \"%ls\", error: 0x%02X.", svcName, lastErrorCode);
  662. break;
  663. }
  664. else {
  665. if (svcStatus.dwCurrentState == SERVICE_RUNNING) {
  666. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is running.", svcName);
  667. return;
  668. }
  669. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is not running.", svcName);
  670. break;
  671. }
  672. }
  673. hr = StringCchPrintfW(szCmd, cchCmd, L"/c sc start %ls", svcName);
  674. if (FAILED(hr)) {
  675. WcaLog(LOGMSG_STANDARD, "Failed to make command: %ls", svcName);
  676. return;
  677. }
  678. hi = ShellExecuteW(NULL, L"open", L"cmd.exe", szCmd, NULL, SW_HIDE);
  679. if ((int)hi <= 32) {
  680. WcaLog(LOGMSG_STANDARD, "Failed to start service with shell : %d, last error: 0x%02X.", (int)hi, GetLastError());
  681. }
  682. else {
  683. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is started with shell.", svcName);
  684. }
  685. }
  686. void TryStopDeleteServiceByShell(LPWSTR svcName)
  687. {
  688. HRESULT hr = S_OK;
  689. HINSTANCE hi = 0;
  690. wchar_t szCmd[800] = { 0 };
  691. DWORD cchCmd = sizeof(szCmd) / sizeof(szCmd[0]);
  692. SERVICE_STATUS_PROCESS svcStatus;
  693. DWORD lastErrorCode = 0;
  694. WcaLog(LOGMSG_STANDARD, "TryStopDeleteServiceByShell, service: %ls", svcName);
  695. hr = StringCchPrintfW(szCmd, cchCmd, L"/c sc stop %ls", svcName);
  696. if (FAILED(hr)) {
  697. WcaLog(LOGMSG_STANDARD, "Failed to make command: %ls", svcName);
  698. return;
  699. }
  700. hi = ShellExecuteW(NULL, L"open", L"cmd.exe", szCmd, NULL, SW_HIDE);
  701. // Query and log if the service is stopped or deleted.
  702. for (int k = 0; k < 10; ++k) {
  703. if (!IsServiceRunningW(svcName)) {
  704. break;
  705. }
  706. Sleep(100);
  707. }
  708. if (!QueryServiceStatusExW(svcName, &svcStatus)) {
  709. if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) {
  710. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is already deleted.", svcName);
  711. return;
  712. }
  713. WcaLog(LOGMSG_STANDARD, "Failed to query service status: \"%ls\" with shell, error: 0x%02X.", svcName, lastErrorCode);
  714. }
  715. else {
  716. WcaLog(LOGMSG_STANDARD, "Status of service: \"%ls\" with shell, current status: %d.", svcName, svcStatus.dwCurrentState);
  717. }
  718. hr = StringCchPrintfW(szCmd, cchCmd, L"/c sc delete %ls", svcName);
  719. if (FAILED(hr)) {
  720. WcaLog(LOGMSG_STANDARD, "Failed to make command: %ls", svcName);
  721. return;
  722. }
  723. hi = ShellExecuteW(NULL, L"open", L"cmd.exe", szCmd, NULL, SW_HIDE);
  724. if ((int)hi <= 32) {
  725. WcaLog(LOGMSG_STANDARD, "Failed to delete service with shell : %d, last error: 0x%02X.", (int)hi, GetLastError());
  726. }
  727. else {
  728. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" deletion is completed without errors with shell,", svcName);
  729. }
  730. // Query and log the status of the service after deletion.
  731. for (int k = 0; k < 10; ++k) {
  732. if (!QueryServiceStatusExW(svcName, &svcStatus)) {
  733. if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) {
  734. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is deleted with shell.", svcName);
  735. return;
  736. }
  737. }
  738. Sleep(100);
  739. }
  740. if (!QueryServiceStatusExW(svcName, &svcStatus)) {
  741. lastErrorCode = GetLastError();
  742. if (lastErrorCode == ERROR_SERVICE_DOES_NOT_EXIST) {
  743. WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is deleted with shell.", svcName);
  744. return;
  745. }
  746. WcaLog(LOGMSG_STANDARD, "Failed to query service status: \"%ls\" with shell, error: 0x%02X.", svcName, lastErrorCode);
  747. }
  748. else {
  749. WcaLog(LOGMSG_STANDARD, "Failed to delete service: \"%ls\" with shell, current status: %d.", svcName, svcStatus.dwCurrentState);
  750. }
  751. }