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