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