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