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