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