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