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