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