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.

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