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