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