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