You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2734 lines
96 KiB

  1. //-------------------------------------------------------------------------------------------------
  2. // <copyright file="WixStandardBootstrapperApplication.cpp" company="Outercurve Foundation">
  3. // Copyright (c) 2004, Outercurve Foundation.
  4. // This software is released under Microsoft Reciprocal License (MS-RL).
  5. // The license and further copyright text can be found in the file
  6. // LICENSE.TXT at the root directory of the distribution.
  7. // </copyright>
  8. //-------------------------------------------------------------------------------------------------
  9. #include "pch.h"
  10. static const LPCWSTR PYBA_WINDOW_CLASS = L"PythonBA";
  11. static const LPCWSTR PYBA_VARIABLE_LAUNCH_TARGET_PATH = L"LaunchTarget";
  12. static const LPCWSTR PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID = L"LaunchTargetElevatedId";
  13. static const LPCWSTR PYBA_VARIABLE_LAUNCH_ARGUMENTS = L"LaunchArguments";
  14. static const LPCWSTR PYBA_VARIABLE_LAUNCH_HIDDEN = L"LaunchHidden";
  15. static const DWORD PYBA_ACQUIRE_PERCENTAGE = 30;
  16. static const LPCWSTR PYBA_VARIABLE_BUNDLE_FILE_VERSION = L"WixBundleFileVersion";
  17. enum PYBA_STATE {
  18. PYBA_STATE_INITIALIZING,
  19. PYBA_STATE_INITIALIZED,
  20. PYBA_STATE_HELP,
  21. PYBA_STATE_DETECTING,
  22. PYBA_STATE_DETECTED,
  23. PYBA_STATE_PLANNING,
  24. PYBA_STATE_PLANNED,
  25. PYBA_STATE_APPLYING,
  26. PYBA_STATE_CACHING,
  27. PYBA_STATE_CACHED,
  28. PYBA_STATE_EXECUTING,
  29. PYBA_STATE_EXECUTED,
  30. PYBA_STATE_APPLIED,
  31. PYBA_STATE_FAILED,
  32. };
  33. static const int WM_PYBA_SHOW_HELP = WM_APP + 100;
  34. static const int WM_PYBA_DETECT_PACKAGES = WM_APP + 101;
  35. static const int WM_PYBA_PLAN_PACKAGES = WM_APP + 102;
  36. static const int WM_PYBA_APPLY_PACKAGES = WM_APP + 103;
  37. static const int WM_PYBA_CHANGE_STATE = WM_APP + 104;
  38. static const int WM_PYBA_SHOW_FAILURE = WM_APP + 105;
  39. // This enum must be kept in the same order as the PAGE_NAMES array.
  40. enum PAGE {
  41. PAGE_LOADING,
  42. PAGE_HELP,
  43. PAGE_INSTALL,
  44. PAGE_SIMPLE_INSTALL,
  45. PAGE_CUSTOM1,
  46. PAGE_CUSTOM2,
  47. PAGE_MODIFY,
  48. PAGE_PROGRESS,
  49. PAGE_PROGRESS_PASSIVE,
  50. PAGE_SUCCESS,
  51. PAGE_FAILURE,
  52. COUNT_PAGE,
  53. };
  54. // This array must be kept in the same order as the PAGE enum.
  55. static LPCWSTR PAGE_NAMES[] = {
  56. L"Loading",
  57. L"Help",
  58. L"Install",
  59. L"SimpleInstall",
  60. L"Custom1",
  61. L"Custom2",
  62. L"Modify",
  63. L"Progress",
  64. L"ProgressPassive",
  65. L"Success",
  66. L"Failure",
  67. };
  68. enum CONTROL_ID {
  69. // Non-paged controls
  70. ID_CLOSE_BUTTON = THEME_FIRST_ASSIGN_CONTROL_ID,
  71. ID_MINIMIZE_BUTTON,
  72. // Welcome page
  73. ID_INSTALL_ALL_USERS_BUTTON,
  74. ID_INSTALL_JUST_FOR_ME_BUTTON,
  75. ID_INSTALL_CUSTOM_BUTTON,
  76. ID_INSTALL_SIMPLE_BUTTON,
  77. ID_INSTALL_CANCEL_BUTTON,
  78. // Customize Page
  79. ID_TARGETDIR_EDITBOX,
  80. ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX,
  81. ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX,
  82. ID_CUSTOM_BROWSE_BUTTON,
  83. ID_CUSTOM_INSTALL_BUTTON,
  84. ID_CUSTOM_NEXT_BUTTON,
  85. ID_CUSTOM1_BACK_BUTTON,
  86. ID_CUSTOM2_BACK_BUTTON,
  87. ID_CUSTOM1_CANCEL_BUTTON,
  88. ID_CUSTOM2_CANCEL_BUTTON,
  89. // Modify page
  90. ID_MODIFY_BUTTON,
  91. ID_REPAIR_BUTTON,
  92. ID_UNINSTALL_BUTTON,
  93. ID_MODIFY_CANCEL_BUTTON,
  94. // Progress page
  95. ID_CACHE_PROGRESS_PACKAGE_TEXT,
  96. ID_CACHE_PROGRESS_BAR,
  97. ID_CACHE_PROGRESS_TEXT,
  98. ID_EXECUTE_PROGRESS_PACKAGE_TEXT,
  99. ID_EXECUTE_PROGRESS_BAR,
  100. ID_EXECUTE_PROGRESS_TEXT,
  101. ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT,
  102. ID_OVERALL_PROGRESS_PACKAGE_TEXT,
  103. ID_OVERALL_PROGRESS_BAR,
  104. ID_OVERALL_CALCULATED_PROGRESS_BAR,
  105. ID_OVERALL_PROGRESS_TEXT,
  106. ID_PROGRESS_CANCEL_BUTTON,
  107. // Success page
  108. ID_LAUNCH_BUTTON,
  109. ID_SUCCESS_TEXT,
  110. ID_SUCCESS_RESTART_TEXT,
  111. ID_SUCCESS_RESTART_BUTTON,
  112. ID_SUCCESS_CANCEL_BUTTON,
  113. // Failure page
  114. ID_FAILURE_LOGFILE_LINK,
  115. ID_FAILURE_MESSAGE_TEXT,
  116. ID_FAILURE_RESTART_TEXT,
  117. ID_FAILURE_RESTART_BUTTON,
  118. ID_FAILURE_CANCEL_BUTTON
  119. };
  120. static THEME_ASSIGN_CONTROL_ID CONTROL_ID_NAMES[] = {
  121. { ID_CLOSE_BUTTON, L"CloseButton" },
  122. { ID_MINIMIZE_BUTTON, L"MinimizeButton" },
  123. { ID_INSTALL_ALL_USERS_BUTTON, L"InstallAllUsersButton" },
  124. { ID_INSTALL_JUST_FOR_ME_BUTTON, L"InstallJustForMeButton" },
  125. { ID_INSTALL_CUSTOM_BUTTON, L"InstallCustomButton" },
  126. { ID_INSTALL_SIMPLE_BUTTON, L"InstallSimpleButton" },
  127. { ID_INSTALL_CANCEL_BUTTON, L"InstallCancelButton" },
  128. { ID_TARGETDIR_EDITBOX, L"TargetDir" },
  129. { ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, L"AssociateFiles" },
  130. { ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, L"InstallAllUsers" },
  131. { ID_CUSTOM_BROWSE_BUTTON, L"CustomBrowseButton" },
  132. { ID_CUSTOM_INSTALL_BUTTON, L"CustomInstallButton" },
  133. { ID_CUSTOM_NEXT_BUTTON, L"CustomNextButton" },
  134. { ID_CUSTOM1_BACK_BUTTON, L"Custom1BackButton" },
  135. { ID_CUSTOM2_BACK_BUTTON, L"Custom2BackButton" },
  136. { ID_CUSTOM1_CANCEL_BUTTON, L"Custom1CancelButton" },
  137. { ID_CUSTOM2_CANCEL_BUTTON, L"Custom2CancelButton" },
  138. { ID_MODIFY_BUTTON, L"ModifyButton" },
  139. { ID_REPAIR_BUTTON, L"RepairButton" },
  140. { ID_UNINSTALL_BUTTON, L"UninstallButton" },
  141. { ID_MODIFY_CANCEL_BUTTON, L"ModifyCancelButton" },
  142. { ID_CACHE_PROGRESS_PACKAGE_TEXT, L"CacheProgressPackageText" },
  143. { ID_CACHE_PROGRESS_BAR, L"CacheProgressbar" },
  144. { ID_CACHE_PROGRESS_TEXT, L"CacheProgressText" },
  145. { ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"ExecuteProgressPackageText" },
  146. { ID_EXECUTE_PROGRESS_BAR, L"ExecuteProgressbar" },
  147. { ID_EXECUTE_PROGRESS_TEXT, L"ExecuteProgressText" },
  148. { ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"ExecuteProgressActionDataText" },
  149. { ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"OverallProgressPackageText" },
  150. { ID_OVERALL_PROGRESS_BAR, L"OverallProgressbar" },
  151. { ID_OVERALL_CALCULATED_PROGRESS_BAR, L"OverallCalculatedProgressbar" },
  152. { ID_OVERALL_PROGRESS_TEXT, L"OverallProgressText" },
  153. { ID_PROGRESS_CANCEL_BUTTON, L"ProgressCancelButton" },
  154. { ID_LAUNCH_BUTTON, L"LaunchButton" },
  155. { ID_SUCCESS_TEXT, L"SuccessText" },
  156. { ID_SUCCESS_RESTART_TEXT, L"SuccessRestartText" },
  157. { ID_SUCCESS_RESTART_BUTTON, L"SuccessRestartButton" },
  158. { ID_SUCCESS_CANCEL_BUTTON, L"SuccessCancelButton" },
  159. { ID_FAILURE_LOGFILE_LINK, L"FailureLogFileLink" },
  160. { ID_FAILURE_MESSAGE_TEXT, L"FailureMessageText" },
  161. { ID_FAILURE_RESTART_TEXT, L"FailureRestartText" },
  162. { ID_FAILURE_RESTART_BUTTON, L"FailureRestartButton" },
  163. { ID_FAILURE_CANCEL_BUTTON, L"FailureCancelButton" },
  164. };
  165. class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication {
  166. void ShowPage(DWORD newPageId) {
  167. // Process each control for special handling in the new page.
  168. ProcessPageControls(ThemeGetPage(_theme, newPageId));
  169. // Enable disable controls per-page.
  170. if (_pageIds[PAGE_INSTALL] == newPageId || _pageIds[PAGE_SIMPLE_INSTALL] == newPageId) {
  171. InstallPage_Show();
  172. } else if (_pageIds[PAGE_CUSTOM1] == newPageId) {
  173. Custom1Page_Show();
  174. } else if (_pageIds[PAGE_CUSTOM2] == newPageId) {
  175. Custom2Page_Show();
  176. } else if (_pageIds[PAGE_MODIFY] == newPageId) {
  177. ModifyPage_Show();
  178. } else if (_pageIds[PAGE_SUCCESS] == newPageId) {
  179. SuccessPage_Show();
  180. } else if (_pageIds[PAGE_FAILURE] == newPageId) {
  181. FailurePage_Show();
  182. }
  183. // Prevent repainting while switching page to avoid ugly flickering
  184. _suppressPaint = TRUE;
  185. ThemeShowPage(_theme, newPageId, SW_SHOW);
  186. ThemeShowPage(_theme, _visiblePageId, SW_HIDE);
  187. _suppressPaint = FALSE;
  188. InvalidateRect(_theme->hwndParent, nullptr, TRUE);
  189. _visiblePageId = newPageId;
  190. // On the install page set the focus to the install button or
  191. // the next enabled control if install is disabled
  192. if (_pageIds[PAGE_INSTALL] == newPageId) {
  193. ThemeSetFocus(_theme, ID_INSTALL_ALL_USERS_BUTTON);
  194. } else if (_pageIds[PAGE_SIMPLE_INSTALL] == newPageId) {
  195. ThemeSetFocus(_theme, ID_INSTALL_SIMPLE_BUTTON);
  196. }
  197. }
  198. //
  199. // Handles control clicks
  200. //
  201. void OnCommand(CONTROL_ID id) {
  202. LPWSTR defaultDir = nullptr;
  203. LPWSTR targetDir = nullptr;
  204. LONGLONG elevated, crtInstalled;
  205. BOOL checked;
  206. WCHAR wzPath[MAX_PATH] = { };
  207. BROWSEINFOW browseInfo = { };
  208. PIDLIST_ABSOLUTE pidl = nullptr;
  209. HRESULT hr = S_OK;
  210. switch(id) {
  211. case ID_CLOSE_BUTTON:
  212. OnClickCloseButton();
  213. break;
  214. // Install commands
  215. case ID_INSTALL_SIMPLE_BUTTON:
  216. hr = BalGetStringVariable(L"TargetDir", &targetDir);
  217. if (FAILED(hr) || !targetDir || !*targetDir) {
  218. LONGLONG installAll;
  219. if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll)) && installAll) {
  220. hr = BalGetStringVariable(L"DefaultAllUsersTargetDir", &defaultDir);
  221. BalExitOnFailure(hr, "Failed to get the default all users install directory");
  222. } else {
  223. hr = BalGetStringVariable(L"DefaultJustForMeTargetDir", &defaultDir);
  224. BalExitOnFailure(hr, "Failed to get the default per-user install directory");
  225. }
  226. if (!defaultDir || !*defaultDir) {
  227. BalLogError(E_INVALIDARG, "Default install directory is blank");
  228. }
  229. hr = BalFormatString(defaultDir, &targetDir);
  230. BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir);
  231. hr = _engine->SetVariableString(L"TargetDir", targetDir);
  232. ReleaseStr(targetDir);
  233. BalExitOnFailure(hr, "Failed to set install target directory");
  234. } else {
  235. ReleaseStr(targetDir);
  236. }
  237. OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
  238. break;
  239. case ID_INSTALL_ALL_USERS_BUTTON:
  240. SavePageSettings();
  241. hr = _engine->SetVariableNumeric(L"InstallAllUsers", 1);
  242. ExitOnFailure(hr, L"Failed to set install scope");
  243. hr = _engine->SetVariableNumeric(L"CompileAll", 1);
  244. ExitOnFailure(hr, L"Failed to set compile all setting");
  245. hr = BalGetStringVariable(L"DefaultAllUsersTargetDir", &defaultDir);
  246. BalExitOnFailure(hr, "Failed to get the default all users install directory");
  247. if (!defaultDir || !*defaultDir) {
  248. BalLogError(E_INVALIDARG, "Default install directory is blank");
  249. }
  250. hr = BalFormatString(defaultDir, &targetDir);
  251. BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir);
  252. hr = _engine->SetVariableString(L"TargetDir", targetDir);
  253. ReleaseStr(targetDir);
  254. BalExitOnFailure(hr, "Failed to set install target directory");
  255. OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
  256. break;
  257. case ID_INSTALL_JUST_FOR_ME_BUTTON:
  258. SavePageSettings();
  259. hr = _engine->SetVariableNumeric(L"InstallAllUsers", 0);
  260. ExitOnFailure(hr, L"Failed to set install scope");
  261. hr = BalGetStringVariable(L"DefaultJustForMeTargetDir", &defaultDir);
  262. BalExitOnFailure(hr, "Failed to get the default per-user install directory");
  263. if (!defaultDir || !*defaultDir) {
  264. BalLogError(E_INVALIDARG, "Default install directory is blank");
  265. }
  266. hr = BalFormatString(defaultDir, &targetDir);
  267. BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir);
  268. hr = _engine->SetVariableString(L"TargetDir", targetDir);
  269. ReleaseStr(targetDir);
  270. BalExitOnFailure(hr, "Failed to set install target directory");
  271. if (!QueryElevateForCrtInstall()) {
  272. break;
  273. }
  274. OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
  275. break;
  276. case ID_CUSTOM1_BACK_BUTTON:
  277. SavePageSettings();
  278. if (_modifying) {
  279. GoToPage(PAGE_MODIFY);
  280. } else {
  281. GoToPage(PAGE_INSTALL);
  282. }
  283. break;
  284. case ID_INSTALL_CUSTOM_BUTTON: __fallthrough;
  285. case ID_CUSTOM2_BACK_BUTTON:
  286. SavePageSettings();
  287. GoToPage(PAGE_CUSTOM1);
  288. break;
  289. case ID_CUSTOM_NEXT_BUTTON:
  290. SavePageSettings();
  291. GoToPage(PAGE_CUSTOM2);
  292. break;
  293. case ID_CUSTOM_INSTALL_BUTTON:
  294. SavePageSettings();
  295. hr = BalGetStringVariable(L"TargetDir", &targetDir);
  296. if (SUCCEEDED(hr)) {
  297. // TODO: Check whether directory exists and contains another installation
  298. ReleaseStr(targetDir);
  299. }
  300. checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX);
  301. if (!checked && !QueryElevateForCrtInstall()) {
  302. break;
  303. }
  304. OnPlan(_command.action);
  305. break;
  306. case ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX:
  307. hr = BalGetNumericVariable(L"WixBundleElevated", &elevated);
  308. checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX);
  309. ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, checked && (FAILED(hr) || !elevated));
  310. ThemeGetTextControl(_theme, ID_TARGETDIR_EDITBOX, &targetDir);
  311. if (targetDir) {
  312. // Check the current value against the default to see
  313. // if we should switch it automatically.
  314. hr = BalGetStringVariable(
  315. checked ? L"DefaultJustForMeTargetDir" : L"DefaultAllUsersTargetDir",
  316. &defaultDir
  317. );
  318. if (SUCCEEDED(hr) && defaultDir) {
  319. LPWSTR formatted = nullptr;
  320. if (defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) {
  321. if (wcscmp(formatted, targetDir) == 0) {
  322. ReleaseStr(defaultDir);
  323. defaultDir = nullptr;
  324. ReleaseStr(formatted);
  325. formatted = nullptr;
  326. hr = BalGetStringVariable(
  327. checked ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir",
  328. &defaultDir
  329. );
  330. if (SUCCEEDED(hr) && defaultDir && defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) {
  331. ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, formatted);
  332. ReleaseStr(formatted);
  333. }
  334. } else {
  335. ReleaseStr(formatted);
  336. }
  337. }
  338. ReleaseStr(defaultDir);
  339. }
  340. }
  341. break;
  342. case ID_CUSTOM_BROWSE_BUTTON:
  343. browseInfo.hwndOwner = _hWnd;
  344. browseInfo.pszDisplayName = wzPath;
  345. browseInfo.lpszTitle = _theme->sczCaption;
  346. browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
  347. pidl = ::SHBrowseForFolderW(&browseInfo);
  348. if (pidl && ::SHGetPathFromIDListW(pidl, wzPath)) {
  349. ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, wzPath);
  350. }
  351. if (pidl) {
  352. ::CoTaskMemFree(pidl);
  353. }
  354. break;
  355. // Modify commands
  356. case ID_MODIFY_BUTTON:
  357. // Some variables cannot be modified
  358. _engine->SetVariableString(L"InstallAllUsersState", L"disable");
  359. _engine->SetVariableString(L"TargetDirState", L"disable");
  360. _engine->SetVariableString(L"CustomBrowseButtonState", L"disable");
  361. _modifying = TRUE;
  362. GoToPage(PAGE_CUSTOM1);
  363. break;
  364. case ID_REPAIR_BUTTON:
  365. OnPlan(BOOTSTRAPPER_ACTION_REPAIR);
  366. break;
  367. case ID_UNINSTALL_BUTTON:
  368. OnPlan(BOOTSTRAPPER_ACTION_UNINSTALL);
  369. break;
  370. }
  371. LExit:
  372. return;
  373. }
  374. void InstallPage_Show() {
  375. // Ensure the All Users install button has a UAC shield
  376. LONGLONG elevated, installAll;
  377. if (FAILED(BalGetNumericVariable(L"WixBundleElevated", &elevated))) {
  378. elevated = 0;
  379. }
  380. ThemeControlElevates(_theme, ID_INSTALL_ALL_USERS_BUTTON, !elevated);
  381. if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll)) && installAll) {
  382. ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, !elevated);
  383. }
  384. }
  385. void Custom1Page_Show() {
  386. }
  387. void Custom2Page_Show() {
  388. HRESULT hr;
  389. LONGLONG installAll, elevated, includeLauncher;
  390. if (FAILED(BalGetNumericVariable(L"WixBundleElevated", &elevated))) {
  391. elevated = 0;
  392. }
  393. if (SUCCEEDED(BalGetNumericVariable(L"InstallAllUsers", &installAll))) {
  394. ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, installAll && !elevated);
  395. } else {
  396. installAll = 0;
  397. }
  398. if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) && includeLauncher) {
  399. ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, TRUE);
  400. } else {
  401. ThemeSendControlMessage(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, BM_SETCHECK, BST_UNCHECKED, 0);
  402. ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, FALSE);
  403. }
  404. LPWSTR targetDir = nullptr;
  405. hr = BalGetStringVariable(L"TargetDir", &targetDir);
  406. if (SUCCEEDED(hr) && targetDir && targetDir[0]) {
  407. ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir);
  408. StrFree(targetDir);
  409. } else if (SUCCEEDED(hr)) {
  410. StrFree(targetDir);
  411. targetDir = nullptr;
  412. LPWSTR defaultTargetDir = nullptr;
  413. hr = BalGetStringVariable(L"DefaultCustomTargetDir", &defaultTargetDir);
  414. if (SUCCEEDED(hr) && defaultTargetDir && !defaultTargetDir[0]) {
  415. StrFree(defaultTargetDir);
  416. defaultTargetDir = nullptr;
  417. hr = BalGetStringVariable(
  418. installAll ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir",
  419. &defaultTargetDir
  420. );
  421. }
  422. if (SUCCEEDED(hr) && defaultTargetDir) {
  423. if (defaultTargetDir[0] && SUCCEEDED(BalFormatString(defaultTargetDir, &targetDir))) {
  424. ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir);
  425. StrFree(targetDir);
  426. }
  427. StrFree(defaultTargetDir);
  428. }
  429. }
  430. }
  431. void ModifyPage_Show() {
  432. ThemeControlEnable(_theme, ID_REPAIR_BUTTON, !_suppressRepair);
  433. }
  434. void SuccessPage_Show() {
  435. // on the "Success" page, check if the restart or launch button should be enabled.
  436. BOOL showRestartButton = FALSE;
  437. BOOL launchTargetExists = FALSE;
  438. LOC_STRING *successText = nullptr;
  439. HRESULT hr = S_OK;
  440. if (_restartRequired) {
  441. if (BOOTSTRAPPER_RESTART_PROMPT == _command.restart) {
  442. showRestartButton = TRUE;
  443. }
  444. } else if (ThemeControlExists(_theme, ID_LAUNCH_BUTTON)) {
  445. launchTargetExists = BalStringVariableExists(PYBA_VARIABLE_LAUNCH_TARGET_PATH);
  446. }
  447. switch (_plannedAction) {
  448. case BOOTSTRAPPER_ACTION_INSTALL:
  449. hr = LocGetString(_wixLoc, L"#(loc.SuccessInstallMessage)", &successText);
  450. break;
  451. case BOOTSTRAPPER_ACTION_MODIFY:
  452. hr = LocGetString(_wixLoc, L"#(loc.SuccessModifyMessage)", &successText);
  453. break;
  454. case BOOTSTRAPPER_ACTION_REPAIR:
  455. hr = LocGetString(_wixLoc, L"#(loc.SuccessRepairMessage)", &successText);
  456. break;
  457. case BOOTSTRAPPER_ACTION_UNINSTALL:
  458. hr = LocGetString(_wixLoc, L"#(loc.SuccessRemoveMessage)", &successText);
  459. break;
  460. }
  461. if (successText) {
  462. LPWSTR formattedString = nullptr;
  463. BalFormatString(successText->wzText, &formattedString);
  464. if (formattedString) {
  465. ThemeSetTextControl(_theme, ID_SUCCESS_TEXT, formattedString);
  466. StrFree(formattedString);
  467. }
  468. }
  469. ThemeControlEnable(_theme, ID_LAUNCH_BUTTON, launchTargetExists && BOOTSTRAPPER_ACTION_UNINSTALL < _plannedAction);
  470. ThemeControlEnable(_theme, ID_SUCCESS_RESTART_TEXT, showRestartButton);
  471. ThemeControlEnable(_theme, ID_SUCCESS_RESTART_BUTTON, showRestartButton);
  472. }
  473. void FailurePage_Show() {
  474. // on the "Failure" page, show error message and check if the restart button should be enabled.
  475. // if there is a log file variable then we'll assume the log file exists.
  476. BOOL showLogLink = (_bundle.sczLogVariable && *_bundle.sczLogVariable);
  477. BOOL showErrorMessage = FALSE;
  478. BOOL showRestartButton = FALSE;
  479. if (FAILED(_hrFinal)) {
  480. LPWSTR unformattedText = nullptr;
  481. LPWSTR text = nullptr;
  482. // If we know the failure message, use that.
  483. if (_failedMessage && *_failedMessage) {
  484. StrAllocString(&unformattedText, _failedMessage, 0);
  485. } else {
  486. // try to get the error message from the error code.
  487. StrAllocFromError(&unformattedText, _hrFinal, nullptr);
  488. if (!unformattedText || !*unformattedText) {
  489. StrAllocFromError(&unformattedText, E_FAIL, nullptr);
  490. }
  491. }
  492. if (E_WIXSTDBA_CONDITION_FAILED == _hrFinal) {
  493. if (unformattedText) {
  494. StrAllocString(&text, unformattedText, 0);
  495. }
  496. } else {
  497. StrAllocFormatted(&text, L"0x%08x - %ls", _hrFinal, unformattedText);
  498. }
  499. if (text) {
  500. ThemeSetTextControl(_theme, ID_FAILURE_MESSAGE_TEXT, text);
  501. showErrorMessage = TRUE;
  502. }
  503. ReleaseStr(text);
  504. ReleaseStr(unformattedText);
  505. }
  506. if (_restartRequired && BOOTSTRAPPER_RESTART_PROMPT == _command.restart) {
  507. showRestartButton = TRUE;
  508. }
  509. ThemeControlEnable(_theme, ID_FAILURE_LOGFILE_LINK, showLogLink);
  510. ThemeControlEnable(_theme, ID_FAILURE_MESSAGE_TEXT, showErrorMessage);
  511. ThemeControlEnable(_theme, ID_FAILURE_RESTART_TEXT, showRestartButton);
  512. ThemeControlEnable(_theme, ID_FAILURE_RESTART_BUTTON, showRestartButton);
  513. }
  514. public: // IBootstrapperApplication
  515. virtual STDMETHODIMP OnStartup() {
  516. HRESULT hr = S_OK;
  517. DWORD dwUIThreadId = 0;
  518. // create UI thread
  519. _hUiThread = ::CreateThread(nullptr, 0, UiThreadProc, this, 0, &dwUIThreadId);
  520. if (!_hUiThread) {
  521. ExitWithLastError(hr, "Failed to create UI thread.");
  522. }
  523. LExit:
  524. return hr;
  525. }
  526. virtual STDMETHODIMP_(int) OnShutdown() {
  527. int nResult = IDNOACTION;
  528. // wait for UI thread to terminate
  529. if (_hUiThread) {
  530. ::WaitForSingleObject(_hUiThread, INFINITE);
  531. ReleaseHandle(_hUiThread);
  532. }
  533. // If a restart was required.
  534. if (_restartRequired && _allowRestart) {
  535. nResult = IDRESTART;
  536. }
  537. return nResult;
  538. }
  539. virtual STDMETHODIMP_(int) OnDetectRelatedBundle(
  540. __in LPCWSTR wzBundleId,
  541. __in BOOTSTRAPPER_RELATION_TYPE relationType,
  542. __in LPCWSTR /*wzBundleTag*/,
  543. __in BOOL fPerMachine,
  544. __in DWORD64 /*dw64Version*/,
  545. __in BOOTSTRAPPER_RELATED_OPERATION operation
  546. ) {
  547. BalInfoAddRelatedBundleAsPackage(&_bundle.packages, wzBundleId, relationType, fPerMachine);
  548. // Remember when our bundle would cause a downgrade.
  549. if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) {
  550. _downgrading = TRUE;
  551. }
  552. return CheckCanceled() ? IDCANCEL : IDOK;
  553. }
  554. virtual STDMETHODIMP_(void) OnDetectPackageComplete(
  555. __in LPCWSTR wzPackageId,
  556. __in HRESULT /*hrStatus*/,
  557. __in BOOTSTRAPPER_PACKAGE_STATE state
  558. ) { }
  559. virtual STDMETHODIMP_(void) OnDetectComplete(__in HRESULT hrStatus) {
  560. if (SUCCEEDED(hrStatus) && _baFunction) {
  561. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect complete BA function");
  562. _baFunction->OnDetectComplete();
  563. }
  564. if (SUCCEEDED(hrStatus)) {
  565. hrStatus = EvaluateConditions();
  566. }
  567. SetState(PYBA_STATE_DETECTED, hrStatus);
  568. // If we're not interacting with the user or we're doing a layout or we're just after a force restart
  569. // then automatically start planning.
  570. if (BOOTSTRAPPER_DISPLAY_FULL > _command.display ||
  571. BOOTSTRAPPER_ACTION_LAYOUT == _command.action ||
  572. BOOTSTRAPPER_ACTION_UNINSTALL == _command.action ||
  573. BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType) {
  574. if (SUCCEEDED(hrStatus)) {
  575. ::PostMessageW(_hWnd, WM_PYBA_PLAN_PACKAGES, 0, _command.action);
  576. }
  577. }
  578. }
  579. virtual STDMETHODIMP_(int) OnPlanRelatedBundle(
  580. __in_z LPCWSTR /*wzBundleId*/,
  581. __inout_z BOOTSTRAPPER_REQUEST_STATE* pRequestedState
  582. ) {
  583. return CheckCanceled() ? IDCANCEL : IDOK;
  584. }
  585. virtual STDMETHODIMP_(int) OnPlanPackageBegin(
  586. __in_z LPCWSTR wzPackageId,
  587. __inout BOOTSTRAPPER_REQUEST_STATE *pRequestState
  588. ) {
  589. HRESULT hr = S_OK;
  590. BAL_INFO_PACKAGE* pPackage = nullptr;
  591. if (_nextPackageAfterRestart) // after force restart, skip packages until after the package that caused the restart.
  592. {
  593. // After restart we need to finish the dependency registration for our package so allow the package
  594. // to go present.
  595. if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, _nextPackageAfterRestart, -1)) {
  596. // Do not allow a repair because that could put us in a perpetual restart loop.
  597. if (BOOTSTRAPPER_REQUEST_STATE_REPAIR == *pRequestState) {
  598. *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT;
  599. }
  600. ReleaseNullStr(_nextPackageAfterRestart); // no more skipping now.
  601. } else {
  602. // not the matching package, so skip it.
  603. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Skipping package: %ls, after restart because it was applied before the restart.", wzPackageId);
  604. *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE;
  605. }
  606. }
  607. return CheckCanceled() ? IDCANCEL : IDOK;
  608. }
  609. virtual STDMETHODIMP_(int) OnPlanMsiFeature(
  610. __in_z LPCWSTR wzPackageId,
  611. __in_z LPCWSTR wzFeatureId,
  612. __inout BOOTSTRAPPER_FEATURE_STATE* pRequestedState
  613. ) {
  614. LONGLONG install;
  615. if (wcscmp(wzFeatureId, L"AssociateFiles") == 0) {
  616. if (SUCCEEDED(_engine->GetVariableNumeric(L"AssociateFiles", &install)) && install) {
  617. *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL;
  618. } else {
  619. *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_ABSENT;
  620. }
  621. }
  622. return CheckCanceled() ? IDCANCEL : IDNOACTION;
  623. }
  624. virtual STDMETHODIMP_(void) OnPlanComplete(__in HRESULT hrStatus) {
  625. if (SUCCEEDED(hrStatus) && _baFunction) {
  626. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan complete BA function");
  627. _baFunction->OnPlanComplete();
  628. }
  629. SetState(PYBA_STATE_PLANNED, hrStatus);
  630. if (SUCCEEDED(hrStatus)) {
  631. ::PostMessageW(_hWnd, WM_PYBA_APPLY_PACKAGES, 0, 0);
  632. }
  633. _startedExecution = FALSE;
  634. _calculatedCacheProgress = 0;
  635. _calculatedExecuteProgress = 0;
  636. }
  637. virtual STDMETHODIMP_(int) OnCachePackageBegin(
  638. __in_z LPCWSTR wzPackageId,
  639. __in DWORD cCachePayloads,
  640. __in DWORD64 dw64PackageCacheSize
  641. ) {
  642. if (wzPackageId && *wzPackageId) {
  643. BAL_INFO_PACKAGE* pPackage = nullptr;
  644. HRESULT hr = BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage);
  645. LPCWSTR wz = (SUCCEEDED(hr) && pPackage->sczDisplayName) ? pPackage->sczDisplayName : wzPackageId;
  646. ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, wz);
  647. // If something started executing, leave it in the overall progress text.
  648. if (!_startedExecution) {
  649. ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz);
  650. }
  651. }
  652. return __super::OnCachePackageBegin(wzPackageId, cCachePayloads, dw64PackageCacheSize);
  653. }
  654. virtual STDMETHODIMP_(int) OnCacheAcquireProgress(
  655. __in_z LPCWSTR wzPackageOrContainerId,
  656. __in_z_opt LPCWSTR wzPayloadId,
  657. __in DWORD64 dw64Progress,
  658. __in DWORD64 dw64Total,
  659. __in DWORD dwOverallPercentage
  660. ) {
  661. WCHAR wzProgress[5] = { };
  662. #ifdef DEBUG
  663. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnCacheAcquireProgress() - container/package: %ls, payload: %ls, progress: %I64u, total: %I64u, overall progress: %u%%", wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage);
  664. #endif
  665. ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallPercentage);
  666. ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_TEXT, wzProgress);
  667. ThemeSetProgressControl(_theme, ID_CACHE_PROGRESS_BAR, dwOverallPercentage);
  668. _calculatedCacheProgress = dwOverallPercentage * PYBA_ACQUIRE_PERCENTAGE / 100;
  669. ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress);
  670. SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress);
  671. return __super::OnCacheAcquireProgress(wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage);
  672. }
  673. virtual STDMETHODIMP_(int) OnCacheAcquireComplete(
  674. __in_z LPCWSTR wzPackageOrContainerId,
  675. __in_z_opt LPCWSTR wzPayloadId,
  676. __in HRESULT hrStatus,
  677. __in int nRecommendation
  678. ) {
  679. SetProgressState(hrStatus);
  680. return __super::OnCacheAcquireComplete(wzPackageOrContainerId, wzPayloadId, hrStatus, nRecommendation);
  681. }
  682. virtual STDMETHODIMP_(int) OnCacheVerifyComplete(
  683. __in_z LPCWSTR wzPackageId,
  684. __in_z LPCWSTR wzPayloadId,
  685. __in HRESULT hrStatus,
  686. __in int nRecommendation
  687. ) {
  688. SetProgressState(hrStatus);
  689. return __super::OnCacheVerifyComplete(wzPackageId, wzPayloadId, hrStatus, nRecommendation);
  690. }
  691. virtual STDMETHODIMP_(void) OnCacheComplete(__in HRESULT /*hrStatus*/) {
  692. ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, L"");
  693. SetState(PYBA_STATE_CACHED, S_OK); // we always return success here and let OnApplyComplete() deal with the error.
  694. }
  695. virtual STDMETHODIMP_(int) OnError(
  696. __in BOOTSTRAPPER_ERROR_TYPE errorType,
  697. __in LPCWSTR wzPackageId,
  698. __in DWORD dwCode,
  699. __in_z LPCWSTR wzError,
  700. __in DWORD dwUIHint,
  701. __in DWORD /*cData*/,
  702. __in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/,
  703. __in int nRecommendation
  704. ) {
  705. int nResult = nRecommendation;
  706. LPWSTR sczError = nullptr;
  707. if (BOOTSTRAPPER_DISPLAY_EMBEDDED == _command.display) {
  708. HRESULT hr = _engine->SendEmbeddedError(dwCode, wzError, dwUIHint, &nResult);
  709. if (FAILED(hr)) {
  710. nResult = IDERROR;
  711. }
  712. } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
  713. // If this is an authentication failure, let the engine try to handle it for us.
  714. if (BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER == errorType || BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY == errorType) {
  715. nResult = IDTRYAGAIN;
  716. } else // show a generic error message box.
  717. {
  718. BalRetryErrorOccurred(wzPackageId, dwCode);
  719. if (!_showingInternalUIThisPackage) {
  720. // If no error message was provided, use the error code to try and get an error message.
  721. if (!wzError || !*wzError || BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER != errorType) {
  722. HRESULT hr = StrAllocFromError(&sczError, dwCode, nullptr);
  723. if (FAILED(hr) || !sczError || !*sczError) {
  724. StrAllocFormatted(&sczError, L"0x%x", dwCode);
  725. }
  726. }
  727. nResult = ::MessageBoxW(_hWnd, sczError ? sczError : wzError, _theme->sczCaption, dwUIHint);
  728. }
  729. }
  730. SetProgressState(HRESULT_FROM_WIN32(dwCode));
  731. } else {
  732. // just take note of the error code and let things continue.
  733. BalRetryErrorOccurred(wzPackageId, dwCode);
  734. }
  735. ReleaseStr(sczError);
  736. return nResult;
  737. }
  738. virtual STDMETHODIMP_(int) OnExecuteMsiMessage(
  739. __in_z LPCWSTR wzPackageId,
  740. __in INSTALLMESSAGE mt,
  741. __in UINT uiFlags,
  742. __in_z LPCWSTR wzMessage,
  743. __in DWORD cData,
  744. __in_ecount_z_opt(cData) LPCWSTR* rgwzData,
  745. __in int nRecommendation
  746. ) {
  747. #ifdef DEBUG
  748. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteMsiMessage() - package: %ls, message: %ls", wzPackageId, wzMessage);
  749. #endif
  750. if (BOOTSTRAPPER_DISPLAY_FULL == _command.display && (INSTALLMESSAGE_WARNING == mt || INSTALLMESSAGE_USER == mt)) {
  751. int nResult = ::MessageBoxW(_hWnd, wzMessage, _theme->sczCaption, uiFlags);
  752. return nResult;
  753. }
  754. if (INSTALLMESSAGE_ACTIONSTART == mt) {
  755. ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, wzMessage);
  756. }
  757. return __super::OnExecuteMsiMessage(wzPackageId, mt, uiFlags, wzMessage, cData, rgwzData, nRecommendation);
  758. }
  759. virtual STDMETHODIMP_(int) OnProgress(__in DWORD dwProgressPercentage, __in DWORD dwOverallProgressPercentage) {
  760. WCHAR wzProgress[5] = { };
  761. #ifdef DEBUG
  762. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnProgress() - progress: %u%%, overall progress: %u%%", dwProgressPercentage, dwOverallProgressPercentage);
  763. #endif
  764. ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage);
  765. ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_TEXT, wzProgress);
  766. ThemeSetProgressControl(_theme, ID_OVERALL_PROGRESS_BAR, dwOverallProgressPercentage);
  767. SetTaskbarButtonProgress(dwOverallProgressPercentage);
  768. return __super::OnProgress(dwProgressPercentage, dwOverallProgressPercentage);
  769. }
  770. virtual STDMETHODIMP_(int) OnExecutePackageBegin(__in_z LPCWSTR wzPackageId, __in BOOL fExecute) {
  771. LPWSTR sczFormattedString = nullptr;
  772. _startedExecution = TRUE;
  773. if (wzPackageId && *wzPackageId) {
  774. BAL_INFO_PACKAGE* pPackage = nullptr;
  775. BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage);
  776. LPCWSTR wz = wzPackageId;
  777. if (pPackage) {
  778. LOC_STRING* pLocString = nullptr;
  779. switch (pPackage->type) {
  780. case BAL_INFO_PACKAGE_TYPE_BUNDLE_ADDON:
  781. LocGetString(_wixLoc, L"#(loc.ExecuteAddonRelatedBundleMessage)", &pLocString);
  782. break;
  783. case BAL_INFO_PACKAGE_TYPE_BUNDLE_PATCH:
  784. LocGetString(_wixLoc, L"#(loc.ExecutePatchRelatedBundleMessage)", &pLocString);
  785. break;
  786. case BAL_INFO_PACKAGE_TYPE_BUNDLE_UPGRADE:
  787. LocGetString(_wixLoc, L"#(loc.ExecuteUpgradeRelatedBundleMessage)", &pLocString);
  788. break;
  789. }
  790. if (pLocString) {
  791. // If the wix developer is showing a hidden variable in the UI, then obviously they don't care about keeping it safe
  792. // so don't go down the rabbit hole of making sure that this is securely freed.
  793. BalFormatString(pLocString->wzText, &sczFormattedString);
  794. }
  795. wz = sczFormattedString ? sczFormattedString : pPackage->sczDisplayName ? pPackage->sczDisplayName : wzPackageId;
  796. }
  797. _showingInternalUIThisPackage = pPackage && pPackage->fDisplayInternalUI;
  798. ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, wz);
  799. ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz);
  800. } else {
  801. _showingInternalUIThisPackage = FALSE;
  802. }
  803. ReleaseStr(sczFormattedString);
  804. return __super::OnExecutePackageBegin(wzPackageId, fExecute);
  805. }
  806. virtual int __stdcall OnExecuteProgress(
  807. __in_z LPCWSTR wzPackageId,
  808. __in DWORD dwProgressPercentage,
  809. __in DWORD dwOverallProgressPercentage
  810. ) {
  811. WCHAR wzProgress[8] = { };
  812. #ifdef DEBUG
  813. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteProgress() - package: %ls, progress: %u%%, overall progress: %u%%", wzPackageId, dwProgressPercentage, dwOverallProgressPercentage);
  814. #endif
  815. ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage);
  816. ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_TEXT, wzProgress);
  817. ThemeSetProgressControl(_theme, ID_EXECUTE_PROGRESS_BAR, dwOverallProgressPercentage);
  818. _calculatedExecuteProgress = dwOverallProgressPercentage * (100 - PYBA_ACQUIRE_PERCENTAGE) / 100;
  819. ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress);
  820. SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress);
  821. return __super::OnExecuteProgress(wzPackageId, dwProgressPercentage, dwOverallProgressPercentage);
  822. }
  823. virtual STDMETHODIMP_(int) OnExecutePackageComplete(
  824. __in_z LPCWSTR wzPackageId,
  825. __in HRESULT hrExitCode,
  826. __in BOOTSTRAPPER_APPLY_RESTART restart,
  827. __in int nRecommendation
  828. ) {
  829. SetProgressState(hrExitCode);
  830. if (_wcsnicmp(wzPackageId, L"path_", 5) == 0 && SUCCEEDED(hrExitCode)) {
  831. SendMessageTimeoutW(
  832. HWND_BROADCAST,
  833. WM_SETTINGCHANGE,
  834. 0,
  835. reinterpret_cast<LPARAM>(L"Environment"),
  836. SMTO_ABORTIFHUNG,
  837. 1000,
  838. nullptr
  839. );
  840. }
  841. int nResult = __super::OnExecutePackageComplete(wzPackageId, hrExitCode, restart, nRecommendation);
  842. return nResult;
  843. }
  844. virtual STDMETHODIMP_(void) OnExecuteComplete(__in HRESULT hrStatus) {
  845. ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"");
  846. ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"");
  847. ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"");
  848. ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE); // no more cancel.
  849. SetState(PYBA_STATE_EXECUTED, S_OK); // we always return success here and let OnApplyComplete() deal with the error.
  850. SetProgressState(hrStatus);
  851. }
  852. virtual STDMETHODIMP_(int) OnResolveSource(
  853. __in_z LPCWSTR wzPackageOrContainerId,
  854. __in_z_opt LPCWSTR wzPayloadId,
  855. __in_z LPCWSTR wzLocalSource,
  856. __in_z_opt LPCWSTR wzDownloadSource
  857. ) {
  858. int nResult = IDERROR; // assume we won't resolve source and that is unexpected.
  859. if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
  860. if (wzDownloadSource) {
  861. nResult = IDDOWNLOAD;
  862. } else {
  863. // prompt to change the source location.
  864. OPENFILENAMEW ofn = { };
  865. WCHAR wzFile[MAX_PATH] = { };
  866. ::StringCchCopyW(wzFile, countof(wzFile), wzLocalSource);
  867. ofn.lStructSize = sizeof(ofn);
  868. ofn.hwndOwner = _hWnd;
  869. ofn.lpstrFile = wzFile;
  870. ofn.nMaxFile = countof(wzFile);
  871. ofn.lpstrFilter = L"All Files\0*.*\0";
  872. ofn.nFilterIndex = 1;
  873. ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
  874. ofn.lpstrTitle = _theme->sczCaption;
  875. if (::GetOpenFileNameW(&ofn)) {
  876. HRESULT hr = _engine->SetLocalSource(wzPackageOrContainerId, wzPayloadId, ofn.lpstrFile);
  877. nResult = SUCCEEDED(hr) ? IDRETRY : IDERROR;
  878. } else {
  879. nResult = IDCANCEL;
  880. }
  881. }
  882. } else if (wzDownloadSource) {
  883. // If doing a non-interactive install and download source is available, let's try downloading the package silently
  884. nResult = IDDOWNLOAD;
  885. }
  886. // else there's nothing more we can do in non-interactive mode
  887. return CheckCanceled() ? IDCANCEL : nResult;
  888. }
  889. virtual STDMETHODIMP_(int) OnApplyComplete(__in HRESULT hrStatus, __in BOOTSTRAPPER_APPLY_RESTART restart) {
  890. _restartResult = restart; // remember the restart result so we return the correct error code no matter what the user chooses to do in the UI.
  891. // If a restart was encountered and we are not suppressing restarts, then restart is required.
  892. _restartRequired = (BOOTSTRAPPER_APPLY_RESTART_NONE != restart && BOOTSTRAPPER_RESTART_NEVER < _command.restart);
  893. // If a restart is required and we're not displaying a UI or we are not supposed to prompt for restart then allow the restart.
  894. _allowRestart = _restartRequired && (BOOTSTRAPPER_DISPLAY_FULL > _command.display || BOOTSTRAPPER_RESTART_PROMPT < _command.restart);
  895. // If we are showing UI, wait a beat before moving to the final screen.
  896. if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
  897. ::Sleep(250);
  898. }
  899. SetState(PYBA_STATE_APPLIED, hrStatus);
  900. SetTaskbarButtonProgress(100); // show full progress bar, green, yellow, or red
  901. return IDNOACTION;
  902. }
  903. virtual STDMETHODIMP_(void) OnLaunchApprovedExeComplete(__in HRESULT hrStatus, __in DWORD /*processId*/) {
  904. if (HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) == hrStatus) {
  905. //try with ShelExec next time
  906. OnClickLaunchButton();
  907. } else {
  908. ::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
  909. }
  910. }
  911. private:
  912. //
  913. // UiThreadProc - entrypoint for UI thread.
  914. //
  915. static DWORD WINAPI UiThreadProc(__in LPVOID pvContext) {
  916. HRESULT hr = S_OK;
  917. PythonBootstrapperApplication* pThis = (PythonBootstrapperApplication*)pvContext;
  918. BOOL comInitialized = FALSE;
  919. BOOL ret = FALSE;
  920. MSG msg = { };
  921. // Initialize COM and theme.
  922. hr = ::CoInitialize(nullptr);
  923. BalExitOnFailure(hr, "Failed to initialize COM.");
  924. comInitialized = TRUE;
  925. hr = ThemeInitialize(pThis->_hModule);
  926. BalExitOnFailure(hr, "Failed to initialize theme manager.");
  927. hr = pThis->InitializeData();
  928. BalExitOnFailure(hr, "Failed to initialize data in bootstrapper application.");
  929. // Create main window.
  930. pThis->InitializeTaskbarButton();
  931. hr = pThis->CreateMainWindow();
  932. BalExitOnFailure(hr, "Failed to create main window.");
  933. if (FAILED(pThis->_hrFinal)) {
  934. pThis->SetState(PYBA_STATE_FAILED, hr);
  935. ::PostMessageW(pThis->_hWnd, WM_PYBA_SHOW_FAILURE, 0, 0);
  936. } else {
  937. // Okay, we're ready for packages now.
  938. pThis->SetState(PYBA_STATE_INITIALIZED, hr);
  939. ::PostMessageW(pThis->_hWnd, BOOTSTRAPPER_ACTION_HELP == pThis->_command.action ? WM_PYBA_SHOW_HELP : WM_PYBA_DETECT_PACKAGES, 0, 0);
  940. }
  941. // message pump
  942. while (0 != (ret = ::GetMessageW(&msg, nullptr, 0, 0))) {
  943. if (-1 == ret) {
  944. hr = E_UNEXPECTED;
  945. BalExitOnFailure(hr, "Unexpected return value from message pump.");
  946. } else if (!ThemeHandleKeyboardMessage(pThis->_theme, msg.hwnd, &msg)) {
  947. ::TranslateMessage(&msg);
  948. ::DispatchMessageW(&msg);
  949. }
  950. }
  951. // Succeeded thus far, check to see if anything went wrong while actually
  952. // executing changes.
  953. if (FAILED(pThis->_hrFinal)) {
  954. hr = pThis->_hrFinal;
  955. } else if (pThis->CheckCanceled()) {
  956. hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT);
  957. }
  958. LExit:
  959. // destroy main window
  960. pThis->DestroyMainWindow();
  961. // initiate engine shutdown
  962. DWORD dwQuit = HRESULT_CODE(hr);
  963. if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == pThis->_restartResult) {
  964. dwQuit = ERROR_SUCCESS_REBOOT_INITIATED;
  965. } else if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == pThis->_restartResult) {
  966. dwQuit = ERROR_SUCCESS_REBOOT_REQUIRED;
  967. }
  968. pThis->_engine->Quit(dwQuit);
  969. ReleaseTheme(pThis->_theme);
  970. ThemeUninitialize();
  971. // uninitialize COM
  972. if (comInitialized) {
  973. ::CoUninitialize();
  974. }
  975. return hr;
  976. }
  977. //
  978. // InitializeData - initializes all the package information.
  979. //
  980. HRESULT InitializeData() {
  981. HRESULT hr = S_OK;
  982. LPWSTR sczModulePath = nullptr;
  983. IXMLDOMDocument *pixdManifest = nullptr;
  984. hr = BalManifestLoad(_hModule, &pixdManifest);
  985. BalExitOnFailure(hr, "Failed to load bootstrapper application manifest.");
  986. hr = ParseOverridableVariablesFromXml(pixdManifest);
  987. BalExitOnFailure(hr, "Failed to read overridable variables.");
  988. hr = ProcessCommandLine(&_language);
  989. ExitOnFailure(hr, "Unknown commandline parameters.");
  990. hr = PathRelativeToModule(&sczModulePath, nullptr, _hModule);
  991. BalExitOnFailure(hr, "Failed to get module path.");
  992. hr = LoadLocalization(sczModulePath, _language);
  993. ExitOnFailure(hr, "Failed to load localization.");
  994. hr = LoadTheme(sczModulePath, _language);
  995. ExitOnFailure(hr, "Failed to load theme.");
  996. hr = BalInfoParseFromXml(&_bundle, pixdManifest);
  997. BalExitOnFailure(hr, "Failed to load bundle information.");
  998. hr = BalConditionsParseFromXml(&_conditions, pixdManifest, _wixLoc);
  999. BalExitOnFailure(hr, "Failed to load conditions from XML.");
  1000. hr = LoadBootstrapperBAFunctions();
  1001. BalExitOnFailure(hr, "Failed to load bootstrapper functions.");
  1002. GetBundleFileVersion();
  1003. // don't fail if we couldn't get the version info; best-effort only
  1004. LExit:
  1005. ReleaseObject(pixdManifest);
  1006. ReleaseStr(sczModulePath);
  1007. return hr;
  1008. }
  1009. //
  1010. // ProcessCommandLine - process the provided command line arguments.
  1011. //
  1012. HRESULT ProcessCommandLine(__inout LPWSTR* psczLanguage) {
  1013. HRESULT hr = S_OK;
  1014. int argc = 0;
  1015. LPWSTR* argv = nullptr;
  1016. LPWSTR sczVariableName = nullptr;
  1017. LPWSTR sczVariableValue = nullptr;
  1018. if (_command.wzCommandLine && *_command.wzCommandLine) {
  1019. argv = ::CommandLineToArgvW(_command.wzCommandLine, &argc);
  1020. ExitOnNullWithLastError(argv, hr, "Failed to get command line.");
  1021. for (int i = 0; i < argc; ++i) {
  1022. if (argv[i][0] == L'-' || argv[i][0] == L'/') {
  1023. if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1)) {
  1024. if (i + 1 >= argc) {
  1025. hr = E_INVALIDARG;
  1026. BalExitOnFailure(hr, "Must specify a language.");
  1027. }
  1028. ++i;
  1029. hr = StrAllocString(psczLanguage, &argv[i][0], 0);
  1030. BalExitOnFailure(hr, "Failed to copy language.");
  1031. }
  1032. } else if (_overridableVariables) {
  1033. int value;
  1034. const wchar_t* pwc = wcschr(argv[i], L'=');
  1035. if (pwc) {
  1036. hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]);
  1037. BalExitOnFailure(hr, "Failed to copy variable name.");
  1038. hr = DictKeyExists(_overridableVariables, sczVariableName);
  1039. if (E_NOTFOUND == hr) {
  1040. BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Ignoring attempt to set non-overridable variable: '%ls'.", sczVariableName);
  1041. hr = S_OK;
  1042. continue;
  1043. }
  1044. ExitOnFailure(hr, "Failed to check the dictionary of overridable variables.");
  1045. hr = StrAllocString(&sczVariableValue, ++pwc, 0);
  1046. BalExitOnFailure(hr, "Failed to copy variable value.");
  1047. if (::StrToIntEx(sczVariableValue, STIF_DEFAULT, &value)) {
  1048. hr = _engine->SetVariableNumeric(sczVariableName, value);
  1049. } else {
  1050. hr = _engine->SetVariableString(sczVariableName, sczVariableValue);
  1051. }
  1052. BalExitOnFailure(hr, "Failed to set variable.");
  1053. } else {
  1054. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]);
  1055. }
  1056. }
  1057. }
  1058. }
  1059. LExit:
  1060. if (argv) {
  1061. ::LocalFree(argv);
  1062. }
  1063. ReleaseStr(sczVariableName);
  1064. ReleaseStr(sczVariableValue);
  1065. return hr;
  1066. }
  1067. HRESULT LoadLocalization(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) {
  1068. HRESULT hr = S_OK;
  1069. LPWSTR sczLocPath = nullptr;
  1070. LPCWSTR wzLocFileName = L"Default.wxl";
  1071. hr = LocProbeForFile(wzModulePath, wzLocFileName, wzLanguage, &sczLocPath);
  1072. BalExitOnFailure2(hr, "Failed to probe for loc file: %ls in path: %ls", wzLocFileName, wzModulePath);
  1073. hr = LocLoadFromFile(sczLocPath, &_wixLoc);
  1074. BalExitOnFailure1(hr, "Failed to load loc file from path: %ls", sczLocPath);
  1075. if (WIX_LOCALIZATION_LANGUAGE_NOT_SET != _wixLoc->dwLangId) {
  1076. ::SetThreadLocale(_wixLoc->dwLangId);
  1077. }
  1078. hr = StrAllocString(&_confirmCloseMessage, L"#(loc.ConfirmCancelMessage)", 0);
  1079. ExitOnFailure(hr, "Failed to initialize confirm message loc identifier.");
  1080. hr = LocLocalizeString(_wixLoc, &_confirmCloseMessage);
  1081. BalExitOnFailure1(hr, "Failed to localize confirm close message: %ls", _confirmCloseMessage);
  1082. LExit:
  1083. ReleaseStr(sczLocPath);
  1084. return hr;
  1085. }
  1086. HRESULT LoadTheme(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) {
  1087. HRESULT hr = S_OK;
  1088. LPWSTR sczThemePath = nullptr;
  1089. LPCWSTR wzThemeFileName = L"Default.thm";
  1090. LPWSTR sczCaption = nullptr;
  1091. hr = LocProbeForFile(wzModulePath, wzThemeFileName, wzLanguage, &sczThemePath);
  1092. BalExitOnFailure2(hr, "Failed to probe for theme file: %ls in path: %ls", wzThemeFileName, wzModulePath);
  1093. hr = ThemeLoadFromFile(sczThemePath, &_theme);
  1094. BalExitOnFailure1(hr, "Failed to load theme from path: %ls", sczThemePath);
  1095. hr = ThemeLocalize(_theme, _wixLoc);
  1096. BalExitOnFailure1(hr, "Failed to localize theme: %ls", sczThemePath);
  1097. // Update the caption if there are any formatted strings in it.
  1098. // If the wix developer is showing a hidden variable in the UI, then
  1099. // obviously they don't care about keeping it safe so don't go down the
  1100. // rabbit hole of making sure that this is securely freed.
  1101. hr = BalFormatString(_theme->sczCaption, &sczCaption);
  1102. if (SUCCEEDED(hr)) {
  1103. ThemeUpdateCaption(_theme, sczCaption);
  1104. }
  1105. LExit:
  1106. ReleaseStr(sczCaption);
  1107. ReleaseStr(sczThemePath);
  1108. return hr;
  1109. }
  1110. HRESULT ParseOverridableVariablesFromXml(__in IXMLDOMDocument* pixdManifest) {
  1111. HRESULT hr = S_OK;
  1112. IXMLDOMNode* pNode = nullptr;
  1113. IXMLDOMNodeList* pNodes = nullptr;
  1114. DWORD cNodes = 0;
  1115. LPWSTR scz = nullptr;
  1116. BOOL hidden = FALSE;
  1117. // get the list of variables users can override on the command line
  1118. hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes);
  1119. if (S_FALSE == hr) {
  1120. ExitFunction1(hr = S_OK);
  1121. }
  1122. ExitOnFailure(hr, "Failed to select overridable variable nodes.");
  1123. hr = pNodes->get_length((long*)&cNodes);
  1124. ExitOnFailure(hr, "Failed to get overridable variable node count.");
  1125. if (cNodes) {
  1126. hr = DictCreateStringList(&_overridableVariables, 32, DICT_FLAG_NONE);
  1127. ExitOnFailure(hr, "Failed to create the string dictionary.");
  1128. for (DWORD i = 0; i < cNodes; ++i) {
  1129. hr = XmlNextElement(pNodes, &pNode, nullptr);
  1130. ExitOnFailure(hr, "Failed to get next node.");
  1131. // @Name
  1132. hr = XmlGetAttributeEx(pNode, L"Name", &scz);
  1133. ExitOnFailure(hr, "Failed to get @Name.");
  1134. hr = XmlGetYesNoAttribute(pNode, L"Hidden", &hidden);
  1135. if (!hidden) {
  1136. hr = DictAddKey(_overridableVariables, scz);
  1137. ExitOnFailure1(hr, "Failed to add \"%ls\" to the string dictionary.", scz);
  1138. }
  1139. // prepare next iteration
  1140. ReleaseNullObject(pNode);
  1141. }
  1142. }
  1143. LExit:
  1144. ReleaseObject(pNode);
  1145. ReleaseObject(pNodes);
  1146. ReleaseStr(scz);
  1147. return hr;
  1148. }
  1149. //
  1150. // Get the file version of the bootstrapper and record in bootstrapper log file
  1151. //
  1152. HRESULT GetBundleFileVersion() {
  1153. HRESULT hr = S_OK;
  1154. ULARGE_INTEGER uliVersion = { };
  1155. LPWSTR sczCurrentPath = nullptr;
  1156. hr = PathForCurrentProcess(&sczCurrentPath, nullptr);
  1157. BalExitOnFailure(hr, "Failed to get bundle path.");
  1158. hr = FileVersion(sczCurrentPath, &uliVersion.HighPart, &uliVersion.LowPart);
  1159. BalExitOnFailure(hr, "Failed to get bundle file version.");
  1160. hr = _engine->SetVariableVersion(PYBA_VARIABLE_BUNDLE_FILE_VERSION, uliVersion.QuadPart);
  1161. BalExitOnFailure(hr, "Failed to set WixBundleFileVersion variable.");
  1162. LExit:
  1163. ReleaseStr(sczCurrentPath);
  1164. return hr;
  1165. }
  1166. //
  1167. // CreateMainWindow - creates the main install window.
  1168. //
  1169. HRESULT CreateMainWindow() {
  1170. HRESULT hr = S_OK;
  1171. HICON hIcon = reinterpret_cast<HICON>(_theme->hIcon);
  1172. WNDCLASSW wc = { };
  1173. DWORD dwWindowStyle = 0;
  1174. int x = CW_USEDEFAULT;
  1175. int y = CW_USEDEFAULT;
  1176. POINT ptCursor = { };
  1177. HMONITOR hMonitor = nullptr;
  1178. MONITORINFO mi = { };
  1179. // If the theme did not provide an icon, try using the icon from the bundle engine.
  1180. if (!hIcon) {
  1181. HMODULE hBootstrapperEngine = ::GetModuleHandleW(nullptr);
  1182. if (hBootstrapperEngine) {
  1183. hIcon = ::LoadIconW(hBootstrapperEngine, MAKEINTRESOURCEW(1));
  1184. }
  1185. }
  1186. // Register the window class and create the window.
  1187. wc.lpfnWndProc = PythonBootstrapperApplication::WndProc;
  1188. wc.hInstance = _hModule;
  1189. wc.hIcon = hIcon;
  1190. wc.hCursor = ::LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW);
  1191. wc.hbrBackground = _theme->rgFonts[_theme->dwFontId].hBackground;
  1192. wc.lpszMenuName = nullptr;
  1193. wc.lpszClassName = PYBA_WINDOW_CLASS;
  1194. if (!::RegisterClassW(&wc)) {
  1195. ExitWithLastError(hr, "Failed to register window.");
  1196. }
  1197. _registered = TRUE;
  1198. // Calculate the window style based on the theme style and command display value.
  1199. dwWindowStyle = _theme->dwStyle;
  1200. if (BOOTSTRAPPER_DISPLAY_NONE >= _command.display) {
  1201. dwWindowStyle &= ~WS_VISIBLE;
  1202. }
  1203. // Don't show the window if there is a splash screen (it will be made visible when the splash screen is hidden)
  1204. if (::IsWindow(_command.hwndSplashScreen)) {
  1205. dwWindowStyle &= ~WS_VISIBLE;
  1206. }
  1207. // Center the window on the monitor with the mouse.
  1208. if (::GetCursorPos(&ptCursor)) {
  1209. hMonitor = ::MonitorFromPoint(ptCursor, MONITOR_DEFAULTTONEAREST);
  1210. if (hMonitor) {
  1211. mi.cbSize = sizeof(mi);
  1212. if (::GetMonitorInfoW(hMonitor, &mi)) {
  1213. x = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - _theme->nWidth) / 2;
  1214. y = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - _theme->nHeight) / 2;
  1215. }
  1216. }
  1217. }
  1218. _hWnd = ::CreateWindowExW(
  1219. 0,
  1220. wc.lpszClassName,
  1221. _theme->sczCaption,
  1222. dwWindowStyle,
  1223. x,
  1224. y,
  1225. _theme->nWidth,
  1226. _theme->nHeight,
  1227. HWND_DESKTOP,
  1228. nullptr,
  1229. _hModule,
  1230. this
  1231. );
  1232. ExitOnNullWithLastError(_hWnd, hr, "Failed to create window.");
  1233. hr = S_OK;
  1234. LExit:
  1235. return hr;
  1236. }
  1237. //
  1238. // InitializeTaskbarButton - initializes taskbar button for progress.
  1239. //
  1240. void InitializeTaskbarButton() {
  1241. HRESULT hr = S_OK;
  1242. hr = ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), reinterpret_cast<LPVOID*>(&_taskbarList));
  1243. if (REGDB_E_CLASSNOTREG == hr) {
  1244. // not supported before Windows 7
  1245. ExitFunction1(hr = S_OK);
  1246. }
  1247. BalExitOnFailure(hr, "Failed to create ITaskbarList3. Continuing.");
  1248. _taskbarButtonCreatedMessage = ::RegisterWindowMessageW(L"TaskbarButtonCreated");
  1249. BalExitOnNullWithLastError(_taskbarButtonCreatedMessage, hr, "Failed to get TaskbarButtonCreated message. Continuing.");
  1250. LExit:
  1251. return;
  1252. }
  1253. //
  1254. // DestroyMainWindow - clean up all the window registration.
  1255. //
  1256. void DestroyMainWindow() {
  1257. if (::IsWindow(_hWnd)) {
  1258. ::DestroyWindow(_hWnd);
  1259. _hWnd = nullptr;
  1260. _taskbarButtonOK = FALSE;
  1261. }
  1262. if (_registered) {
  1263. ::UnregisterClassW(PYBA_WINDOW_CLASS, _hModule);
  1264. _registered = FALSE;
  1265. }
  1266. }
  1267. //
  1268. // WndProc - standard windows message handler.
  1269. //
  1270. static LRESULT CALLBACK WndProc(
  1271. __in HWND hWnd,
  1272. __in UINT uMsg,
  1273. __in WPARAM wParam,
  1274. __in LPARAM lParam
  1275. ) {
  1276. #pragma warning(suppress:4312)
  1277. auto pBA = reinterpret_cast<PythonBootstrapperApplication*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
  1278. switch (uMsg) {
  1279. case WM_NCCREATE: {
  1280. LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
  1281. pBA = reinterpret_cast<PythonBootstrapperApplication*>(lpcs->lpCreateParams);
  1282. #pragma warning(suppress:4244)
  1283. ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
  1284. break;
  1285. }
  1286. case WM_NCDESTROY: {
  1287. LRESULT lres = ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam);
  1288. ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
  1289. return lres;
  1290. }
  1291. case WM_CREATE:
  1292. if (!pBA->OnCreate(hWnd)) {
  1293. return -1;
  1294. }
  1295. break;
  1296. case WM_QUERYENDSESSION:
  1297. return IDCANCEL != pBA->OnSystemShutdown(static_cast<DWORD>(lParam), IDCANCEL);
  1298. case WM_CLOSE:
  1299. // If the user chose not to close, do *not* let the default window proc handle the message.
  1300. if (!pBA->OnClose()) {
  1301. return 0;
  1302. }
  1303. break;
  1304. case WM_DESTROY:
  1305. ::PostQuitMessage(0);
  1306. break;
  1307. case WM_PAINT: __fallthrough;
  1308. case WM_ERASEBKGND:
  1309. if (pBA && pBA->_suppressPaint) {
  1310. return TRUE;
  1311. }
  1312. break;
  1313. case WM_PYBA_SHOW_HELP:
  1314. pBA->OnShowHelp();
  1315. return 0;
  1316. case WM_PYBA_DETECT_PACKAGES:
  1317. pBA->OnDetect();
  1318. return 0;
  1319. case WM_PYBA_PLAN_PACKAGES:
  1320. pBA->OnPlan(static_cast<BOOTSTRAPPER_ACTION>(lParam));
  1321. return 0;
  1322. case WM_PYBA_APPLY_PACKAGES:
  1323. pBA->OnApply();
  1324. return 0;
  1325. case WM_PYBA_CHANGE_STATE:
  1326. pBA->OnChangeState(static_cast<PYBA_STATE>(lParam));
  1327. return 0;
  1328. case WM_PYBA_SHOW_FAILURE:
  1329. pBA->OnShowFailure();
  1330. return 0;
  1331. case WM_COMMAND:
  1332. switch (LOWORD(wParam)) {
  1333. // Customize commands
  1334. // Success/failure commands
  1335. case ID_LAUNCH_BUTTON:
  1336. pBA->OnClickLaunchButton();
  1337. return 0;
  1338. case ID_SUCCESS_RESTART_BUTTON: __fallthrough;
  1339. case ID_FAILURE_RESTART_BUTTON:
  1340. pBA->OnClickRestartButton();
  1341. return 0;
  1342. case IDCANCEL: __fallthrough;
  1343. case ID_INSTALL_CANCEL_BUTTON: __fallthrough;
  1344. case ID_CUSTOM1_CANCEL_BUTTON: __fallthrough;
  1345. case ID_CUSTOM2_CANCEL_BUTTON: __fallthrough;
  1346. case ID_MODIFY_CANCEL_BUTTON: __fallthrough;
  1347. case ID_PROGRESS_CANCEL_BUTTON: __fallthrough;
  1348. case ID_SUCCESS_CANCEL_BUTTON: __fallthrough;
  1349. case ID_FAILURE_CANCEL_BUTTON: __fallthrough;
  1350. case ID_CLOSE_BUTTON:
  1351. pBA->OnCommand(ID_CLOSE_BUTTON);
  1352. return 0;
  1353. default:
  1354. pBA->OnCommand((CONTROL_ID)LOWORD(wParam));
  1355. }
  1356. break;
  1357. case WM_NOTIFY:
  1358. if (lParam) {
  1359. LPNMHDR pnmhdr = reinterpret_cast<LPNMHDR>(lParam);
  1360. switch (pnmhdr->code) {
  1361. case NM_CLICK: __fallthrough;
  1362. case NM_RETURN:
  1363. switch (static_cast<DWORD>(pnmhdr->idFrom)) {
  1364. case ID_FAILURE_LOGFILE_LINK:
  1365. pBA->OnClickLogFileLink();
  1366. return 1;
  1367. }
  1368. }
  1369. }
  1370. break;
  1371. case WM_CTLCOLORBTN:
  1372. if (pBA) {
  1373. HWND button = (HWND)lParam;
  1374. DWORD style = GetWindowLong(button, GWL_STYLE) & BS_TYPEMASK;
  1375. if (style == BS_COMMANDLINK || style == BS_DEFCOMMANDLINK) {
  1376. return (LRESULT)pBA->_theme->rgFonts[pBA->_theme->dwFontId].hBackground;
  1377. }
  1378. }
  1379. break;
  1380. }
  1381. if (pBA && pBA->_taskbarList && uMsg == pBA->_taskbarButtonCreatedMessage) {
  1382. pBA->_taskbarButtonOK = TRUE;
  1383. return 0;
  1384. }
  1385. return ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam);
  1386. }
  1387. //
  1388. // OnCreate - finishes loading the theme.
  1389. //
  1390. BOOL OnCreate(__in HWND hWnd) {
  1391. HRESULT hr = S_OK;
  1392. hr = ThemeLoadControls(_theme, hWnd, CONTROL_ID_NAMES, countof(CONTROL_ID_NAMES));
  1393. BalExitOnFailure(hr, "Failed to load theme controls.");
  1394. C_ASSERT(COUNT_PAGE == countof(PAGE_NAMES));
  1395. C_ASSERT(countof(_pageIds) == countof(PAGE_NAMES));
  1396. ThemeGetPageIds(_theme, PAGE_NAMES, _pageIds, countof(_pageIds));
  1397. // Initialize the text on all "application" (non-page) controls.
  1398. for (DWORD i = 0; i < _theme->cControls; ++i) {
  1399. THEME_CONTROL* pControl = _theme->rgControls + i;
  1400. LPWSTR text = nullptr;
  1401. LPWSTR name = nullptr;
  1402. LOC_STRING *locText = nullptr;
  1403. // If a command link has a note, then add it.
  1404. if ((pControl->dwStyle & BS_TYPEMASK) == BS_COMMANDLINK ||
  1405. (pControl->dwStyle & BS_TYPEMASK) == BS_DEFCOMMANDLINK) {
  1406. hr = StrAllocFormatted(&name, L"#(loc.%lsNote)", pControl->sczName);
  1407. if (SUCCEEDED(hr)) {
  1408. hr = LocGetString(_wixLoc, name, &locText);
  1409. ReleaseStr(name);
  1410. if (SUCCEEDED(hr) && locText && locText->wzText && locText->wzText[0]) {
  1411. hr = BalFormatString(locText->wzText, &text);
  1412. if (SUCCEEDED(hr) && text && text[0]) {
  1413. ThemeSendControlMessage(_theme, pControl->wId, BCM_SETNOTE, 0, (LPARAM)text);
  1414. ReleaseStr(text);
  1415. text = nullptr;
  1416. }
  1417. }
  1418. }
  1419. hr = S_OK;
  1420. }
  1421. if (!pControl->wPageId && pControl->sczText && *pControl->sczText) {
  1422. HRESULT hrFormat;
  1423. // If the wix developer is showing a hidden variable in the UI,
  1424. // then obviously they don't care about keeping it safe so don't
  1425. // go down the rabbit hole of making sure that this is securely
  1426. // freed.
  1427. hrFormat = BalFormatString(pControl->sczText, &text);
  1428. if (SUCCEEDED(hrFormat)) {
  1429. ThemeSetTextControl(_theme, pControl->wId, text);
  1430. ReleaseStr(text);
  1431. }
  1432. }
  1433. }
  1434. LExit:
  1435. return SUCCEEDED(hr);
  1436. }
  1437. //
  1438. // OnShowFailure - display the failure page.
  1439. //
  1440. void OnShowFailure() {
  1441. SetState(PYBA_STATE_FAILED, S_OK);
  1442. // If the UI should be visible, display it now and hide the splash screen
  1443. if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
  1444. ::ShowWindow(_theme->hwndParent, SW_SHOW);
  1445. }
  1446. _engine->CloseSplashScreen();
  1447. return;
  1448. }
  1449. //
  1450. // OnShowHelp - display the help page.
  1451. //
  1452. void OnShowHelp() {
  1453. SetState(PYBA_STATE_HELP, S_OK);
  1454. // If the UI should be visible, display it now and hide the splash screen
  1455. if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
  1456. ::ShowWindow(_theme->hwndParent, SW_SHOW);
  1457. }
  1458. _engine->CloseSplashScreen();
  1459. return;
  1460. }
  1461. //
  1462. // OnDetect - start the processing of packages.
  1463. //
  1464. void OnDetect() {
  1465. HRESULT hr = S_OK;
  1466. if (_baFunction) {
  1467. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect BA function");
  1468. hr = _baFunction->OnDetect();
  1469. BalExitOnFailure(hr, "Failed calling detect BA function.");
  1470. }
  1471. SetState(PYBA_STATE_DETECTING, hr);
  1472. // If the UI should be visible, display it now and hide the splash screen
  1473. if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) {
  1474. ::ShowWindow(_theme->hwndParent, SW_SHOW);
  1475. }
  1476. _engine->CloseSplashScreen();
  1477. // Tell the core we're ready for the packages to be processed now.
  1478. hr = _engine->Detect();
  1479. BalExitOnFailure(hr, "Failed to start detecting chain.");
  1480. LExit:
  1481. if (FAILED(hr)) {
  1482. SetState(PYBA_STATE_DETECTING, hr);
  1483. }
  1484. return;
  1485. }
  1486. //
  1487. // OnPlan - plan the detected changes.
  1488. //
  1489. void OnPlan(__in BOOTSTRAPPER_ACTION action) {
  1490. HRESULT hr = S_OK;
  1491. LPCWSTR likeInstalling = nullptr;
  1492. LPCWSTR likeInstallation = nullptr;
  1493. _plannedAction = action;
  1494. switch (action) {
  1495. case BOOTSTRAPPER_ACTION_INSTALL:
  1496. likeInstalling = L"Installing";
  1497. likeInstallation = L"Installation";
  1498. break;
  1499. case BOOTSTRAPPER_ACTION_MODIFY:
  1500. // For modify, we actually want to pass INSTALL
  1501. action = BOOTSTRAPPER_ACTION_INSTALL;
  1502. likeInstalling = L"Modifying";
  1503. likeInstallation = L"Modification";
  1504. break;
  1505. case BOOTSTRAPPER_ACTION_REPAIR:
  1506. likeInstalling = L"Repairing";
  1507. likeInstallation = L"Repair";
  1508. break;
  1509. case BOOTSTRAPPER_ACTION_UNINSTALL:
  1510. likeInstalling = L"Uninstalling";
  1511. likeInstallation = L"Uninstallation";
  1512. break;
  1513. }
  1514. if (likeInstalling) {
  1515. LPWSTR locName = nullptr;
  1516. LOC_STRING *locText = nullptr;
  1517. hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstalling);
  1518. if (SUCCEEDED(hr)) {
  1519. hr = LocGetString(_wixLoc, locName, &locText);
  1520. ReleaseStr(locName);
  1521. }
  1522. _engine->SetVariableString(
  1523. L"ActionLikeInstalling",
  1524. SUCCEEDED(hr) && locText ? locText->wzText : likeInstalling
  1525. );
  1526. }
  1527. if (likeInstallation) {
  1528. LPWSTR locName = nullptr;
  1529. LOC_STRING *locText = nullptr;
  1530. hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstallation);
  1531. if (SUCCEEDED(hr)) {
  1532. hr = LocGetString(_wixLoc, locName, &locText);
  1533. ReleaseStr(locName);
  1534. }
  1535. _engine->SetVariableString(
  1536. L"ActionLikeInstallation",
  1537. SUCCEEDED(hr) && locText ? locText->wzText : likeInstallation
  1538. );
  1539. }
  1540. // If we are going to apply a downgrade, bail.
  1541. if (_downgrading && BOOTSTRAPPER_ACTION_UNINSTALL < action) {
  1542. if (_suppressDowngradeFailure) {
  1543. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "A newer version of this product is installed but downgrade failure has been suppressed; continuing...");
  1544. } else {
  1545. hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION);
  1546. BalExitOnFailure(hr, "Cannot install a product when a newer version is installed.");
  1547. }
  1548. }
  1549. SetState(PYBA_STATE_PLANNING, hr);
  1550. if (_baFunction) {
  1551. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan BA function");
  1552. _baFunction->OnPlan();
  1553. }
  1554. hr = _engine->Plan(action);
  1555. BalExitOnFailure(hr, "Failed to start planning packages.");
  1556. LExit:
  1557. if (FAILED(hr)) {
  1558. SetState(PYBA_STATE_PLANNING, hr);
  1559. }
  1560. return;
  1561. }
  1562. //
  1563. // OnApply - apply the packages.
  1564. //
  1565. void OnApply() {
  1566. HRESULT hr = S_OK;
  1567. SetState(PYBA_STATE_APPLYING, hr);
  1568. SetProgressState(hr);
  1569. SetTaskbarButtonProgress(0);
  1570. hr = _engine->Apply(_hWnd);
  1571. BalExitOnFailure(hr, "Failed to start applying packages.");
  1572. ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, TRUE); // ensure the cancel button is enabled before starting.
  1573. LExit:
  1574. if (FAILED(hr)) {
  1575. SetState(PYBA_STATE_APPLYING, hr);
  1576. }
  1577. return;
  1578. }
  1579. //
  1580. // OnChangeState - change state.
  1581. //
  1582. void OnChangeState(__in PYBA_STATE state) {
  1583. LPWSTR unformattedText = nullptr;
  1584. _state = state;
  1585. // If our install is at the end (success or failure) and we're not showing full UI
  1586. // then exit (prompt for restart if required).
  1587. if ((PYBA_STATE_APPLIED <= _state && BOOTSTRAPPER_DISPLAY_FULL > _command.display)) {
  1588. // If a restart was required but we were not automatically allowed to
  1589. // accept the reboot then do the prompt.
  1590. if (_restartRequired && !_allowRestart) {
  1591. StrAllocFromError(&unformattedText, HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), nullptr);
  1592. _allowRestart = IDOK == ::MessageBoxW(
  1593. _hWnd,
  1594. unformattedText ? unformattedText : L"The requested operation is successful. Changes will not be effective until the system is rebooted.",
  1595. _theme->sczCaption,
  1596. MB_ICONEXCLAMATION | MB_OKCANCEL
  1597. );
  1598. }
  1599. // Quietly exit.
  1600. ::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
  1601. } else { // try to change the pages.
  1602. DWORD newPageId = 0;
  1603. DeterminePageId(_state, &newPageId);
  1604. if (_visiblePageId != newPageId) {
  1605. ShowPage(newPageId);
  1606. }
  1607. }
  1608. ReleaseStr(unformattedText);
  1609. }
  1610. //
  1611. // Called before showing a page to handle all controls.
  1612. //
  1613. void ProcessPageControls(THEME_PAGE *pPage) {
  1614. if (!pPage) {
  1615. return;
  1616. }
  1617. for (DWORD i = 0; i < pPage->cControlIndices; ++i) {
  1618. THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i];
  1619. // If this is a named control, try to set its default state.
  1620. if (pControl->sczName && *pControl->sczName) {
  1621. // If this is a checkable control, try to set its default state
  1622. // to the state of a matching named Burn variable.
  1623. if (IsCheckable(pControl)) {
  1624. LONGLONG llValue = 0;
  1625. HRESULT hr = BalGetNumericVariable(pControl->sczName, &llValue);
  1626. // If the control value isn't set then disable it.
  1627. if (!SUCCEEDED(hr)) {
  1628. ThemeControlEnable(_theme, pControl->wId, FALSE);
  1629. } else {
  1630. ThemeSendControlMessage(
  1631. _theme,
  1632. pControl->wId,
  1633. BM_SETCHECK,
  1634. SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED,
  1635. 0
  1636. );
  1637. }
  1638. }
  1639. // Hide or disable controls based on the control name with 'State' appended
  1640. LPWSTR controlName = nullptr;
  1641. HRESULT hr = StrAllocFormatted(&controlName, L"%lsState", pControl->sczName);
  1642. if (SUCCEEDED(hr)) {
  1643. LPWSTR controlState = nullptr;
  1644. hr = BalGetStringVariable(controlName, &controlState);
  1645. if (SUCCEEDED(hr) && controlState && *controlState) {
  1646. if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"disable", -1)) {
  1647. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Disable control %ls", pControl->sczName);
  1648. ThemeControlEnable(_theme, pControl->wId, FALSE);
  1649. } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"hide", -1)) {
  1650. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hide control %ls", pControl->sczName);
  1651. // TODO: This doesn't work
  1652. ThemeShowControl(_theme, pControl->wId, SW_HIDE);
  1653. }
  1654. }
  1655. StrFree(controlState);
  1656. }
  1657. StrFree(controlName);
  1658. }
  1659. // Format the text in each of the new page's controls
  1660. if (pControl->sczText && *pControl->sczText) {
  1661. // If the wix developer is showing a hidden variable
  1662. // in the UI, then obviously they don't care about
  1663. // keeping it safe so don't go down the rabbit hole
  1664. // of making sure that this is securely freed.
  1665. LPWSTR text = nullptr;
  1666. HRESULT hr = BalFormatString(pControl->sczText, &text);
  1667. if (SUCCEEDED(hr)) {
  1668. ThemeSetTextControl(_theme, pControl->wId, text);
  1669. }
  1670. }
  1671. }
  1672. }
  1673. //
  1674. // OnClose - called when the window is trying to be closed.
  1675. //
  1676. BOOL OnClose() {
  1677. BOOL close = FALSE;
  1678. // If we've already succeeded or failed or showing the help page, just close (prompts are annoying if the bootstrapper is done).
  1679. if (PYBA_STATE_APPLIED <= _state || PYBA_STATE_HELP == _state) {
  1680. close = TRUE;
  1681. } else {
  1682. // prompt the user or force the cancel if there is no UI.
  1683. close = PromptCancel(
  1684. _hWnd,
  1685. BOOTSTRAPPER_DISPLAY_FULL != _command.display,
  1686. _confirmCloseMessage ? _confirmCloseMessage : L"Are you sure you want to cancel?",
  1687. _theme->sczCaption
  1688. );
  1689. }
  1690. // If we're doing progress then we never close, we just cancel to let rollback occur.
  1691. if (PYBA_STATE_APPLYING <= _state && PYBA_STATE_APPLIED > _state) {
  1692. // If we canceled disable cancel button since clicking it again is silly.
  1693. if (close) {
  1694. ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE);
  1695. }
  1696. close = FALSE;
  1697. }
  1698. return close;
  1699. }
  1700. //
  1701. // OnClickCloseButton - close the application.
  1702. //
  1703. void OnClickCloseButton() {
  1704. ::SendMessageW(_hWnd, WM_CLOSE, 0, 0);
  1705. }
  1706. //
  1707. // OnClickLaunchButton - launch the app from the success page.
  1708. //
  1709. void OnClickLaunchButton() {
  1710. HRESULT hr = S_OK;
  1711. LPWSTR sczUnformattedLaunchTarget = nullptr;
  1712. LPWSTR sczLaunchTarget = nullptr;
  1713. LPWSTR sczLaunchTargetElevatedId = nullptr;
  1714. LPWSTR sczUnformattedArguments = nullptr;
  1715. LPWSTR sczArguments = nullptr;
  1716. int nCmdShow = SW_SHOWNORMAL;
  1717. hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_TARGET_PATH, &sczUnformattedLaunchTarget);
  1718. BalExitOnFailure1(hr, "Failed to get launch target variable '%ls'.", PYBA_VARIABLE_LAUNCH_TARGET_PATH);
  1719. hr = BalFormatString(sczUnformattedLaunchTarget, &sczLaunchTarget);
  1720. BalExitOnFailure1(hr, "Failed to format launch target variable: %ls", sczUnformattedLaunchTarget);
  1721. if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID)) {
  1722. hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID, &sczLaunchTargetElevatedId);
  1723. BalExitOnFailure1(hr, "Failed to get launch target elevated id '%ls'.", PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID);
  1724. }
  1725. if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_ARGUMENTS)) {
  1726. hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_ARGUMENTS, &sczUnformattedArguments);
  1727. BalExitOnFailure1(hr, "Failed to get launch arguments '%ls'.", PYBA_VARIABLE_LAUNCH_ARGUMENTS);
  1728. }
  1729. if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_HIDDEN)) {
  1730. nCmdShow = SW_HIDE;
  1731. }
  1732. if (sczLaunchTargetElevatedId && !_triedToLaunchElevated) {
  1733. _triedToLaunchElevated = TRUE;
  1734. hr = _engine->LaunchApprovedExe(_hWnd, sczLaunchTargetElevatedId, sczUnformattedArguments, 0);
  1735. if (FAILED(hr)) {
  1736. BalLogError(hr, "Failed to launch elevated target: %ls", sczLaunchTargetElevatedId);
  1737. //try with ShelExec next time
  1738. OnClickLaunchButton();
  1739. }
  1740. } else {
  1741. if (sczUnformattedArguments) {
  1742. hr = BalFormatString(sczUnformattedArguments, &sczArguments);
  1743. BalExitOnFailure1(hr, "Failed to format launch arguments variable: %ls", sczUnformattedArguments);
  1744. }
  1745. hr = ShelExec(sczLaunchTarget, sczArguments, L"open", nullptr, nCmdShow, _hWnd, nullptr);
  1746. BalExitOnFailure1(hr, "Failed to launch target: %ls", sczLaunchTarget);
  1747. ::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
  1748. }
  1749. LExit:
  1750. StrSecureZeroFreeString(sczArguments);
  1751. ReleaseStr(sczUnformattedArguments);
  1752. ReleaseStr(sczLaunchTargetElevatedId);
  1753. StrSecureZeroFreeString(sczLaunchTarget);
  1754. ReleaseStr(sczUnformattedLaunchTarget);
  1755. return;
  1756. }
  1757. //
  1758. // OnClickRestartButton - allows the restart and closes the app.
  1759. //
  1760. void OnClickRestartButton() {
  1761. AssertSz(_restartRequired, "Restart must be requested to be able to click on the restart button.");
  1762. _allowRestart = TRUE;
  1763. ::SendMessageW(_hWnd, WM_CLOSE, 0, 0);
  1764. return;
  1765. }
  1766. //
  1767. // OnClickLogFileLink - show the log file.
  1768. //
  1769. void OnClickLogFileLink() {
  1770. HRESULT hr = S_OK;
  1771. LPWSTR sczLogFile = nullptr;
  1772. hr = BalGetStringVariable(_bundle.sczLogVariable, &sczLogFile);
  1773. BalExitOnFailure1(hr, "Failed to get log file variable '%ls'.", _bundle.sczLogVariable);
  1774. hr = ShelExec(L"notepad.exe", sczLogFile, L"open", nullptr, SW_SHOWDEFAULT, _hWnd, nullptr);
  1775. BalExitOnFailure1(hr, "Failed to open log file target: %ls", sczLogFile);
  1776. LExit:
  1777. ReleaseStr(sczLogFile);
  1778. return;
  1779. }
  1780. //
  1781. // SetState
  1782. //
  1783. void SetState(__in PYBA_STATE state, __in HRESULT hrStatus) {
  1784. if (FAILED(hrStatus)) {
  1785. _hrFinal = hrStatus;
  1786. }
  1787. if (FAILED(_hrFinal)) {
  1788. state = PYBA_STATE_FAILED;
  1789. }
  1790. if (_state != state) {
  1791. ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, state);
  1792. }
  1793. }
  1794. //
  1795. // GoToPage
  1796. //
  1797. void GoToPage(__in PAGE page) {
  1798. _installPage = page;
  1799. ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, _state);
  1800. }
  1801. void DeterminePageId(__in PYBA_STATE state, __out DWORD* pdwPageId) {
  1802. LONGLONG simple;
  1803. if (BOOTSTRAPPER_DISPLAY_PASSIVE == _command.display) {
  1804. switch (state) {
  1805. case PYBA_STATE_INITIALIZED:
  1806. *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action
  1807. ? _pageIds[PAGE_HELP]
  1808. : _pageIds[PAGE_LOADING];
  1809. break;
  1810. case PYBA_STATE_HELP:
  1811. *pdwPageId = _pageIds[PAGE_HELP];
  1812. break;
  1813. case PYBA_STATE_DETECTING:
  1814. *pdwPageId = _pageIds[PAGE_LOADING]
  1815. ? _pageIds[PAGE_LOADING]
  1816. : _pageIds[PAGE_PROGRESS_PASSIVE]
  1817. ? _pageIds[PAGE_PROGRESS_PASSIVE]
  1818. : _pageIds[PAGE_PROGRESS];
  1819. break;
  1820. case PYBA_STATE_DETECTED: __fallthrough;
  1821. case PYBA_STATE_PLANNING: __fallthrough;
  1822. case PYBA_STATE_PLANNED: __fallthrough;
  1823. case PYBA_STATE_APPLYING: __fallthrough;
  1824. case PYBA_STATE_CACHING: __fallthrough;
  1825. case PYBA_STATE_CACHED: __fallthrough;
  1826. case PYBA_STATE_EXECUTING: __fallthrough;
  1827. case PYBA_STATE_EXECUTED:
  1828. *pdwPageId = _pageIds[PAGE_PROGRESS_PASSIVE]
  1829. ? _pageIds[PAGE_PROGRESS_PASSIVE]
  1830. : _pageIds[PAGE_PROGRESS];
  1831. break;
  1832. default:
  1833. *pdwPageId = 0;
  1834. break;
  1835. }
  1836. } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) {
  1837. switch (state) {
  1838. case PYBA_STATE_INITIALIZING:
  1839. *pdwPageId = 0;
  1840. break;
  1841. case PYBA_STATE_INITIALIZED:
  1842. *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action
  1843. ? _pageIds[PAGE_HELP]
  1844. : _pageIds[PAGE_LOADING];
  1845. break;
  1846. case PYBA_STATE_HELP:
  1847. *pdwPageId = _pageIds[PAGE_HELP];
  1848. break;
  1849. case PYBA_STATE_DETECTING:
  1850. *pdwPageId = _pageIds[PAGE_LOADING];
  1851. break;
  1852. case PYBA_STATE_DETECTED:
  1853. if (_installPage == PAGE_LOADING) {
  1854. switch (_command.action) {
  1855. case BOOTSTRAPPER_ACTION_INSTALL:
  1856. if (SUCCEEDED(BalGetNumericVariable(L"SimpleInstall", &simple)) && simple) {
  1857. _installPage = PAGE_SIMPLE_INSTALL;
  1858. } else {
  1859. _installPage = PAGE_INSTALL;
  1860. }
  1861. break;
  1862. case BOOTSTRAPPER_ACTION_MODIFY: __fallthrough;
  1863. case BOOTSTRAPPER_ACTION_REPAIR: __fallthrough;
  1864. case BOOTSTRAPPER_ACTION_UNINSTALL:
  1865. _installPage = PAGE_MODIFY;
  1866. break;
  1867. }
  1868. }
  1869. *pdwPageId = _pageIds[_installPage];
  1870. break;
  1871. case PYBA_STATE_PLANNING: __fallthrough;
  1872. case PYBA_STATE_PLANNED: __fallthrough;
  1873. case PYBA_STATE_APPLYING: __fallthrough;
  1874. case PYBA_STATE_CACHING: __fallthrough;
  1875. case PYBA_STATE_CACHED: __fallthrough;
  1876. case PYBA_STATE_EXECUTING: __fallthrough;
  1877. case PYBA_STATE_EXECUTED:
  1878. *pdwPageId = _pageIds[PAGE_PROGRESS];
  1879. break;
  1880. case PYBA_STATE_APPLIED:
  1881. *pdwPageId = _pageIds[PAGE_SUCCESS];
  1882. break;
  1883. case PYBA_STATE_FAILED:
  1884. *pdwPageId = _pageIds[PAGE_FAILURE];
  1885. break;
  1886. }
  1887. }
  1888. }
  1889. BOOL IsCrtInstalled() {
  1890. if (_crtInstalledToken > 0) {
  1891. return TRUE;
  1892. } else if (_crtInstalledToken == 0) {
  1893. return FALSE;
  1894. }
  1895. // Check whether at least CRT v10.0.9920.0 is available.
  1896. // It should only be installed as a Windows Update package, which means
  1897. // we don't need to worry about 32-bit/64-bit.
  1898. // However, since the WU package does not include vcruntime140.dll, we
  1899. // still install that ourselves.
  1900. LPCWSTR crtFile = L"api-ms-win-crt-runtime-l1-1-0.dll";
  1901. DWORD cbVer = GetFileVersionInfoSizeW(crtFile, nullptr);
  1902. if (!cbVer) {
  1903. _crtInstalledToken = 0;
  1904. return FALSE;
  1905. }
  1906. void *pData = malloc(cbVer);
  1907. if (!pData) {
  1908. _crtInstalledToken = 0;
  1909. return FALSE;
  1910. }
  1911. if (!GetFileVersionInfoW(crtFile, 0, cbVer, pData)) {
  1912. free(pData);
  1913. _crtInstalledToken = 0;
  1914. return FALSE;
  1915. }
  1916. VS_FIXEDFILEINFO *ffi;
  1917. UINT cb;
  1918. BOOL result = FALSE;
  1919. if (VerQueryValueW(pData, L"\\", (LPVOID*)&ffi, &cb) &&
  1920. ffi->dwFileVersionMS == 0x000A0000 && ffi->dwFileVersionLS >= 0x26C00000) {
  1921. result = TRUE;
  1922. }
  1923. free(pData);
  1924. _crtInstalledToken = result ? 1 : 0;
  1925. return result;
  1926. }
  1927. BOOL QueryElevateForCrtInstall() {
  1928. // Called to prompt the user that even though they think they won't need
  1929. // to elevate, they actually will because of the CRT install.
  1930. if (IsCrtInstalled()) {
  1931. // CRT is already installed - no need to prompt
  1932. return TRUE;
  1933. }
  1934. LONGLONG elevated;
  1935. HRESULT hr = BalGetNumericVariable(L"WixBundleElevated", &elevated);
  1936. if (SUCCEEDED(hr) && elevated) {
  1937. // Already elevated - no need to prompt
  1938. return TRUE;
  1939. }
  1940. LOC_STRING *locStr;
  1941. hr = LocGetString(_wixLoc, L"#(loc.ElevateForCRTInstall)", &locStr);
  1942. if (FAILED(hr)) {
  1943. BalLogError(hr, "Failed to get ElevateForCRTInstall string");
  1944. return FALSE;
  1945. }
  1946. return ::MessageBoxW(_hWnd, locStr->wzText, _theme->sczCaption, MB_YESNO) != IDNO;
  1947. }
  1948. HRESULT EvaluateConditions() {
  1949. HRESULT hr = S_OK;
  1950. BOOL result = FALSE;
  1951. for (DWORD i = 0; i < _conditions.cConditions; ++i) {
  1952. BAL_CONDITION* pCondition = _conditions.rgConditions + i;
  1953. hr = BalConditionEvaluate(pCondition, _engine, &result, &_failedMessage);
  1954. BalExitOnFailure(hr, "Failed to evaluate condition.");
  1955. if (!result) {
  1956. // Hope they didn't have hidden variables in their message, because it's going in the log in plaintext.
  1957. BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "%ls", _failedMessage);
  1958. hr = E_WIXSTDBA_CONDITION_FAILED;
  1959. // todo: remove in WiX v4, in case people are relying on v3.x logging behavior
  1960. BalExitOnFailure1(hr, "Bundle condition evaluated to false: %ls", pCondition->sczCondition);
  1961. }
  1962. }
  1963. ReleaseNullStrSecure(_failedMessage);
  1964. LExit:
  1965. return hr;
  1966. }
  1967. void SetTaskbarButtonProgress(__in DWORD dwOverallPercentage) {
  1968. HRESULT hr = S_OK;
  1969. if (_taskbarButtonOK) {
  1970. hr = _taskbarList->SetProgressValue(_hWnd, dwOverallPercentage, 100UL);
  1971. BalExitOnFailure1(hr, "Failed to set taskbar button progress to: %d%%.", dwOverallPercentage);
  1972. }
  1973. LExit:
  1974. return;
  1975. }
  1976. void SetTaskbarButtonState(__in TBPFLAG tbpFlags) {
  1977. HRESULT hr = S_OK;
  1978. if (_taskbarButtonOK) {
  1979. hr = _taskbarList->SetProgressState(_hWnd, tbpFlags);
  1980. BalExitOnFailure1(hr, "Failed to set taskbar button state.", tbpFlags);
  1981. }
  1982. LExit:
  1983. return;
  1984. }
  1985. void SetProgressState(__in HRESULT hrStatus) {
  1986. TBPFLAG flag = TBPF_NORMAL;
  1987. if (IsCanceled() || HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hrStatus) {
  1988. flag = TBPF_PAUSED;
  1989. } else if (IsRollingBack() || FAILED(hrStatus)) {
  1990. flag = TBPF_ERROR;
  1991. }
  1992. SetTaskbarButtonState(flag);
  1993. }
  1994. HRESULT LoadBootstrapperBAFunctions() {
  1995. HRESULT hr = S_OK;
  1996. LPWSTR sczBafPath = nullptr;
  1997. hr = PathRelativeToModule(&sczBafPath, L"bafunctions.dll", _hModule);
  1998. BalExitOnFailure(hr, "Failed to get path to BA function DLL.");
  1999. #ifdef DEBUG
  2000. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: LoadBootstrapperBAFunctions() - BA function DLL %ls", sczBafPath);
  2001. #endif
  2002. _hBAFModule = ::LoadLibraryW(sczBafPath);
  2003. if (_hBAFModule) {
  2004. auto pfnBAFunctionCreate = reinterpret_cast<PFN_BOOTSTRAPPER_BA_FUNCTION_CREATE>(::GetProcAddress(_hBAFModule, "CreateBootstrapperBAFunction"));
  2005. BalExitOnNullWithLastError1(pfnBAFunctionCreate, hr, "Failed to get CreateBootstrapperBAFunction entry-point from: %ls", sczBafPath);
  2006. hr = pfnBAFunctionCreate(_engine, _hBAFModule, &_baFunction);
  2007. BalExitOnFailure(hr, "Failed to create BA function.");
  2008. }
  2009. #ifdef DEBUG
  2010. else {
  2011. BalLogError(HRESULT_FROM_WIN32(::GetLastError()), "PYBA: LoadBootstrapperBAFunctions() - Failed to load DLL %ls", sczBafPath);
  2012. }
  2013. #endif
  2014. LExit:
  2015. if (_hBAFModule && !_baFunction) {
  2016. ::FreeLibrary(_hBAFModule);
  2017. _hBAFModule = nullptr;
  2018. }
  2019. ReleaseStr(sczBafPath);
  2020. return hr;
  2021. }
  2022. BOOL IsCheckable(THEME_CONTROL* pControl) {
  2023. if (!pControl->sczName || !pControl->sczName[0]) {
  2024. return FALSE;
  2025. }
  2026. if (pControl->type == THEME_CONTROL_TYPE_CHECKBOX) {
  2027. return TRUE;
  2028. }
  2029. if (pControl->type == THEME_CONTROL_TYPE_BUTTON) {
  2030. if ((pControl->dwStyle & BS_TYPEMASK) == BS_AUTORADIOBUTTON) {
  2031. return TRUE;
  2032. }
  2033. }
  2034. return FALSE;
  2035. }
  2036. void SavePageSettings() {
  2037. DWORD pageId = 0;
  2038. THEME_PAGE* pPage = nullptr;
  2039. DeterminePageId(_state, &pageId);
  2040. pPage = ThemeGetPage(_theme, pageId);
  2041. if (!pPage) {
  2042. return;
  2043. }
  2044. for (DWORD i = 0; i < pPage->cControlIndices; ++i) {
  2045. // Loop through all the checkable controls and set a Burn variable
  2046. // with that name to true or false.
  2047. THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i];
  2048. if (IsCheckable(pControl) && ThemeControlEnabled(_theme, pControl->wId)) {
  2049. BOOL checked = ThemeIsControlChecked(_theme, pControl->wId);
  2050. _engine->SetVariableNumeric(pControl->sczName, checked ? 1 : 0);
  2051. }
  2052. // Loop through all the editbox controls with names and set a
  2053. // Burn variable with that name to the contents.
  2054. if (THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName) {
  2055. LPWSTR sczValue = nullptr;
  2056. ThemeGetTextControl(_theme, pControl->wId, &sczValue);
  2057. _engine->SetVariableString(pControl->sczName, sczValue);
  2058. }
  2059. }
  2060. }
  2061. public:
  2062. //
  2063. // Constructor - initialize member variables.
  2064. //
  2065. PythonBootstrapperApplication(
  2066. __in HMODULE hModule,
  2067. __in BOOL fPrereq,
  2068. __in HRESULT hrHostInitialization,
  2069. __in IBootstrapperEngine* pEngine,
  2070. __in const BOOTSTRAPPER_COMMAND* pCommand
  2071. ) : CBalBaseBootstrapperApplication(pEngine, pCommand, 3, 3000) {
  2072. _hModule = hModule;
  2073. memcpy_s(&_command, sizeof(_command), pCommand, sizeof(BOOTSTRAPPER_COMMAND));
  2074. LONGLONG llInstalled = 0;
  2075. HRESULT hr = BalGetNumericVariable(L"WixBundleInstalled", &llInstalled);
  2076. if (SUCCEEDED(hr) && BOOTSTRAPPER_RESUME_TYPE_REBOOT != _command.resumeType && 0 < llInstalled && BOOTSTRAPPER_ACTION_INSTALL == _command.action) {
  2077. _command.action = BOOTSTRAPPER_ACTION_MODIFY;
  2078. } else if (0 == llInstalled && (BOOTSTRAPPER_ACTION_MODIFY == _command.action || BOOTSTRAPPER_ACTION_REPAIR == _command.action)) {
  2079. _command.action = BOOTSTRAPPER_ACTION_INSTALL;
  2080. }
  2081. _plannedAction = BOOTSTRAPPER_ACTION_UNKNOWN;
  2082. // When resuming from restart doing some install-like operation, try to find the package that forced the
  2083. // restart. We'll use this information during planning.
  2084. _nextPackageAfterRestart = nullptr;
  2085. if (BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType && BOOTSTRAPPER_ACTION_UNINSTALL < _command.action) {
  2086. // Ensure the forced restart package variable is null when it is an empty string.
  2087. HRESULT hr = BalGetStringVariable(L"WixBundleForcedRestartPackage", &_nextPackageAfterRestart);
  2088. if (FAILED(hr) || !_nextPackageAfterRestart || !*_nextPackageAfterRestart) {
  2089. ReleaseNullStr(_nextPackageAfterRestart);
  2090. }
  2091. }
  2092. pEngine->SetVariableNumeric(L"CRTInstalled", IsCrtInstalled() ? 1 : 0);
  2093. _wixLoc = nullptr;
  2094. memset(&_bundle, 0, sizeof(_bundle));
  2095. memset(&_conditions, 0, sizeof(_conditions));
  2096. _confirmCloseMessage = nullptr;
  2097. _failedMessage = nullptr;
  2098. _language = nullptr;
  2099. _theme = nullptr;
  2100. memset(_pageIds, 0, sizeof(_pageIds));
  2101. _hUiThread = nullptr;
  2102. _registered = FALSE;
  2103. _hWnd = nullptr;
  2104. _state = PYBA_STATE_INITIALIZING;
  2105. _visiblePageId = 0;
  2106. _installPage = PAGE_LOADING;
  2107. _hrFinal = hrHostInitialization;
  2108. _downgrading = FALSE;
  2109. _restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE;
  2110. _restartRequired = FALSE;
  2111. _allowRestart = FALSE;
  2112. _suppressDowngradeFailure = FALSE;
  2113. _suppressRepair = FALSE;
  2114. _modifying = FALSE;
  2115. _crtInstalledToken = -1;
  2116. _overridableVariables = nullptr;
  2117. _taskbarList = nullptr;
  2118. _taskbarButtonCreatedMessage = UINT_MAX;
  2119. _taskbarButtonOK = FALSE;
  2120. _showingInternalUIThisPackage = FALSE;
  2121. _triedToLaunchElevated = FALSE;
  2122. _suppressPaint = FALSE;
  2123. pEngine->AddRef();
  2124. _engine = pEngine;
  2125. _hBAFModule = nullptr;
  2126. _baFunction = nullptr;
  2127. }
  2128. //
  2129. // Destructor - release member variables.
  2130. //
  2131. ~PythonBootstrapperApplication() {
  2132. AssertSz(!::IsWindow(_hWnd), "Window should have been destroyed before destructor.");
  2133. AssertSz(!_theme, "Theme should have been released before destructor.");
  2134. ReleaseObject(_taskbarList);
  2135. ReleaseDict(_overridableVariables);
  2136. ReleaseStr(_failedMessage);
  2137. ReleaseStr(_confirmCloseMessage);
  2138. BalConditionsUninitialize(&_conditions);
  2139. BalInfoUninitialize(&_bundle);
  2140. LocFree(_wixLoc);
  2141. ReleaseStr(_language);
  2142. ReleaseStr(_nextPackageAfterRestart);
  2143. ReleaseNullObject(_engine);
  2144. if (_hBAFModule) {
  2145. ::FreeLibrary(_hBAFModule);
  2146. _hBAFModule = nullptr;
  2147. }
  2148. }
  2149. private:
  2150. HMODULE _hModule;
  2151. BOOTSTRAPPER_COMMAND _command;
  2152. IBootstrapperEngine* _engine;
  2153. BOOTSTRAPPER_ACTION _plannedAction;
  2154. LPWSTR _nextPackageAfterRestart;
  2155. WIX_LOCALIZATION* _wixLoc;
  2156. BAL_INFO_BUNDLE _bundle;
  2157. BAL_CONDITIONS _conditions;
  2158. LPWSTR _failedMessage;
  2159. LPWSTR _confirmCloseMessage;
  2160. LPWSTR _language;
  2161. THEME* _theme;
  2162. DWORD _pageIds[countof(PAGE_NAMES)];
  2163. HANDLE _hUiThread;
  2164. BOOL _registered;
  2165. HWND _hWnd;
  2166. PYBA_STATE _state;
  2167. HRESULT _hrFinal;
  2168. DWORD _visiblePageId;
  2169. PAGE _installPage;
  2170. BOOL _startedExecution;
  2171. DWORD _calculatedCacheProgress;
  2172. DWORD _calculatedExecuteProgress;
  2173. BOOL _downgrading;
  2174. BOOTSTRAPPER_APPLY_RESTART _restartResult;
  2175. BOOL _restartRequired;
  2176. BOOL _allowRestart;
  2177. BOOL _suppressDowngradeFailure;
  2178. BOOL _suppressRepair;
  2179. BOOL _modifying;
  2180. int _crtInstalledToken;
  2181. STRINGDICT_HANDLE _overridableVariables;
  2182. ITaskbarList3* _taskbarList;
  2183. UINT _taskbarButtonCreatedMessage;
  2184. BOOL _taskbarButtonOK;
  2185. BOOL _showingInternalUIThisPackage;
  2186. BOOL _triedToLaunchElevated;
  2187. BOOL _suppressPaint;
  2188. HMODULE _hBAFModule;
  2189. IBootstrapperBAFunction* _baFunction;
  2190. };
  2191. //
  2192. // CreateBootstrapperApplication - creates a new IBootstrapperApplication object.
  2193. //
  2194. HRESULT CreateBootstrapperApplication(
  2195. __in HMODULE hModule,
  2196. __in BOOL fPrereq,
  2197. __in HRESULT hrHostInitialization,
  2198. __in IBootstrapperEngine* pEngine,
  2199. __in const BOOTSTRAPPER_COMMAND* pCommand,
  2200. __out IBootstrapperApplication** ppApplication
  2201. ) {
  2202. HRESULT hr = S_OK;
  2203. if (fPrereq) {
  2204. hr = E_INVALIDARG;
  2205. ExitWithLastError(hr, "Failed to create UI thread.");
  2206. }
  2207. PythonBootstrapperApplication* pApplication = nullptr;
  2208. pApplication = new PythonBootstrapperApplication(hModule, fPrereq, hrHostInitialization, pEngine, pCommand);
  2209. ExitOnNull(pApplication, hr, E_OUTOFMEMORY, "Failed to create new standard bootstrapper application object.");
  2210. *ppApplication = pApplication;
  2211. pApplication = nullptr;
  2212. LExit:
  2213. ReleaseObject(pApplication);
  2214. return hr;
  2215. }