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