//------------------------------------------------------------------------------------------------- // // Copyright (c) 2004, Outercurve Foundation. // This software is released under Microsoft Reciprocal License (MS-RL). // The license and further copyright text can be found in the file // LICENSE.TXT at the root directory of the distribution. // //------------------------------------------------------------------------------------------------- #include "pch.h" static const LPCWSTR PYBA_WINDOW_CLASS = L"PythonBA"; static const DWORD PYBA_ACQUIRE_PERCENTAGE = 30; static const LPCWSTR PYBA_VARIABLE_BUNDLE_FILE_VERSION = L"WixBundleFileVersion"; enum PYBA_STATE { PYBA_STATE_INITIALIZING, PYBA_STATE_INITIALIZED, PYBA_STATE_HELP, PYBA_STATE_DETECTING, PYBA_STATE_DETECTED, PYBA_STATE_PLANNING, PYBA_STATE_PLANNED, PYBA_STATE_APPLYING, PYBA_STATE_CACHING, PYBA_STATE_CACHED, PYBA_STATE_EXECUTING, PYBA_STATE_EXECUTED, PYBA_STATE_APPLIED, PYBA_STATE_FAILED, }; static const int WM_PYBA_SHOW_HELP = WM_APP + 100; static const int WM_PYBA_DETECT_PACKAGES = WM_APP + 101; static const int WM_PYBA_PLAN_PACKAGES = WM_APP + 102; static const int WM_PYBA_APPLY_PACKAGES = WM_APP + 103; static const int WM_PYBA_CHANGE_STATE = WM_APP + 104; static const int WM_PYBA_SHOW_FAILURE = WM_APP + 105; // This enum must be kept in the same order as the PAGE_NAMES array. enum PAGE { PAGE_LOADING, PAGE_HELP, PAGE_INSTALL, PAGE_UPGRADE, PAGE_SIMPLE_INSTALL, PAGE_CUSTOM1, PAGE_CUSTOM2, PAGE_MODIFY, PAGE_PROGRESS, PAGE_PROGRESS_PASSIVE, PAGE_SUCCESS, PAGE_FAILURE, COUNT_PAGE, }; // This array must be kept in the same order as the PAGE enum. static LPCWSTR PAGE_NAMES[] = { L"Loading", L"Help", L"Install", L"Upgrade", L"SimpleInstall", L"Custom1", L"Custom2", L"Modify", L"Progress", L"ProgressPassive", L"Success", L"Failure", }; enum CONTROL_ID { // Non-paged controls ID_CLOSE_BUTTON = THEME_FIRST_ASSIGN_CONTROL_ID, ID_MINIMIZE_BUTTON, // Welcome page ID_INSTALL_BUTTON, ID_INSTALL_CUSTOM_BUTTON, ID_INSTALL_SIMPLE_BUTTON, ID_INSTALL_UPGRADE_BUTTON, ID_INSTALL_UPGRADE_CUSTOM_BUTTON, ID_INSTALL_CANCEL_BUTTON, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, // Customize Page ID_TARGETDIR_EDITBOX, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, ID_CUSTOM_COMPILE_ALL_CHECKBOX, ID_CUSTOM_BROWSE_BUTTON, ID_CUSTOM_BROWSE_BUTTON_LABEL, ID_CUSTOM_INSTALL_BUTTON, ID_CUSTOM_NEXT_BUTTON, ID_CUSTOM1_BACK_BUTTON, ID_CUSTOM2_BACK_BUTTON, ID_CUSTOM1_CANCEL_BUTTON, ID_CUSTOM2_CANCEL_BUTTON, // Modify page ID_MODIFY_BUTTON, ID_REPAIR_BUTTON, ID_UNINSTALL_BUTTON, ID_MODIFY_CANCEL_BUTTON, // Progress page ID_CACHE_PROGRESS_PACKAGE_TEXT, ID_CACHE_PROGRESS_BAR, ID_CACHE_PROGRESS_TEXT, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, ID_EXECUTE_PROGRESS_BAR, ID_EXECUTE_PROGRESS_TEXT, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, ID_OVERALL_PROGRESS_PACKAGE_TEXT, ID_OVERALL_PROGRESS_BAR, ID_OVERALL_CALCULATED_PROGRESS_BAR, ID_OVERALL_PROGRESS_TEXT, ID_PROGRESS_CANCEL_BUTTON, // Success page ID_SUCCESS_TEXT, ID_SUCCESS_RESTART_TEXT, ID_SUCCESS_RESTART_BUTTON, ID_SUCCESS_CANCEL_BUTTON, ID_SUCCESS_MAX_PATH_BUTTON, // Failure page ID_FAILURE_LOGFILE_LINK, ID_FAILURE_MESSAGE_TEXT, ID_FAILURE_RESTART_TEXT, ID_FAILURE_RESTART_BUTTON, ID_FAILURE_CANCEL_BUTTON }; static THEME_ASSIGN_CONTROL_ID CONTROL_ID_NAMES[] = { { ID_CLOSE_BUTTON, L"CloseButton" }, { ID_MINIMIZE_BUTTON, L"MinimizeButton" }, { ID_INSTALL_BUTTON, L"InstallButton" }, { ID_INSTALL_CUSTOM_BUTTON, L"InstallCustomButton" }, { ID_INSTALL_SIMPLE_BUTTON, L"InstallSimpleButton" }, { ID_INSTALL_UPGRADE_BUTTON, L"InstallUpgradeButton" }, { ID_INSTALL_UPGRADE_CUSTOM_BUTTON, L"InstallUpgradeCustomButton" }, { ID_INSTALL_CANCEL_BUTTON, L"InstallCancelButton" }, { ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"InstallLauncherAllUsers" }, { ID_TARGETDIR_EDITBOX, L"TargetDir" }, { ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, L"AssociateFiles" }, { ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, L"InstallAllUsers" }, { ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"CustomInstallLauncherAllUsers" }, { ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, L"Include_launcherHelp" }, { ID_CUSTOM_COMPILE_ALL_CHECKBOX, L"CompileAll" }, { ID_CUSTOM_BROWSE_BUTTON, L"CustomBrowseButton" }, { ID_CUSTOM_BROWSE_BUTTON_LABEL, L"CustomBrowseButtonLabel" }, { ID_CUSTOM_INSTALL_BUTTON, L"CustomInstallButton" }, { ID_CUSTOM_NEXT_BUTTON, L"CustomNextButton" }, { ID_CUSTOM1_BACK_BUTTON, L"Custom1BackButton" }, { ID_CUSTOM2_BACK_BUTTON, L"Custom2BackButton" }, { ID_CUSTOM1_CANCEL_BUTTON, L"Custom1CancelButton" }, { ID_CUSTOM2_CANCEL_BUTTON, L"Custom2CancelButton" }, { ID_MODIFY_BUTTON, L"ModifyButton" }, { ID_REPAIR_BUTTON, L"RepairButton" }, { ID_UNINSTALL_BUTTON, L"UninstallButton" }, { ID_MODIFY_CANCEL_BUTTON, L"ModifyCancelButton" }, { ID_CACHE_PROGRESS_PACKAGE_TEXT, L"CacheProgressPackageText" }, { ID_CACHE_PROGRESS_BAR, L"CacheProgressbar" }, { ID_CACHE_PROGRESS_TEXT, L"CacheProgressText" }, { ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"ExecuteProgressPackageText" }, { ID_EXECUTE_PROGRESS_BAR, L"ExecuteProgressbar" }, { ID_EXECUTE_PROGRESS_TEXT, L"ExecuteProgressText" }, { ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"ExecuteProgressActionDataText" }, { ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"OverallProgressPackageText" }, { ID_OVERALL_PROGRESS_BAR, L"OverallProgressbar" }, { ID_OVERALL_CALCULATED_PROGRESS_BAR, L"OverallCalculatedProgressbar" }, { ID_OVERALL_PROGRESS_TEXT, L"OverallProgressText" }, { ID_PROGRESS_CANCEL_BUTTON, L"ProgressCancelButton" }, { ID_SUCCESS_TEXT, L"SuccessText" }, { ID_SUCCESS_RESTART_TEXT, L"SuccessRestartText" }, { ID_SUCCESS_RESTART_BUTTON, L"SuccessRestartButton" }, { ID_SUCCESS_CANCEL_BUTTON, L"SuccessCancelButton" }, { ID_SUCCESS_MAX_PATH_BUTTON, L"SuccessMaxPathButton" }, { ID_FAILURE_LOGFILE_LINK, L"FailureLogFileLink" }, { ID_FAILURE_MESSAGE_TEXT, L"FailureMessageText" }, { ID_FAILURE_RESTART_TEXT, L"FailureRestartText" }, { ID_FAILURE_RESTART_BUTTON, L"FailureRestartButton" }, { ID_FAILURE_CANCEL_BUTTON, L"FailureCancelButton" }, }; static struct { LPCWSTR regName; LPCWSTR variableName; } OPTIONAL_FEATURES[] = { { L"core_d", L"Include_debug" }, { L"core_pdb", L"Include_symbols" }, { L"dev", L"Include_dev" }, { L"doc", L"Include_doc" }, { L"exe", L"Include_exe" }, { L"lib", L"Include_lib" }, { L"path", L"PrependPath" }, { L"appendpath", L"AppendPath" }, { L"pip", L"Include_pip" }, { L"tcltk", L"Include_tcltk" }, { L"test", L"Include_test" }, { L"tools", L"Include_tools" }, { L"Shortcuts", L"Shortcuts" }, // Include_launcher and AssociateFiles are handled separately and so do // not need to be included in this list. { nullptr, nullptr } }; class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication { void ShowPage(DWORD newPageId) { // Process each control for special handling in the new page. ProcessPageControls(ThemeGetPage(_theme, newPageId)); // Enable disable controls per-page. if (_pageIds[PAGE_INSTALL] == newPageId || _pageIds[PAGE_SIMPLE_INSTALL] == newPageId || _pageIds[PAGE_UPGRADE] == newPageId) { InstallPage_Show(); } else if (_pageIds[PAGE_CUSTOM1] == newPageId) { Custom1Page_Show(); } else if (_pageIds[PAGE_CUSTOM2] == newPageId) { Custom2Page_Show(); } else if (_pageIds[PAGE_MODIFY] == newPageId) { ModifyPage_Show(); } else if (_pageIds[PAGE_SUCCESS] == newPageId) { SuccessPage_Show(); } else if (_pageIds[PAGE_FAILURE] == newPageId) { FailurePage_Show(); } // Prevent repainting while switching page to avoid ugly flickering _suppressPaint = TRUE; ThemeShowPage(_theme, newPageId, SW_SHOW); ThemeShowPage(_theme, _visiblePageId, SW_HIDE); _suppressPaint = FALSE; InvalidateRect(_theme->hwndParent, nullptr, TRUE); _visiblePageId = newPageId; // On the install page set the focus to the install button or // the next enabled control if install is disabled if (_pageIds[PAGE_INSTALL] == newPageId) { ThemeSetFocus(_theme, ID_INSTALL_BUTTON); } else if (_pageIds[PAGE_SIMPLE_INSTALL] == newPageId) { ThemeSetFocus(_theme, ID_INSTALL_SIMPLE_BUTTON); } } // // Handles control clicks // void OnCommand(CONTROL_ID id) { LPWSTR defaultDir = nullptr; LPWSTR targetDir = nullptr; LONGLONG elevated, crtInstalled, installAllUsers; BOOL checked, launcherChecked; WCHAR wzPath[MAX_PATH] = { }; BROWSEINFOW browseInfo = { }; PIDLIST_ABSOLUTE pidl = nullptr; DWORD pageId; HRESULT hr = S_OK; switch(id) { case ID_CLOSE_BUTTON: OnClickCloseButton(); break; // Install commands case ID_INSTALL_SIMPLE_BUTTON: __fallthrough; case ID_INSTALL_UPGRADE_BUTTON: __fallthrough; case ID_INSTALL_BUTTON: SavePageSettings(); hr = BalGetNumericVariable(L"InstallAllUsers", &installAllUsers); ExitOnFailure(hr, L"Failed to get install scope"); hr = _engine->SetVariableNumeric(L"CompileAll", installAllUsers); ExitOnFailure(hr, L"Failed to update CompileAll"); hr = EnsureTargetDir(); ExitOnFailure(hr, L"Failed to set TargetDir"); OnPlan(BOOTSTRAPPER_ACTION_INSTALL); break; case ID_CUSTOM1_BACK_BUTTON: SavePageSettings(); if (_modifying) { GoToPage(PAGE_MODIFY); } else if (_upgrading) { GoToPage(PAGE_UPGRADE); } else { GoToPage(PAGE_INSTALL); } break; case ID_INSTALL_CUSTOM_BUTTON: __fallthrough; case ID_INSTALL_UPGRADE_CUSTOM_BUTTON: __fallthrough; case ID_CUSTOM2_BACK_BUTTON: SavePageSettings(); GoToPage(PAGE_CUSTOM1); break; case ID_CUSTOM_NEXT_BUTTON: SavePageSettings(); GoToPage(PAGE_CUSTOM2); break; case ID_CUSTOM_INSTALL_BUTTON: SavePageSettings(); hr = EnsureTargetDir(); ExitOnFailure(hr, L"Failed to set TargetDir"); hr = BalGetStringVariable(L"TargetDir", &targetDir); if (SUCCEEDED(hr)) { // TODO: Check whether directory exists and contains another installation ReleaseStr(targetDir); } OnPlan(_command.action); break; case ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX: checked = ThemeIsControlChecked(_theme, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX); _engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked); ThemeControlElevates(_theme, ID_INSTALL_BUTTON, WillElevate()); break; case ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX: checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX); _engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked); ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate()); break; case ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX: checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX); _engine->SetVariableNumeric(L"InstallAllUsers", checked); ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate()); ThemeControlEnable(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, !checked); if (checked) { _engine->SetVariableNumeric(L"CompileAll", 1); ThemeSendControlMessage(_theme, ID_CUSTOM_COMPILE_ALL_CHECKBOX, BM_SETCHECK, BST_CHECKED, 0); } ThemeGetTextControl(_theme, ID_TARGETDIR_EDITBOX, &targetDir); if (targetDir) { // Check the current value against the default to see // if we should switch it automatically. hr = BalGetStringVariable( checked ? L"DefaultJustForMeTargetDir" : L"DefaultAllUsersTargetDir", &defaultDir ); if (SUCCEEDED(hr) && defaultDir) { LPWSTR formatted = nullptr; if (defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) { if (wcscmp(formatted, targetDir) == 0) { ReleaseStr(defaultDir); defaultDir = nullptr; ReleaseStr(formatted); formatted = nullptr; hr = BalGetStringVariable( checked ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir", &defaultDir ); if (SUCCEEDED(hr) && defaultDir && defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) { ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, formatted); ReleaseStr(formatted); } } else { ReleaseStr(formatted); } } ReleaseStr(defaultDir); } } break; case ID_CUSTOM_BROWSE_BUTTON: browseInfo.hwndOwner = _hWnd; browseInfo.pszDisplayName = wzPath; browseInfo.lpszTitle = _theme->sczCaption; browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; pidl = ::SHBrowseForFolderW(&browseInfo); if (pidl && ::SHGetPathFromIDListW(pidl, wzPath)) { ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, wzPath); } if (pidl) { ::CoTaskMemFree(pidl); } break; // Modify commands case ID_MODIFY_BUTTON: // Some variables cannot be modified _engine->SetVariableString(L"InstallAllUsersState", L"disable"); _engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable"); _engine->SetVariableString(L"TargetDirState", L"disable"); _engine->SetVariableString(L"CustomBrowseButtonState", L"disable"); _modifying = TRUE; GoToPage(PAGE_CUSTOM1); break; case ID_REPAIR_BUTTON: OnPlan(BOOTSTRAPPER_ACTION_REPAIR); break; case ID_UNINSTALL_BUTTON: OnPlan(BOOTSTRAPPER_ACTION_UNINSTALL); break; case ID_SUCCESS_MAX_PATH_BUTTON: EnableMaxPathSupport(); ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE); break; } LExit: return; } void InstallPage_Show() { // Ensure the All Users install button has a UAC shield BOOL elevated = WillElevate(); ThemeControlElevates(_theme, ID_INSTALL_BUTTON, elevated); ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, elevated); ThemeControlElevates(_theme, ID_INSTALL_UPGRADE_BUTTON, elevated); LONGLONG blockedLauncher; if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) && blockedLauncher) { LOC_STRING *pLocString = nullptr; if (SUCCEEDED(LocGetString(_wixLoc, L"#(loc.ShortInstallLauncherBlockedLabel)", &pLocString)) && pLocString) { ThemeSetTextControl(_theme, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, pLocString->wzText); } } } void Custom1Page_Show() { LONGLONG installLauncherAllUsers; if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &installLauncherAllUsers))) { installLauncherAllUsers = 0; } ThemeSendControlMessage(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, BM_SETCHECK, installLauncherAllUsers ? BST_CHECKED : BST_UNCHECKED, 0); LOC_STRING *pLocString = nullptr; LPCWSTR locKey = L"#(loc.Include_launcherHelp)"; LONGLONG blockedLauncher; if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) && blockedLauncher) { locKey = L"#(loc.Include_launcherRemove)"; } else if (SUCCEEDED(BalGetNumericVariable(L"DetectedOldLauncher", &blockedLauncher)) && blockedLauncher) { locKey = L"#(loc.Include_launcherUpgrade)"; } if (SUCCEEDED(LocGetString(_wixLoc, locKey, &pLocString)) && pLocString) { ThemeSetTextControl(_theme, ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, pLocString->wzText); } } void Custom2Page_Show() { HRESULT hr; LONGLONG installAll, includeLauncher; if (FAILED(BalGetNumericVariable(L"InstallAllUsers", &installAll))) { installAll = 0; } if (WillElevate()) { ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, TRUE); ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_HIDE); } else { ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, FALSE); ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_SHOW); } if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) && includeLauncher) { ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, TRUE); } else { ThemeSendControlMessage(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, BM_SETCHECK, BST_UNCHECKED, 0); ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, FALSE); } LPWSTR targetDir = nullptr; hr = BalGetStringVariable(L"TargetDir", &targetDir); if (SUCCEEDED(hr) && targetDir && targetDir[0]) { ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir); StrFree(targetDir); } else if (SUCCEEDED(hr)) { StrFree(targetDir); targetDir = nullptr; LPWSTR defaultTargetDir = nullptr; hr = BalGetStringVariable(L"DefaultCustomTargetDir", &defaultTargetDir); if (SUCCEEDED(hr) && defaultTargetDir && !defaultTargetDir[0]) { StrFree(defaultTargetDir); defaultTargetDir = nullptr; hr = BalGetStringVariable( installAll ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir", &defaultTargetDir ); } if (SUCCEEDED(hr) && defaultTargetDir) { if (defaultTargetDir[0] && SUCCEEDED(BalFormatString(defaultTargetDir, &targetDir))) { ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir); StrFree(targetDir); } StrFree(defaultTargetDir); } } } void ModifyPage_Show() { ThemeControlEnable(_theme, ID_REPAIR_BUTTON, !_suppressRepair); } void SuccessPage_Show() { // on the "Success" page, check if the restart button should be enabled. BOOL showRestartButton = FALSE; LOC_STRING *successText = nullptr; HRESULT hr = S_OK; if (_restartRequired) { if (BOOTSTRAPPER_RESTART_PROMPT == _command.restart) { showRestartButton = TRUE; } } switch (_plannedAction) { case BOOTSTRAPPER_ACTION_INSTALL: hr = LocGetString(_wixLoc, L"#(loc.SuccessInstallMessage)", &successText); break; case BOOTSTRAPPER_ACTION_MODIFY: hr = LocGetString(_wixLoc, L"#(loc.SuccessModifyMessage)", &successText); break; case BOOTSTRAPPER_ACTION_REPAIR: hr = LocGetString(_wixLoc, L"#(loc.SuccessRepairMessage)", &successText); break; case BOOTSTRAPPER_ACTION_UNINSTALL: hr = LocGetString(_wixLoc, L"#(loc.SuccessRemoveMessage)", &successText); break; } if (successText) { LPWSTR formattedString = nullptr; BalFormatString(successText->wzText, &formattedString); if (formattedString) { ThemeSetTextControl(_theme, ID_SUCCESS_TEXT, formattedString); StrFree(formattedString); } } ThemeControlEnable(_theme, ID_SUCCESS_RESTART_TEXT, showRestartButton); ThemeControlEnable(_theme, ID_SUCCESS_RESTART_BUTTON, showRestartButton); if (_command.action != BOOTSTRAPPER_ACTION_INSTALL || !IsWindowsVersionOrGreater(10, 0, 0)) { ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE); } else { DWORD dataType = 0, buffer = 0, bufferLen = sizeof(buffer); HKEY hKey; LRESULT res = RegOpenKeyExW( HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\FileSystem", 0, KEY_READ, &hKey ); if (res == ERROR_SUCCESS) { res = RegQueryValueExW(hKey, L"LongPathsEnabled", nullptr, &dataType, (LPBYTE)&buffer, &bufferLen); RegCloseKey(hKey); } else { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Failed to open SYSTEM\\CurrentControlSet\\Control\\FileSystem: error code %d", res); } if (res == ERROR_SUCCESS && dataType == REG_DWORD && buffer == 0) { ThemeControlElevates(_theme, ID_SUCCESS_MAX_PATH_BUTTON, TRUE); } else { if (res == ERROR_SUCCESS) BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Failed to read LongPathsEnabled value: error code %d", res); else BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hiding MAX_PATH button because it is already enabled"); ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE); } } } void FailurePage_Show() { // on the "Failure" page, show error message and check if the restart button should be enabled. // if there is a log file variable then we'll assume the log file exists. BOOL showLogLink = (_bundle.sczLogVariable && *_bundle.sczLogVariable); BOOL showErrorMessage = FALSE; BOOL showRestartButton = FALSE; if (FAILED(_hrFinal)) { LPWSTR unformattedText = nullptr; LPWSTR text = nullptr; // If we know the failure message, use that. if (_failedMessage && *_failedMessage) { StrAllocString(&unformattedText, _failedMessage, 0); } else { // try to get the error message from the error code. StrAllocFromError(&unformattedText, _hrFinal, nullptr); if (!unformattedText || !*unformattedText) { StrAllocFromError(&unformattedText, E_FAIL, nullptr); } } if (E_WIXSTDBA_CONDITION_FAILED == _hrFinal) { if (unformattedText) { StrAllocString(&text, unformattedText, 0); } } else { StrAllocFormatted(&text, L"0x%08x - %ls", _hrFinal, unformattedText); } if (text) { ThemeSetTextControl(_theme, ID_FAILURE_MESSAGE_TEXT, text); showErrorMessage = TRUE; } ReleaseStr(text); ReleaseStr(unformattedText); } if (_restartRequired && BOOTSTRAPPER_RESTART_PROMPT == _command.restart) { showRestartButton = TRUE; } ThemeControlEnable(_theme, ID_FAILURE_LOGFILE_LINK, showLogLink); ThemeControlEnable(_theme, ID_FAILURE_MESSAGE_TEXT, showErrorMessage); ThemeControlEnable(_theme, ID_FAILURE_RESTART_TEXT, showRestartButton); ThemeControlEnable(_theme, ID_FAILURE_RESTART_BUTTON, showRestartButton); } static void EnableMaxPathSupport() { LPWSTR targetDir = nullptr, defaultDir = nullptr; HRESULT hr = BalGetStringVariable(L"TargetDir", &targetDir); if (FAILED(hr) || !targetDir || !targetDir[0]) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to get TargetDir"); return; } LPWSTR pythonw = nullptr; StrAllocFormatted(&pythonw, L"%ls\\pythonw.exe", targetDir); if (!pythonw || !pythonw[0]) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to construct pythonw.exe path"); return; } LPCWSTR arguments = L"-c \"import winreg; " "winreg.SetValueEx(" "winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, " "r'SYSTEM\\CurrentControlSet\\Control\\FileSystem'), " "'LongPathsEnabled', " "None, " "winreg.REG_DWORD, " "1" ")\""; BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Executing %ls %ls", pythonw, arguments); HINSTANCE res = ShellExecuteW(0, L"runas", pythonw, arguments, NULL, SW_HIDE); BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "return code 0x%08x", res); } public: // IBootstrapperApplication virtual STDMETHODIMP OnStartup() { HRESULT hr = S_OK; DWORD dwUIThreadId = 0; // create UI thread _hUiThread = ::CreateThread(nullptr, 0, UiThreadProc, this, 0, &dwUIThreadId); if (!_hUiThread) { ExitWithLastError(hr, "Failed to create UI thread."); } LExit: return hr; } virtual STDMETHODIMP_(int) OnShutdown() { int nResult = IDNOACTION; // wait for UI thread to terminate if (_hUiThread) { ::WaitForSingleObject(_hUiThread, INFINITE); ReleaseHandle(_hUiThread); } // If a restart was required. if (_restartRequired && _allowRestart) { nResult = IDRESTART; } return nResult; } virtual STDMETHODIMP_(int) OnDetectRelatedMsiPackage( __in_z LPCWSTR wzPackageId, __in_z LPCWSTR /*wzProductCode*/, __in BOOL fPerMachine, __in DWORD64 /*dw64Version*/, __in BOOTSTRAPPER_RELATED_OPERATION operation ) { // Only check launcher_AllUsers because we'll find the same packages // twice if we check launcher_JustForMe as well. if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_AllUsers", -1)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected existing launcher install"); LONGLONG blockedLauncher, detectedLauncher; if (FAILED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher))) { blockedLauncher = 0; } // Get the prior DetectedLauncher value so we can see if we've // detected more than one, and then update the stored variable // (we use the original value later on via the local). if (FAILED(BalGetNumericVariable(L"DetectedLauncher", &detectedLauncher))) { detectedLauncher = 0; } if (!detectedLauncher) { _engine->SetVariableNumeric(L"DetectedLauncher", 1); } if (blockedLauncher) { // Nothing else to do, we're already blocking } else if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) { // Found a higher version, so we can't install ours. BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Higher version launcher has been detected."); BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Launcher will not be installed"); _engine->SetVariableNumeric(L"BlockedLauncher", 1); } else if (detectedLauncher) { if (!blockedLauncher) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Multiple launcher installs have been detected."); BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "No launcher will be installed or upgraded until one has been removed."); _engine->SetVariableNumeric(L"BlockedLauncher", 1); } } else if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation) { // Found an older version, so let's run the equivalent as an upgrade // This overrides "unknown" all users options, but will leave alone // any that have already been set/detected. // User can deselect the option to include the launcher, but cannot // change it from the current per user/machine setting. LONGLONG includeLauncher, includeLauncherAllUsers; if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))) { includeLauncher = -1; } if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &includeLauncherAllUsers))) { includeLauncherAllUsers = -1; } if (includeLauncher < 0) { _engine->SetVariableNumeric(L"Include_launcher", 1); } if (includeLauncherAllUsers < 0) { _engine->SetVariableNumeric(L"InstallLauncherAllUsers", fPerMachine ? 1 : 0); } else if (includeLauncherAllUsers != fPerMachine ? 1 : 0) { // Requested AllUsers option is inconsistent, so block _engine->SetVariableNumeric(L"BlockedLauncher", 1); } _engine->SetVariableNumeric(L"DetectedOldLauncher", 1); } } return CheckCanceled() ? IDCANCEL : IDNOACTION; } virtual STDMETHODIMP_(int) OnDetectRelatedBundle( __in LPCWSTR wzBundleId, __in BOOTSTRAPPER_RELATION_TYPE relationType, __in LPCWSTR /*wzBundleTag*/, __in BOOL fPerMachine, __in DWORD64 /*dw64Version*/, __in BOOTSTRAPPER_RELATED_OPERATION operation ) { BalInfoAddRelatedBundleAsPackage(&_bundle.packages, wzBundleId, relationType, fPerMachine); // Remember when our bundle would cause a downgrade. if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) { _downgradingOtherVersion = TRUE; } else if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected previous version - planning upgrade"); _upgrading = TRUE; LoadOptionalFeatureStates(_engine); } else if (BOOTSTRAPPER_RELATED_OPERATION_NONE == operation) { if (_command.action == BOOTSTRAPPER_ACTION_INSTALL) { LOC_STRING *pLocString = nullptr; if (SUCCEEDED(LocGetString(_wixLoc, L"#(loc.FailureExistingInstall)", &pLocString)) && pLocString) { BalFormatString(pLocString->wzText, &_failedMessage); } else { BalFormatString(L"Cannot install [WixBundleName] because it is already installed.", &_failedMessage); } BalLog( BOOTSTRAPPER_LOG_LEVEL_ERROR, "Related bundle %ls is preventing install", wzBundleId ); SetState(PYBA_STATE_FAILED, E_WIXSTDBA_CONDITION_FAILED); } } return CheckCanceled() ? IDCANCEL : IDOK; } virtual STDMETHODIMP_(void) OnDetectPackageComplete( __in LPCWSTR wzPackageId, __in HRESULT hrStatus, __in BOOTSTRAPPER_PACKAGE_STATE state ) { } virtual STDMETHODIMP_(void) OnDetectComplete(__in HRESULT hrStatus) { if (SUCCEEDED(hrStatus) && _baFunction) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect complete BA function"); _baFunction->OnDetectComplete(); } if (SUCCEEDED(hrStatus)) { // Update launcher install states // If we didn't detect any existing installs, Include_launcher and // InstallLauncherAllUsers will both be -1, so we will set to their // defaults and leave the options enabled. // Otherwise, if we detected an existing install, we disable the // options so they remain fixed. // The code in OnDetectRelatedMsiPackage is responsible for figuring // out whether existing installs are compatible with the settings in // place during detection. LONGLONG blockedLauncher; if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) && blockedLauncher) { _engine->SetVariableNumeric(L"Include_launcher", 0); _engine->SetVariableNumeric(L"InstallLauncherAllUsers", 0); _engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable"); _engine->SetVariableString(L"Include_launcherState", L"disable"); } else { LONGLONG includeLauncher, includeLauncherAllUsers, associateFiles; if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))) { includeLauncher = -1; } if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &includeLauncherAllUsers))) { includeLauncherAllUsers = -1; } if (FAILED(BalGetNumericVariable(L"AssociateFiles", &associateFiles))) { associateFiles = -1; } if (includeLauncherAllUsers < 0) { includeLauncherAllUsers = 0; _engine->SetVariableNumeric(L"InstallLauncherAllUsers", includeLauncherAllUsers); } if (includeLauncher < 0) { if (BOOTSTRAPPER_ACTION_LAYOUT == _command.action || (BOOTSTRAPPER_ACTION_INSTALL == _command.action && !_upgrading)) { // When installing/downloading, we include the launcher // (though downloads should ignore this setting anyway) _engine->SetVariableNumeric(L"Include_launcher", 1); } else { // Any other action, we should have detected an existing // install (e.g. on remove/modify), so if we didn't, we // assume it's not selected. _engine->SetVariableNumeric(L"Include_launcher", 0); _engine->SetVariableNumeric(L"AssociateFiles", 0); } } if (associateFiles < 0) { auto hr = LoadAssociateFilesStateFromKey( _engine, includeLauncherAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER ); if (FAILED(hr)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load AssociateFiles state: error code 0x%08X", hr); } else if (hr == S_OK) { associateFiles = 1; } _engine->SetVariableNumeric(L"AssociateFiles", associateFiles); } } } if (SUCCEEDED(hrStatus)) { hrStatus = EvaluateConditions(); } if (SUCCEEDED(hrStatus)) { // Ensure the default path has been set hrStatus = EnsureTargetDir(); } SetState(PYBA_STATE_DETECTED, hrStatus); // If we're not interacting with the user or we're doing a layout or we're just after a force restart // then automatically start planning. if (BOOTSTRAPPER_DISPLAY_FULL > _command.display || BOOTSTRAPPER_ACTION_LAYOUT == _command.action || BOOTSTRAPPER_ACTION_UNINSTALL == _command.action || BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType) { if (SUCCEEDED(hrStatus)) { ::PostMessageW(_hWnd, WM_PYBA_PLAN_PACKAGES, 0, _command.action); } } } virtual STDMETHODIMP_(int) OnPlanRelatedBundle( __in_z LPCWSTR /*wzBundleId*/, __inout_z BOOTSTRAPPER_REQUEST_STATE* pRequestedState ) { return CheckCanceled() ? IDCANCEL : IDOK; } virtual STDMETHODIMP_(int) OnPlanPackageBegin( __in_z LPCWSTR wzPackageId, __inout BOOTSTRAPPER_REQUEST_STATE *pRequestState ) { HRESULT hr = S_OK; BAL_INFO_PACKAGE* pPackage = nullptr; if (_nextPackageAfterRestart) { // After restart we need to finish the dependency registration for our package so allow the package // to go present. if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, _nextPackageAfterRestart, -1)) { // Do not allow a repair because that could put us in a perpetual restart loop. if (BOOTSTRAPPER_REQUEST_STATE_REPAIR == *pRequestState) { *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; } ReleaseNullStr(_nextPackageAfterRestart); // no more skipping now. } else { // not the matching package, so skip it. BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Skipping package: %ls, after restart because it was applied before the restart.", wzPackageId); *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; } } else if ((_plannedAction == BOOTSTRAPPER_ACTION_INSTALL || _plannedAction == BOOTSTRAPPER_ACTION_MODIFY) && SUCCEEDED(BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage))) { BOOL f = FALSE; if (SUCCEEDED(_engine->EvaluateCondition(pPackage->sczInstallCondition, &f)) && f) { *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; } } return CheckCanceled() ? IDCANCEL : IDOK; } virtual STDMETHODIMP_(int) OnPlanMsiFeature( __in_z LPCWSTR wzPackageId, __in_z LPCWSTR wzFeatureId, __inout BOOTSTRAPPER_FEATURE_STATE* pRequestedState ) { LONGLONG install; if (wcscmp(wzFeatureId, L"AssociateFiles") == 0 || wcscmp(wzFeatureId, L"Shortcuts") == 0) { if (SUCCEEDED(_engine->GetVariableNumeric(wzFeatureId, &install)) && install) { *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL; } else { *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_ABSENT; } } else { *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL; } return CheckCanceled() ? IDCANCEL : IDNOACTION; } virtual STDMETHODIMP_(void) OnPlanComplete(__in HRESULT hrStatus) { if (SUCCEEDED(hrStatus) && _baFunction) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan complete BA function"); _baFunction->OnPlanComplete(); } SetState(PYBA_STATE_PLANNED, hrStatus); if (SUCCEEDED(hrStatus)) { ::PostMessageW(_hWnd, WM_PYBA_APPLY_PACKAGES, 0, 0); } _startedExecution = FALSE; _calculatedCacheProgress = 0; _calculatedExecuteProgress = 0; } virtual STDMETHODIMP_(int) OnCachePackageBegin( __in_z LPCWSTR wzPackageId, __in DWORD cCachePayloads, __in DWORD64 dw64PackageCacheSize ) { if (wzPackageId && *wzPackageId) { BAL_INFO_PACKAGE* pPackage = nullptr; HRESULT hr = BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage); LPCWSTR wz = (SUCCEEDED(hr) && pPackage->sczDisplayName) ? pPackage->sczDisplayName : wzPackageId; ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, wz); // If something started executing, leave it in the overall progress text. if (!_startedExecution) { ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz); } } return __super::OnCachePackageBegin(wzPackageId, cCachePayloads, dw64PackageCacheSize); } virtual STDMETHODIMP_(int) OnCacheAcquireProgress( __in_z LPCWSTR wzPackageOrContainerId, __in_z_opt LPCWSTR wzPayloadId, __in DWORD64 dw64Progress, __in DWORD64 dw64Total, __in DWORD dwOverallPercentage ) { WCHAR wzProgress[5] = { }; #ifdef DEBUG BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnCacheAcquireProgress() - container/package: %ls, payload: %ls, progress: %I64u, total: %I64u, overall progress: %u%%", wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage); #endif ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallPercentage); ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_TEXT, wzProgress); ThemeSetProgressControl(_theme, ID_CACHE_PROGRESS_BAR, dwOverallPercentage); _calculatedCacheProgress = dwOverallPercentage * PYBA_ACQUIRE_PERCENTAGE / 100; ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress); SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress); return __super::OnCacheAcquireProgress(wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage); } virtual STDMETHODIMP_(int) OnCacheAcquireComplete( __in_z LPCWSTR wzPackageOrContainerId, __in_z_opt LPCWSTR wzPayloadId, __in HRESULT hrStatus, __in int nRecommendation ) { SetProgressState(hrStatus); return __super::OnCacheAcquireComplete(wzPackageOrContainerId, wzPayloadId, hrStatus, nRecommendation); } virtual STDMETHODIMP_(int) OnCacheVerifyComplete( __in_z LPCWSTR wzPackageId, __in_z LPCWSTR wzPayloadId, __in HRESULT hrStatus, __in int nRecommendation ) { SetProgressState(hrStatus); return __super::OnCacheVerifyComplete(wzPackageId, wzPayloadId, hrStatus, nRecommendation); } virtual STDMETHODIMP_(void) OnCacheComplete(__in HRESULT /*hrStatus*/) { ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, L""); SetState(PYBA_STATE_CACHED, S_OK); // we always return success here and let OnApplyComplete() deal with the error. } virtual STDMETHODIMP_(int) OnError( __in BOOTSTRAPPER_ERROR_TYPE errorType, __in LPCWSTR wzPackageId, __in DWORD dwCode, __in_z LPCWSTR wzError, __in DWORD dwUIHint, __in DWORD /*cData*/, __in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/, __in int nRecommendation ) { int nResult = nRecommendation; LPWSTR sczError = nullptr; if (BOOTSTRAPPER_DISPLAY_EMBEDDED == _command.display) { HRESULT hr = _engine->SendEmbeddedError(dwCode, wzError, dwUIHint, &nResult); if (FAILED(hr)) { nResult = IDERROR; } } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) { // If this is an authentication failure, let the engine try to handle it for us. if (BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER == errorType || BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY == errorType) { nResult = IDTRYAGAIN; } else // show a generic error message box. { BalRetryErrorOccurred(wzPackageId, dwCode); if (!_showingInternalUIThisPackage) { // If no error message was provided, use the error code to try and get an error message. if (!wzError || !*wzError || BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER != errorType) { HRESULT hr = StrAllocFromError(&sczError, dwCode, nullptr); if (FAILED(hr) || !sczError || !*sczError) { StrAllocFormatted(&sczError, L"0x%x", dwCode); } } nResult = ::MessageBoxW(_hWnd, sczError ? sczError : wzError, _theme->sczCaption, dwUIHint); } } SetProgressState(HRESULT_FROM_WIN32(dwCode)); } else { // just take note of the error code and let things continue. BalRetryErrorOccurred(wzPackageId, dwCode); } ReleaseStr(sczError); return nResult; } virtual STDMETHODIMP_(int) OnExecuteMsiMessage( __in_z LPCWSTR wzPackageId, __in INSTALLMESSAGE mt, __in UINT uiFlags, __in_z LPCWSTR wzMessage, __in DWORD cData, __in_ecount_z_opt(cData) LPCWSTR* rgwzData, __in int nRecommendation ) { #ifdef DEBUG BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteMsiMessage() - package: %ls, message: %ls", wzPackageId, wzMessage); #endif if (BOOTSTRAPPER_DISPLAY_FULL == _command.display && (INSTALLMESSAGE_WARNING == mt || INSTALLMESSAGE_USER == mt)) { int nResult = ::MessageBoxW(_hWnd, wzMessage, _theme->sczCaption, uiFlags); return nResult; } if (INSTALLMESSAGE_ACTIONSTART == mt) { ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, wzMessage); } return __super::OnExecuteMsiMessage(wzPackageId, mt, uiFlags, wzMessage, cData, rgwzData, nRecommendation); } virtual STDMETHODIMP_(int) OnProgress(__in DWORD dwProgressPercentage, __in DWORD dwOverallProgressPercentage) { WCHAR wzProgress[5] = { }; #ifdef DEBUG BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnProgress() - progress: %u%%, overall progress: %u%%", dwProgressPercentage, dwOverallProgressPercentage); #endif ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage); ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_TEXT, wzProgress); ThemeSetProgressControl(_theme, ID_OVERALL_PROGRESS_BAR, dwOverallProgressPercentage); SetTaskbarButtonProgress(dwOverallProgressPercentage); return __super::OnProgress(dwProgressPercentage, dwOverallProgressPercentage); } virtual STDMETHODIMP_(int) OnExecutePackageBegin(__in_z LPCWSTR wzPackageId, __in BOOL fExecute) { LPWSTR sczFormattedString = nullptr; _startedExecution = TRUE; if (wzPackageId && *wzPackageId) { BAL_INFO_PACKAGE* pPackage = nullptr; BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage); LPCWSTR wz = wzPackageId; if (pPackage) { LOC_STRING* pLocString = nullptr; switch (pPackage->type) { case BAL_INFO_PACKAGE_TYPE_BUNDLE_ADDON: LocGetString(_wixLoc, L"#(loc.ExecuteAddonRelatedBundleMessage)", &pLocString); break; case BAL_INFO_PACKAGE_TYPE_BUNDLE_PATCH: LocGetString(_wixLoc, L"#(loc.ExecutePatchRelatedBundleMessage)", &pLocString); break; case BAL_INFO_PACKAGE_TYPE_BUNDLE_UPGRADE: LocGetString(_wixLoc, L"#(loc.ExecuteUpgradeRelatedBundleMessage)", &pLocString); break; } if (pLocString) { // If the wix developer is showing a hidden variable in the UI, then obviously they don't care about keeping it safe // so don't go down the rabbit hole of making sure that this is securely freed. BalFormatString(pLocString->wzText, &sczFormattedString); } wz = sczFormattedString ? sczFormattedString : pPackage->sczDisplayName ? pPackage->sczDisplayName : wzPackageId; } _showingInternalUIThisPackage = pPackage && pPackage->fDisplayInternalUI; ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, wz); ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz); } else { _showingInternalUIThisPackage = FALSE; } ReleaseStr(sczFormattedString); return __super::OnExecutePackageBegin(wzPackageId, fExecute); } virtual int __stdcall OnExecuteProgress( __in_z LPCWSTR wzPackageId, __in DWORD dwProgressPercentage, __in DWORD dwOverallProgressPercentage ) { WCHAR wzProgress[8] = { }; #ifdef DEBUG BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteProgress() - package: %ls, progress: %u%%, overall progress: %u%%", wzPackageId, dwProgressPercentage, dwOverallProgressPercentage); #endif ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage); ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_TEXT, wzProgress); ThemeSetProgressControl(_theme, ID_EXECUTE_PROGRESS_BAR, dwOverallProgressPercentage); _calculatedExecuteProgress = dwOverallProgressPercentage * (100 - PYBA_ACQUIRE_PERCENTAGE) / 100; ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress); SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress); return __super::OnExecuteProgress(wzPackageId, dwProgressPercentage, dwOverallProgressPercentage); } virtual STDMETHODIMP_(int) OnExecutePackageComplete( __in_z LPCWSTR wzPackageId, __in HRESULT hrExitCode, __in BOOTSTRAPPER_APPLY_RESTART restart, __in int nRecommendation ) { SetProgressState(hrExitCode); if (_wcsnicmp(wzPackageId, L"path_", 5) == 0 && SUCCEEDED(hrExitCode)) { SendMessageTimeoutW( HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast(L"Environment"), SMTO_ABORTIFHUNG, 1000, nullptr ); } int nResult = __super::OnExecutePackageComplete(wzPackageId, hrExitCode, restart, nRecommendation); return nResult; } virtual STDMETHODIMP_(void) OnExecuteComplete(__in HRESULT hrStatus) { ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L""); ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L""); ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, L""); ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE); // no more cancel. SetState(PYBA_STATE_EXECUTED, S_OK); // we always return success here and let OnApplyComplete() deal with the error. SetProgressState(hrStatus); } virtual STDMETHODIMP_(int) OnResolveSource( __in_z LPCWSTR wzPackageOrContainerId, __in_z_opt LPCWSTR wzPayloadId, __in_z LPCWSTR wzLocalSource, __in_z_opt LPCWSTR wzDownloadSource ) { int nResult = IDERROR; // assume we won't resolve source and that is unexpected. if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) { if (wzDownloadSource) { nResult = IDDOWNLOAD; } else { // prompt to change the source location. OPENFILENAMEW ofn = { }; WCHAR wzFile[MAX_PATH] = { }; ::StringCchCopyW(wzFile, countof(wzFile), wzLocalSource); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = _hWnd; ofn.lpstrFile = wzFile; ofn.nMaxFile = countof(wzFile); ofn.lpstrFilter = L"All Files\0*.*\0"; ofn.nFilterIndex = 1; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; ofn.lpstrTitle = _theme->sczCaption; if (::GetOpenFileNameW(&ofn)) { HRESULT hr = _engine->SetLocalSource(wzPackageOrContainerId, wzPayloadId, ofn.lpstrFile); nResult = SUCCEEDED(hr) ? IDRETRY : IDERROR; } else { nResult = IDCANCEL; } } } else if (wzDownloadSource) { // If doing a non-interactive install and download source is available, let's try downloading the package silently nResult = IDDOWNLOAD; } // else there's nothing more we can do in non-interactive mode return CheckCanceled() ? IDCANCEL : nResult; } virtual STDMETHODIMP_(int) OnApplyComplete(__in HRESULT hrStatus, __in BOOTSTRAPPER_APPLY_RESTART restart) { _restartResult = restart; // remember the restart result so we return the correct error code no matter what the user chooses to do in the UI. // If a restart was encountered and we are not suppressing restarts, then restart is required. _restartRequired = (BOOTSTRAPPER_APPLY_RESTART_NONE != restart && BOOTSTRAPPER_RESTART_NEVER < _command.restart); // 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. _allowRestart = _restartRequired && (BOOTSTRAPPER_DISPLAY_FULL > _command.display || BOOTSTRAPPER_RESTART_PROMPT < _command.restart); // If we are showing UI, wait a beat before moving to the final screen. if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { ::Sleep(250); } SetState(PYBA_STATE_APPLIED, hrStatus); SetTaskbarButtonProgress(100); // show full progress bar, green, yellow, or red return IDNOACTION; } virtual STDMETHODIMP_(void) OnLaunchApprovedExeComplete(__in HRESULT hrStatus, __in DWORD /*processId*/) { } private: // // UiThreadProc - entrypoint for UI thread. // static DWORD WINAPI UiThreadProc(__in LPVOID pvContext) { HRESULT hr = S_OK; PythonBootstrapperApplication* pThis = (PythonBootstrapperApplication*)pvContext; BOOL comInitialized = FALSE; BOOL ret = FALSE; MSG msg = { }; // Initialize COM and theme. hr = ::CoInitialize(nullptr); BalExitOnFailure(hr, "Failed to initialize COM."); comInitialized = TRUE; hr = ThemeInitialize(pThis->_hModule); BalExitOnFailure(hr, "Failed to initialize theme manager."); hr = pThis->InitializeData(); BalExitOnFailure(hr, "Failed to initialize data in bootstrapper application."); // Create main window. pThis->InitializeTaskbarButton(); hr = pThis->CreateMainWindow(); BalExitOnFailure(hr, "Failed to create main window."); pThis->ValidateOperatingSystem(); if (FAILED(pThis->_hrFinal)) { pThis->SetState(PYBA_STATE_FAILED, hr); ::PostMessageW(pThis->_hWnd, WM_PYBA_SHOW_FAILURE, 0, 0); } else { // Okay, we're ready for packages now. pThis->SetState(PYBA_STATE_INITIALIZED, hr); ::PostMessageW(pThis->_hWnd, BOOTSTRAPPER_ACTION_HELP == pThis->_command.action ? WM_PYBA_SHOW_HELP : WM_PYBA_DETECT_PACKAGES, 0, 0); } // message pump while (0 != (ret = ::GetMessageW(&msg, nullptr, 0, 0))) { if (-1 == ret) { hr = E_UNEXPECTED; BalExitOnFailure(hr, "Unexpected return value from message pump."); } else if (!ThemeHandleKeyboardMessage(pThis->_theme, msg.hwnd, &msg)) { ::TranslateMessage(&msg); ::DispatchMessageW(&msg); } } // Succeeded thus far, check to see if anything went wrong while actually // executing changes. if (FAILED(pThis->_hrFinal)) { hr = pThis->_hrFinal; } else if (pThis->CheckCanceled()) { hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); } LExit: // destroy main window pThis->DestroyMainWindow(); // initiate engine shutdown DWORD dwQuit = HRESULT_CODE(hr); if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == pThis->_restartResult) { dwQuit = ERROR_SUCCESS_REBOOT_INITIATED; } else if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == pThis->_restartResult) { dwQuit = ERROR_SUCCESS_REBOOT_REQUIRED; } pThis->_engine->Quit(dwQuit); ReleaseTheme(pThis->_theme); ThemeUninitialize(); // uninitialize COM if (comInitialized) { ::CoUninitialize(); } return hr; } // // ParseVariablesFromUnattendXml - reads options from unattend.xml if it // exists // HRESULT ParseVariablesFromUnattendXml() { HRESULT hr = S_OK; LPWSTR sczUnattendXmlPath = nullptr; IXMLDOMDocument *pixdUnattend = nullptr; IXMLDOMNodeList *pNodes = nullptr; IXMLDOMNode *pNode = nullptr; long cNodes; DWORD dwAttr; LPWSTR scz = nullptr; BOOL bValue; int iValue; BOOL tryConvert; BSTR bstrValue = nullptr; hr = BalFormatString(L"[WixBundleOriginalSourceFolder]unattend.xml", &sczUnattendXmlPath); BalExitOnFailure(hr, "Failed to calculate path to unattend.xml"); if (!FileExistsEx(sczUnattendXmlPath, &dwAttr)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_VERBOSE, "Did not find %ls", sczUnattendXmlPath); hr = S_FALSE; goto LExit; } hr = XmlLoadDocumentFromFile(sczUnattendXmlPath, &pixdUnattend); BalExitOnFailure1(hr, "Failed to read %ls", sczUnattendXmlPath); // get the list of variables users have overridden hr = XmlSelectNodes(pixdUnattend, L"/Options/Option", &pNodes); if (S_FALSE == hr) { ExitFunction1(hr = S_OK); } BalExitOnFailure(hr, "Failed to select option nodes."); hr = pNodes->get_length((long*)&cNodes); BalExitOnFailure(hr, "Failed to get option node count."); BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Reading settings from %ls", sczUnattendXmlPath); for (DWORD i = 0; i < cNodes; ++i) { hr = XmlNextElement(pNodes, &pNode, nullptr); BalExitOnFailure(hr, "Failed to get next node."); // @Name hr = XmlGetAttributeEx(pNode, L"Name", &scz); BalExitOnFailure(hr, "Failed to get @Name."); tryConvert = TRUE; hr = XmlGetAttribute(pNode, L"Value", &bstrValue); if (FAILED(hr) || !bstrValue || !*bstrValue) { hr = XmlGetText(pNode, &bstrValue); tryConvert = FALSE; } BalExitOnFailure(hr, "Failed to get @Value."); if (tryConvert && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, bstrValue, -1, L"yes", -1)) { _engine->SetVariableNumeric(scz, 1); } else if (tryConvert && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, bstrValue, -1, L"no", -1)) { _engine->SetVariableNumeric(scz, 0); } else if (tryConvert && ::StrToIntExW(bstrValue, STIF_DEFAULT, &iValue)) { _engine->SetVariableNumeric(scz, iValue); } else { _engine->SetVariableString(scz, bstrValue); } ReleaseNullBSTR(bstrValue); ReleaseNullStr(scz); ReleaseNullObject(pNode); } BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Finished reading from %ls", sczUnattendXmlPath); LExit: ReleaseObject(pNode); ReleaseObject(pNodes); ReleaseObject(pixdUnattend); ReleaseStr(sczUnattendXmlPath); return hr; } // // InitializeData - initializes all the package information. // HRESULT InitializeData() { HRESULT hr = S_OK; LPWSTR sczModulePath = nullptr; IXMLDOMDocument *pixdManifest = nullptr; hr = BalManifestLoad(_hModule, &pixdManifest); BalExitOnFailure(hr, "Failed to load bootstrapper application manifest."); hr = ParseOverridableVariablesFromXml(pixdManifest); BalExitOnFailure(hr, "Failed to read overridable variables."); if (_command.action == BOOTSTRAPPER_ACTION_MODIFY) { LoadOptionalFeatureStates(_engine); } hr = ParseVariablesFromUnattendXml(); ExitOnFailure(hr, "Failed to read unattend.ini file."); hr = ProcessCommandLine(&_language); ExitOnFailure(hr, "Unknown commandline parameters."); hr = PathRelativeToModule(&sczModulePath, nullptr, _hModule); BalExitOnFailure(hr, "Failed to get module path."); hr = LoadLocalization(sczModulePath, _language); ExitOnFailure(hr, "Failed to load localization."); hr = LoadTheme(sczModulePath, _language); ExitOnFailure(hr, "Failed to load theme."); hr = BalInfoParseFromXml(&_bundle, pixdManifest); BalExitOnFailure(hr, "Failed to load bundle information."); hr = BalConditionsParseFromXml(&_conditions, pixdManifest, _wixLoc); BalExitOnFailure(hr, "Failed to load conditions from XML."); hr = LoadBootstrapperBAFunctions(); BalExitOnFailure(hr, "Failed to load bootstrapper functions."); hr = UpdateUIStrings(_command.action); BalExitOnFailure(hr, "Failed to load UI strings."); GetBundleFileVersion(); // don't fail if we couldn't get the version info; best-effort only LExit: ReleaseObject(pixdManifest); ReleaseStr(sczModulePath); return hr; } // // ProcessCommandLine - process the provided command line arguments. // HRESULT ProcessCommandLine(__inout LPWSTR* psczLanguage) { HRESULT hr = S_OK; int argc = 0; LPWSTR* argv = nullptr; LPWSTR sczVariableName = nullptr; LPWSTR sczVariableValue = nullptr; if (_command.wzCommandLine && *_command.wzCommandLine) { argv = ::CommandLineToArgvW(_command.wzCommandLine, &argc); ExitOnNullWithLastError(argv, hr, "Failed to get command line."); for (int i = 0; i < argc; ++i) { if (argv[i][0] == L'-' || argv[i][0] == L'/') { if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1)) { if (i + 1 >= argc) { hr = E_INVALIDARG; BalExitOnFailure(hr, "Must specify a language."); } ++i; hr = StrAllocString(psczLanguage, &argv[i][0], 0); BalExitOnFailure(hr, "Failed to copy language."); } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"simple", -1)) { _engine->SetVariableNumeric(L"SimpleInstall", 1); } } else if (_overridableVariables) { int value; const wchar_t* pwc = wcschr(argv[i], L'='); if (pwc) { hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]); BalExitOnFailure(hr, "Failed to copy variable name."); hr = DictKeyExists(_overridableVariables, sczVariableName); if (E_NOTFOUND == hr) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Ignoring attempt to set non-overridable variable: '%ls'.", sczVariableName); hr = S_OK; continue; } ExitOnFailure(hr, "Failed to check the dictionary of overridable variables."); hr = StrAllocString(&sczVariableValue, ++pwc, 0); BalExitOnFailure(hr, "Failed to copy variable value."); if (::StrToIntEx(sczVariableValue, STIF_DEFAULT, &value)) { hr = _engine->SetVariableNumeric(sczVariableName, value); } else { hr = _engine->SetVariableString(sczVariableName, sczVariableValue); } BalExitOnFailure(hr, "Failed to set variable."); } else { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]); } } } } LExit: if (argv) { ::LocalFree(argv); } ReleaseStr(sczVariableName); ReleaseStr(sczVariableValue); return hr; } HRESULT LoadLocalization(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) { HRESULT hr = S_OK; LPWSTR sczLocPath = nullptr; LPCWSTR wzLocFileName = L"Default.wxl"; hr = LocProbeForFile(wzModulePath, wzLocFileName, wzLanguage, &sczLocPath); BalExitOnFailure2(hr, "Failed to probe for loc file: %ls in path: %ls", wzLocFileName, wzModulePath); hr = LocLoadFromFile(sczLocPath, &_wixLoc); BalExitOnFailure1(hr, "Failed to load loc file from path: %ls", sczLocPath); if (WIX_LOCALIZATION_LANGUAGE_NOT_SET != _wixLoc->dwLangId) { ::SetThreadLocale(_wixLoc->dwLangId); } hr = StrAllocString(&_confirmCloseMessage, L"#(loc.ConfirmCancelMessage)", 0); ExitOnFailure(hr, "Failed to initialize confirm message loc identifier."); hr = LocLocalizeString(_wixLoc, &_confirmCloseMessage); BalExitOnFailure1(hr, "Failed to localize confirm close message: %ls", _confirmCloseMessage); LExit: ReleaseStr(sczLocPath); return hr; } HRESULT LoadTheme(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) { HRESULT hr = S_OK; LPWSTR sczThemePath = nullptr; LPCWSTR wzThemeFileName = L"Default.thm"; LPWSTR sczCaption = nullptr; hr = LocProbeForFile(wzModulePath, wzThemeFileName, wzLanguage, &sczThemePath); BalExitOnFailure2(hr, "Failed to probe for theme file: %ls in path: %ls", wzThemeFileName, wzModulePath); hr = ThemeLoadFromFile(sczThemePath, &_theme); BalExitOnFailure1(hr, "Failed to load theme from path: %ls", sczThemePath); hr = ThemeLocalize(_theme, _wixLoc); BalExitOnFailure1(hr, "Failed to localize theme: %ls", sczThemePath); // Update the caption if there are any formatted strings in it. // If the wix developer is showing a hidden variable in the UI, then // obviously they don't care about keeping it safe so don't go down the // rabbit hole of making sure that this is securely freed. hr = BalFormatString(_theme->sczCaption, &sczCaption); if (SUCCEEDED(hr)) { ThemeUpdateCaption(_theme, sczCaption); } LExit: ReleaseStr(sczCaption); ReleaseStr(sczThemePath); return hr; } HRESULT ParseOverridableVariablesFromXml(__in IXMLDOMDocument* pixdManifest) { HRESULT hr = S_OK; IXMLDOMNode* pNode = nullptr; IXMLDOMNodeList* pNodes = nullptr; DWORD cNodes = 0; LPWSTR scz = nullptr; BOOL hidden = FALSE; // get the list of variables users can override on the command line hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes); if (S_FALSE == hr) { ExitFunction1(hr = S_OK); } ExitOnFailure(hr, "Failed to select overridable variable nodes."); hr = pNodes->get_length((long*)&cNodes); ExitOnFailure(hr, "Failed to get overridable variable node count."); if (cNodes) { hr = DictCreateStringList(&_overridableVariables, 32, DICT_FLAG_NONE); ExitOnFailure(hr, "Failed to create the string dictionary."); for (DWORD i = 0; i < cNodes; ++i) { hr = XmlNextElement(pNodes, &pNode, nullptr); ExitOnFailure(hr, "Failed to get next node."); // @Name hr = XmlGetAttributeEx(pNode, L"Name", &scz); ExitOnFailure(hr, "Failed to get @Name."); hr = XmlGetYesNoAttribute(pNode, L"Hidden", &hidden); if (!hidden) { hr = DictAddKey(_overridableVariables, scz); ExitOnFailure1(hr, "Failed to add \"%ls\" to the string dictionary.", scz); } // prepare next iteration ReleaseNullObject(pNode); } } LExit: ReleaseObject(pNode); ReleaseObject(pNodes); ReleaseStr(scz); return hr; } // // Get the file version of the bootstrapper and record in bootstrapper log file // HRESULT GetBundleFileVersion() { HRESULT hr = S_OK; ULARGE_INTEGER uliVersion = { }; LPWSTR sczCurrentPath = nullptr; hr = PathForCurrentProcess(&sczCurrentPath, nullptr); BalExitOnFailure(hr, "Failed to get bundle path."); hr = FileVersion(sczCurrentPath, &uliVersion.HighPart, &uliVersion.LowPart); BalExitOnFailure(hr, "Failed to get bundle file version."); hr = _engine->SetVariableVersion(PYBA_VARIABLE_BUNDLE_FILE_VERSION, uliVersion.QuadPart); BalExitOnFailure(hr, "Failed to set WixBundleFileVersion variable."); LExit: ReleaseStr(sczCurrentPath); return hr; } // // CreateMainWindow - creates the main install window. // HRESULT CreateMainWindow() { HRESULT hr = S_OK; HICON hIcon = reinterpret_cast(_theme->hIcon); WNDCLASSW wc = { }; DWORD dwWindowStyle = 0; int x = CW_USEDEFAULT; int y = CW_USEDEFAULT; POINT ptCursor = { }; HMONITOR hMonitor = nullptr; MONITORINFO mi = { }; COLORREF fg, bg; HBRUSH bgBrush; // If the theme did not provide an icon, try using the icon from the bundle engine. if (!hIcon) { HMODULE hBootstrapperEngine = ::GetModuleHandleW(nullptr); if (hBootstrapperEngine) { hIcon = ::LoadIconW(hBootstrapperEngine, MAKEINTRESOURCEW(1)); } } fg = RGB(0, 0, 0); bg = RGB(255, 255, 255); bgBrush = (HBRUSH)(COLOR_WINDOW+1); if (_theme->dwFontId < _theme->cFonts) { THEME_FONT *font = &_theme->rgFonts[_theme->dwFontId]; fg = font->crForeground; bg = font->crBackground; bgBrush = font->hBackground; RemapColor(&fg, &bg, &bgBrush); } // Register the window class and create the window. wc.lpfnWndProc = PythonBootstrapperApplication::WndProc; wc.hInstance = _hModule; wc.hIcon = hIcon; wc.hCursor = ::LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW); wc.hbrBackground = bgBrush; wc.lpszMenuName = nullptr; wc.lpszClassName = PYBA_WINDOW_CLASS; if (!::RegisterClassW(&wc)) { ExitWithLastError(hr, "Failed to register window."); } _registered = TRUE; // Calculate the window style based on the theme style and command display value. dwWindowStyle = _theme->dwStyle; if (BOOTSTRAPPER_DISPLAY_NONE >= _command.display) { dwWindowStyle &= ~WS_VISIBLE; } // Don't show the window if there is a splash screen (it will be made visible when the splash screen is hidden) if (::IsWindow(_command.hwndSplashScreen)) { dwWindowStyle &= ~WS_VISIBLE; } // Center the window on the monitor with the mouse. if (::GetCursorPos(&ptCursor)) { hMonitor = ::MonitorFromPoint(ptCursor, MONITOR_DEFAULTTONEAREST); if (hMonitor) { mi.cbSize = sizeof(mi); if (::GetMonitorInfoW(hMonitor, &mi)) { x = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - _theme->nWidth) / 2; y = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - _theme->nHeight) / 2; } } } _hWnd = ::CreateWindowExW( 0, wc.lpszClassName, _theme->sczCaption, dwWindowStyle, x, y, _theme->nWidth, _theme->nHeight, HWND_DESKTOP, nullptr, _hModule, this ); ExitOnNullWithLastError(_hWnd, hr, "Failed to create window."); hr = S_OK; LExit: return hr; } // // InitializeTaskbarButton - initializes taskbar button for progress. // void InitializeTaskbarButton() { HRESULT hr = S_OK; hr = ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), reinterpret_cast(&_taskbarList)); if (REGDB_E_CLASSNOTREG == hr) { // not supported before Windows 7 ExitFunction1(hr = S_OK); } BalExitOnFailure(hr, "Failed to create ITaskbarList3. Continuing."); _taskbarButtonCreatedMessage = ::RegisterWindowMessageW(L"TaskbarButtonCreated"); BalExitOnNullWithLastError(_taskbarButtonCreatedMessage, hr, "Failed to get TaskbarButtonCreated message. Continuing."); LExit: return; } // // DestroyMainWindow - clean up all the window registration. // void DestroyMainWindow() { if (::IsWindow(_hWnd)) { ::DestroyWindow(_hWnd); _hWnd = nullptr; _taskbarButtonOK = FALSE; } if (_registered) { ::UnregisterClassW(PYBA_WINDOW_CLASS, _hModule); _registered = FALSE; } } // // WndProc - standard windows message handler. // static LRESULT CALLBACK WndProc( __in HWND hWnd, __in UINT uMsg, __in WPARAM wParam, __in LPARAM lParam ) { #pragma warning(suppress:4312) auto pBA = reinterpret_cast(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); switch (uMsg) { case WM_NCCREATE: { LPCREATESTRUCT lpcs = reinterpret_cast(lParam); pBA = reinterpret_cast(lpcs->lpCreateParams); #pragma warning(suppress:4244) ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(pBA)); break; } case WM_NCDESTROY: { LRESULT lres = ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam); ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); return lres; } case WM_CREATE: if (!pBA->OnCreate(hWnd)) { return -1; } break; case WM_QUERYENDSESSION: return IDCANCEL != pBA->OnSystemShutdown(static_cast(lParam), IDCANCEL); case WM_CLOSE: // If the user chose not to close, do *not* let the default window proc handle the message. if (!pBA->OnClose()) { return 0; } break; case WM_DESTROY: ::PostQuitMessage(0); break; case WM_PAINT: __fallthrough; case WM_ERASEBKGND: if (pBA && pBA->_suppressPaint) { return TRUE; } break; case WM_PYBA_SHOW_HELP: pBA->OnShowHelp(); return 0; case WM_PYBA_DETECT_PACKAGES: pBA->OnDetect(); return 0; case WM_PYBA_PLAN_PACKAGES: pBA->OnPlan(static_cast(lParam)); return 0; case WM_PYBA_APPLY_PACKAGES: pBA->OnApply(); return 0; case WM_PYBA_CHANGE_STATE: pBA->OnChangeState(static_cast(lParam)); return 0; case WM_PYBA_SHOW_FAILURE: pBA->OnShowFailure(); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { // Customize commands // Success/failure commands case ID_SUCCESS_RESTART_BUTTON: __fallthrough; case ID_FAILURE_RESTART_BUTTON: pBA->OnClickRestartButton(); return 0; case IDCANCEL: __fallthrough; case ID_INSTALL_CANCEL_BUTTON: __fallthrough; case ID_CUSTOM1_CANCEL_BUTTON: __fallthrough; case ID_CUSTOM2_CANCEL_BUTTON: __fallthrough; case ID_MODIFY_CANCEL_BUTTON: __fallthrough; case ID_PROGRESS_CANCEL_BUTTON: __fallthrough; case ID_SUCCESS_CANCEL_BUTTON: __fallthrough; case ID_FAILURE_CANCEL_BUTTON: __fallthrough; case ID_CLOSE_BUTTON: pBA->OnCommand(ID_CLOSE_BUTTON); return 0; default: pBA->OnCommand((CONTROL_ID)LOWORD(wParam)); } break; case WM_NOTIFY: if (lParam) { LPNMHDR pnmhdr = reinterpret_cast(lParam); switch (pnmhdr->code) { case NM_CLICK: __fallthrough; case NM_RETURN: switch (static_cast(pnmhdr->idFrom)) { case ID_FAILURE_LOGFILE_LINK: pBA->OnClickLogFileLink(); return 1; } } } break; case WM_CTLCOLORSTATIC: case WM_CTLCOLORBTN: if (pBA) { HBRUSH brush = nullptr; if (pBA->SetControlColor((HWND)lParam, (HDC)wParam, &brush)) { return (LRESULT)brush; } } break; } if (pBA && pBA->_taskbarList && uMsg == pBA->_taskbarButtonCreatedMessage) { pBA->_taskbarButtonOK = TRUE; return 0; } return ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam); } // // OnCreate - finishes loading the theme. // BOOL OnCreate(__in HWND hWnd) { HRESULT hr = S_OK; hr = ThemeLoadControls(_theme, hWnd, CONTROL_ID_NAMES, countof(CONTROL_ID_NAMES)); BalExitOnFailure(hr, "Failed to load theme controls."); C_ASSERT(COUNT_PAGE == countof(PAGE_NAMES)); C_ASSERT(countof(_pageIds) == countof(PAGE_NAMES)); ThemeGetPageIds(_theme, PAGE_NAMES, _pageIds, countof(_pageIds)); // Initialize the text on all "application" (non-page) controls. for (DWORD i = 0; i < _theme->cControls; ++i) { THEME_CONTROL* pControl = _theme->rgControls + i; LPWSTR text = nullptr; if (!pControl->wPageId && pControl->sczText && *pControl->sczText) { HRESULT hrFormat; // If the wix developer is showing a hidden variable in the UI, // then obviously they don't care about keeping it safe so don't // go down the rabbit hole of making sure that this is securely // freed. hrFormat = BalFormatString(pControl->sczText, &text); if (SUCCEEDED(hrFormat)) { ThemeSetTextControl(_theme, pControl->wId, text); ReleaseStr(text); } } } LExit: return SUCCEEDED(hr); } void RemapColor(COLORREF *fg, COLORREF *bg, HBRUSH *bgBrush) { if (*fg == RGB(0, 0, 0)) { *fg = GetSysColor(COLOR_WINDOWTEXT); } else if (*fg == RGB(128, 128, 128)) { *fg = GetSysColor(COLOR_GRAYTEXT); } if (*bgBrush && *bg == RGB(255, 255, 255)) { *bg = GetSysColor(COLOR_WINDOW); *bgBrush = GetSysColorBrush(COLOR_WINDOW); } } BOOL SetControlColor(HWND hWnd, HDC hDC, HBRUSH *brush) { for (int i = 0; i < _theme->cControls; ++i) { if (_theme->rgControls[i].hWnd != hWnd) { continue; } DWORD fontId = _theme->rgControls[i].dwFontId; if (fontId > _theme->cFonts) { fontId = 0; } THEME_FONT *fnt = &_theme->rgFonts[fontId]; COLORREF fg = fnt->crForeground, bg = fnt->crBackground; *brush = fnt->hBackground; RemapColor(&fg, &bg, brush); ::SetTextColor(hDC, fg); ::SetBkColor(hDC, bg); return TRUE; } return FALSE; } // // OnShowFailure - display the failure page. // void OnShowFailure() { SetState(PYBA_STATE_FAILED, S_OK); // If the UI should be visible, display it now and hide the splash screen if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { ::ShowWindow(_theme->hwndParent, SW_SHOW); } _engine->CloseSplashScreen(); return; } // // OnShowHelp - display the help page. // void OnShowHelp() { SetState(PYBA_STATE_HELP, S_OK); // If the UI should be visible, display it now and hide the splash screen if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { ::ShowWindow(_theme->hwndParent, SW_SHOW); } _engine->CloseSplashScreen(); return; } // // OnDetect - start the processing of packages. // void OnDetect() { HRESULT hr = S_OK; if (_baFunction) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect BA function"); hr = _baFunction->OnDetect(); BalExitOnFailure(hr, "Failed calling detect BA function."); } SetState(PYBA_STATE_DETECTING, hr); // If the UI should be visible, display it now and hide the splash screen if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { ::ShowWindow(_theme->hwndParent, SW_SHOW); } _engine->CloseSplashScreen(); // Tell the core we're ready for the packages to be processed now. hr = _engine->Detect(); BalExitOnFailure(hr, "Failed to start detecting chain."); LExit: if (FAILED(hr)) { SetState(PYBA_STATE_DETECTING, hr); } return; } HRESULT UpdateUIStrings(__in BOOTSTRAPPER_ACTION action) { HRESULT hr = S_OK; LPCWSTR likeInstalling = nullptr; LPCWSTR likeInstallation = nullptr; switch (action) { case BOOTSTRAPPER_ACTION_INSTALL: likeInstalling = L"Installing"; likeInstallation = L"Installation"; break; case BOOTSTRAPPER_ACTION_MODIFY: // For modify, we actually want to pass INSTALL action = BOOTSTRAPPER_ACTION_INSTALL; likeInstalling = L"Modifying"; likeInstallation = L"Modification"; break; case BOOTSTRAPPER_ACTION_REPAIR: likeInstalling = L"Repairing"; likeInstallation = L"Repair"; break; case BOOTSTRAPPER_ACTION_UNINSTALL: likeInstalling = L"Uninstalling"; likeInstallation = L"Uninstallation"; break; } if (likeInstalling) { LPWSTR locName = nullptr; LOC_STRING *locText = nullptr; hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstalling); if (SUCCEEDED(hr)) { hr = LocGetString(_wixLoc, locName, &locText); ReleaseStr(locName); } _engine->SetVariableString( L"ActionLikeInstalling", SUCCEEDED(hr) && locText ? locText->wzText : likeInstalling ); } if (likeInstallation) { LPWSTR locName = nullptr; LOC_STRING *locText = nullptr; hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstallation); if (SUCCEEDED(hr)) { hr = LocGetString(_wixLoc, locName, &locText); ReleaseStr(locName); } _engine->SetVariableString( L"ActionLikeInstallation", SUCCEEDED(hr) && locText ? locText->wzText : likeInstallation ); } return hr; } // // OnPlan - plan the detected changes. // void OnPlan(__in BOOTSTRAPPER_ACTION action) { HRESULT hr = S_OK; _plannedAction = action; hr = UpdateUIStrings(action); BalExitOnFailure(hr, "Failed to update strings"); // If we are going to apply a downgrade, bail. if (_downgradingOtherVersion && BOOTSTRAPPER_ACTION_UNINSTALL < action) { if (_suppressDowngradeFailure) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "A newer version of this product is installed but downgrade failure has been suppressed; continuing..."); } else { hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION); BalExitOnFailure(hr, "Cannot install a product when a newer version is installed."); } } SetState(PYBA_STATE_PLANNING, hr); if (_baFunction) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan BA function"); _baFunction->OnPlan(); } hr = _engine->Plan(action); BalExitOnFailure(hr, "Failed to start planning packages."); LExit: if (FAILED(hr)) { SetState(PYBA_STATE_PLANNING, hr); } return; } // // OnApply - apply the packages. // void OnApply() { HRESULT hr = S_OK; SetState(PYBA_STATE_APPLYING, hr); SetProgressState(hr); SetTaskbarButtonProgress(0); hr = _engine->Apply(_hWnd); BalExitOnFailure(hr, "Failed to start applying packages."); ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, TRUE); // ensure the cancel button is enabled before starting. LExit: if (FAILED(hr)) { SetState(PYBA_STATE_APPLYING, hr); } return; } // // OnChangeState - change state. // void OnChangeState(__in PYBA_STATE state) { LPWSTR unformattedText = nullptr; _state = state; // If our install is at the end (success or failure) and we're not showing full UI // then exit (prompt for restart if required). if ((PYBA_STATE_APPLIED <= _state && BOOTSTRAPPER_DISPLAY_FULL > _command.display)) { // If a restart was required but we were not automatically allowed to // accept the reboot then do the prompt. if (_restartRequired && !_allowRestart) { StrAllocFromError(&unformattedText, HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), nullptr); _allowRestart = IDOK == ::MessageBoxW( _hWnd, unformattedText ? unformattedText : L"The requested operation is successful. Changes will not be effective until the system is rebooted.", _theme->sczCaption, MB_ICONEXCLAMATION | MB_OKCANCEL ); } // Quietly exit. ::PostMessageW(_hWnd, WM_CLOSE, 0, 0); } else { // try to change the pages. DWORD newPageId = 0; DeterminePageId(_state, &newPageId); if (_visiblePageId != newPageId) { ShowPage(newPageId); } } ReleaseStr(unformattedText); } // // Called before showing a page to handle all controls. // void ProcessPageControls(THEME_PAGE *pPage) { if (!pPage) { return; } for (DWORD i = 0; i < pPage->cControlIndices; ++i) { THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i]; BOOL enableControl = TRUE; // If this is a named control, try to set its default state. if (pControl->sczName && *pControl->sczName) { // If this is a checkable control, try to set its default state // to the state of a matching named Burn variable. if (IsCheckable(pControl)) { LONGLONG llValue = 0; HRESULT hr = BalGetNumericVariable(pControl->sczName, &llValue); // If the control value isn't set then disable it. if (!SUCCEEDED(hr)) { enableControl = FALSE; } else { ThemeSendControlMessage( _theme, pControl->wId, BM_SETCHECK, SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED, 0 ); } } // Hide or disable controls based on the control name with 'State' appended LPWSTR controlName = nullptr; HRESULT hr = StrAllocFormatted(&controlName, L"%lsState", pControl->sczName); if (SUCCEEDED(hr)) { LPWSTR controlState = nullptr; hr = BalGetStringVariable(controlName, &controlState); if (SUCCEEDED(hr) && controlState && *controlState) { if (controlState[0] == '[') { LPWSTR formatted = nullptr; if (SUCCEEDED(BalFormatString(controlState, &formatted))) { StrFree(controlState); controlState = formatted; } } if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"disable", -1)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Disable control %ls", pControl->sczName); enableControl = FALSE; } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"hide", -1)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hide control %ls", pControl->sczName); // TODO: This doesn't work ThemeShowControl(_theme, pControl->wId, SW_HIDE); } else { // An explicit state can override the lack of a // backing variable. enableControl = TRUE; } } StrFree(controlState); } StrFree(controlName); controlName = nullptr; // If a command link has a note, then add it. if ((pControl->dwStyle & BS_TYPEMASK) == BS_COMMANDLINK || (pControl->dwStyle & BS_TYPEMASK) == BS_DEFCOMMANDLINK) { hr = StrAllocFormatted(&controlName, L"#(loc.%lsNote)", pControl->sczName); if (SUCCEEDED(hr)) { LOC_STRING *locText = nullptr; hr = LocGetString(_wixLoc, controlName, &locText); if (SUCCEEDED(hr) && locText && locText->wzText && locText->wzText[0]) { LPWSTR text = nullptr; hr = BalFormatString(locText->wzText, &text); if (SUCCEEDED(hr) && text && text[0]) { ThemeSendControlMessage(_theme, pControl->wId, BCM_SETNOTE, 0, (LPARAM)text); ReleaseStr(text); text = nullptr; } } ReleaseStr(controlName); controlName = nullptr; } hr = S_OK; } } ThemeControlEnable(_theme, pControl->wId, enableControl); // Format the text in each of the new page's controls if (pControl->sczText && *pControl->sczText) { // If the wix developer is showing a hidden variable // in the UI, then obviously they don't care about // keeping it safe so don't go down the rabbit hole // of making sure that this is securely freed. LPWSTR text = nullptr; HRESULT hr = BalFormatString(pControl->sczText, &text); if (SUCCEEDED(hr)) { ThemeSetTextControl(_theme, pControl->wId, text); } } } } // // OnClose - called when the window is trying to be closed. // BOOL OnClose() { BOOL close = FALSE; // If we've already succeeded or failed or showing the help page, just close (prompts are annoying if the bootstrapper is done). if (PYBA_STATE_APPLIED <= _state || PYBA_STATE_HELP == _state) { close = TRUE; } else { // prompt the user or force the cancel if there is no UI. close = PromptCancel( _hWnd, BOOTSTRAPPER_DISPLAY_FULL != _command.display, _confirmCloseMessage ? _confirmCloseMessage : L"Are you sure you want to cancel?", _theme->sczCaption ); } // If we're doing progress then we never close, we just cancel to let rollback occur. if (PYBA_STATE_APPLYING <= _state && PYBA_STATE_APPLIED > _state) { // If we canceled disable cancel button since clicking it again is silly. if (close) { ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE); } close = FALSE; } return close; } // // OnClickCloseButton - close the application. // void OnClickCloseButton() { ::SendMessageW(_hWnd, WM_CLOSE, 0, 0); } // // OnClickRestartButton - allows the restart and closes the app. // void OnClickRestartButton() { AssertSz(_restartRequired, "Restart must be requested to be able to click on the restart button."); _allowRestart = TRUE; ::SendMessageW(_hWnd, WM_CLOSE, 0, 0); return; } // // OnClickLogFileLink - show the log file. // void OnClickLogFileLink() { HRESULT hr = S_OK; LPWSTR sczLogFile = nullptr; hr = BalGetStringVariable(_bundle.sczLogVariable, &sczLogFile); BalExitOnFailure1(hr, "Failed to get log file variable '%ls'.", _bundle.sczLogVariable); hr = ShelExec(L"notepad.exe", sczLogFile, L"open", nullptr, SW_SHOWDEFAULT, _hWnd, nullptr); BalExitOnFailure1(hr, "Failed to open log file target: %ls", sczLogFile); LExit: ReleaseStr(sczLogFile); return; } // // SetState // void SetState(__in PYBA_STATE state, __in HRESULT hrStatus) { if (FAILED(hrStatus)) { _hrFinal = hrStatus; } if (FAILED(_hrFinal)) { state = PYBA_STATE_FAILED; } if (_state != state) { ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, state); } } // // GoToPage // void GoToPage(__in PAGE page) { _installPage = page; ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, _state); } void DeterminePageId(__in PYBA_STATE state, __out DWORD* pdwPageId) { LONGLONG simple; if (BOOTSTRAPPER_DISPLAY_PASSIVE == _command.display) { switch (state) { case PYBA_STATE_INITIALIZED: *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action ? _pageIds[PAGE_HELP] : _pageIds[PAGE_LOADING]; break; case PYBA_STATE_HELP: *pdwPageId = _pageIds[PAGE_HELP]; break; case PYBA_STATE_DETECTING: *pdwPageId = _pageIds[PAGE_LOADING] ? _pageIds[PAGE_LOADING] : _pageIds[PAGE_PROGRESS_PASSIVE] ? _pageIds[PAGE_PROGRESS_PASSIVE] : _pageIds[PAGE_PROGRESS]; break; case PYBA_STATE_DETECTED: __fallthrough; case PYBA_STATE_PLANNING: __fallthrough; case PYBA_STATE_PLANNED: __fallthrough; case PYBA_STATE_APPLYING: __fallthrough; case PYBA_STATE_CACHING: __fallthrough; case PYBA_STATE_CACHED: __fallthrough; case PYBA_STATE_EXECUTING: __fallthrough; case PYBA_STATE_EXECUTED: *pdwPageId = _pageIds[PAGE_PROGRESS_PASSIVE] ? _pageIds[PAGE_PROGRESS_PASSIVE] : _pageIds[PAGE_PROGRESS]; break; default: *pdwPageId = 0; break; } } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) { switch (state) { case PYBA_STATE_INITIALIZING: *pdwPageId = 0; break; case PYBA_STATE_INITIALIZED: *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action ? _pageIds[PAGE_HELP] : _pageIds[PAGE_LOADING]; break; case PYBA_STATE_HELP: *pdwPageId = _pageIds[PAGE_HELP]; break; case PYBA_STATE_DETECTING: *pdwPageId = _pageIds[PAGE_LOADING]; break; case PYBA_STATE_DETECTED: if (_installPage == PAGE_LOADING) { switch (_command.action) { case BOOTSTRAPPER_ACTION_INSTALL: if (_upgrading) { _installPage = PAGE_UPGRADE; } else if (SUCCEEDED(BalGetNumericVariable(L"SimpleInstall", &simple)) && simple) { _installPage = PAGE_SIMPLE_INSTALL; } else { _installPage = PAGE_INSTALL; } break; case BOOTSTRAPPER_ACTION_MODIFY: __fallthrough; case BOOTSTRAPPER_ACTION_REPAIR: __fallthrough; case BOOTSTRAPPER_ACTION_UNINSTALL: _installPage = PAGE_MODIFY; break; } } *pdwPageId = _pageIds[_installPage]; break; case PYBA_STATE_PLANNING: __fallthrough; case PYBA_STATE_PLANNED: __fallthrough; case PYBA_STATE_APPLYING: __fallthrough; case PYBA_STATE_CACHING: __fallthrough; case PYBA_STATE_CACHED: __fallthrough; case PYBA_STATE_EXECUTING: __fallthrough; case PYBA_STATE_EXECUTED: *pdwPageId = _pageIds[PAGE_PROGRESS]; break; case PYBA_STATE_APPLIED: *pdwPageId = _pageIds[PAGE_SUCCESS]; break; case PYBA_STATE_FAILED: *pdwPageId = _pageIds[PAGE_FAILURE]; break; } } } BOOL WillElevate() { static BAL_CONDITION WILL_ELEVATE_CONDITION = { L"not WixBundleElevated and (" /*Elevate when installing for all users*/ L"InstallAllUsers or " /*Elevate when installing the launcher for all users and it was not detected*/ L"(Include_launcher and InstallLauncherAllUsers and not BlockedLauncher)" L")", L"" }; BOOL result; return SUCCEEDED(BalConditionEvaluate(&WILL_ELEVATE_CONDITION, _engine, &result, nullptr)) && result; } BOOL IsCrtInstalled() { if (_crtInstalledToken > 0) { return TRUE; } else if (_crtInstalledToken == 0) { return FALSE; } // Check whether at least CRT v10.0.10137.0 is available. // It should only be installed as a Windows Update package, which means // we don't need to worry about 32-bit/64-bit. LPCWSTR crtFile = L"ucrtbase.dll"; DWORD cbVer = GetFileVersionInfoSizeW(crtFile, nullptr); if (!cbVer) { _crtInstalledToken = 0; return FALSE; } void *pData = malloc(cbVer); if (!pData) { _crtInstalledToken = 0; return FALSE; } if (!GetFileVersionInfoW(crtFile, 0, cbVer, pData)) { free(pData); _crtInstalledToken = 0; return FALSE; } VS_FIXEDFILEINFO *ffi; UINT cb; BOOL result = FALSE; if (VerQueryValueW(pData, L"\\", (LPVOID*)&ffi, &cb) && ffi->dwFileVersionMS == 0x000A0000 && ffi->dwFileVersionLS >= 0x27990000) { result = TRUE; } free(pData); _crtInstalledToken = result ? 1 : 0; return result; } HRESULT EvaluateConditions() { HRESULT hr = S_OK; BOOL result = FALSE; for (DWORD i = 0; i < _conditions.cConditions; ++i) { BAL_CONDITION* pCondition = _conditions.rgConditions + i; hr = BalConditionEvaluate(pCondition, _engine, &result, &_failedMessage); BalExitOnFailure(hr, "Failed to evaluate condition."); if (!result) { // Hope they didn't have hidden variables in their message, because it's going in the log in plaintext. BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "%ls", _failedMessage); hr = E_WIXSTDBA_CONDITION_FAILED; // todo: remove in WiX v4, in case people are relying on v3.x logging behavior BalExitOnFailure1(hr, "Bundle condition evaluated to false: %ls", pCondition->sczCondition); } } ReleaseNullStrSecure(_failedMessage); LExit: return hr; } void SetTaskbarButtonProgress(__in DWORD dwOverallPercentage) { HRESULT hr = S_OK; if (_taskbarButtonOK) { hr = _taskbarList->SetProgressValue(_hWnd, dwOverallPercentage, 100UL); BalExitOnFailure1(hr, "Failed to set taskbar button progress to: %d%%.", dwOverallPercentage); } LExit: return; } void SetTaskbarButtonState(__in TBPFLAG tbpFlags) { HRESULT hr = S_OK; if (_taskbarButtonOK) { hr = _taskbarList->SetProgressState(_hWnd, tbpFlags); BalExitOnFailure1(hr, "Failed to set taskbar button state.", tbpFlags); } LExit: return; } void SetProgressState(__in HRESULT hrStatus) { TBPFLAG flag = TBPF_NORMAL; if (IsCanceled() || HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hrStatus) { flag = TBPF_PAUSED; } else if (IsRollingBack() || FAILED(hrStatus)) { flag = TBPF_ERROR; } SetTaskbarButtonState(flag); } HRESULT LoadBootstrapperBAFunctions() { HRESULT hr = S_OK; LPWSTR sczBafPath = nullptr; hr = PathRelativeToModule(&sczBafPath, L"bafunctions.dll", _hModule); BalExitOnFailure(hr, "Failed to get path to BA function DLL."); #ifdef DEBUG BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: LoadBootstrapperBAFunctions() - BA function DLL %ls", sczBafPath); #endif _hBAFModule = ::LoadLibraryW(sczBafPath); if (_hBAFModule) { auto pfnBAFunctionCreate = reinterpret_cast(::GetProcAddress(_hBAFModule, "CreateBootstrapperBAFunction")); BalExitOnNullWithLastError1(pfnBAFunctionCreate, hr, "Failed to get CreateBootstrapperBAFunction entry-point from: %ls", sczBafPath); hr = pfnBAFunctionCreate(_engine, _hBAFModule, &_baFunction); BalExitOnFailure(hr, "Failed to create BA function."); } #ifdef DEBUG else { BalLogError(HRESULT_FROM_WIN32(::GetLastError()), "PYBA: LoadBootstrapperBAFunctions() - Failed to load DLL %ls", sczBafPath); } #endif LExit: if (_hBAFModule && !_baFunction) { ::FreeLibrary(_hBAFModule); _hBAFModule = nullptr; } ReleaseStr(sczBafPath); return hr; } BOOL IsCheckable(THEME_CONTROL* pControl) { if (!pControl->sczName || !pControl->sczName[0]) { return FALSE; } if (pControl->type == THEME_CONTROL_TYPE_CHECKBOX) { return TRUE; } if (pControl->type == THEME_CONTROL_TYPE_BUTTON) { if ((pControl->dwStyle & BS_TYPEMASK) == BS_AUTORADIOBUTTON) { return TRUE; } } return FALSE; } void SavePageSettings() { DWORD pageId = 0; THEME_PAGE* pPage = nullptr; DeterminePageId(_state, &pageId); pPage = ThemeGetPage(_theme, pageId); if (!pPage) { return; } for (DWORD i = 0; i < pPage->cControlIndices; ++i) { // Loop through all the checkable controls and set a Burn variable // with that name to true or false. THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i]; if (IsCheckable(pControl) && ThemeControlEnabled(_theme, pControl->wId)) { BOOL checked = ThemeIsControlChecked(_theme, pControl->wId); _engine->SetVariableNumeric(pControl->sczName, checked ? 1 : 0); } // Loop through all the editbox controls with names and set a // Burn variable with that name to the contents. if (THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName) { LPWSTR sczValue = nullptr; ThemeGetTextControl(_theme, pControl->wId, &sczValue); _engine->SetVariableString(pControl->sczName, sczValue); } } } static bool IsTargetPlatformx64(__in IBootstrapperEngine* pEngine) { WCHAR platform[8]; DWORD platformLen = 8; if (FAILED(pEngine->GetVariableString(L"TargetPlatform", platform, &platformLen))) { return S_FALSE; } return ::CompareStringW(LOCALE_NEUTRAL, 0, platform, -1, L"x64", -1) == CSTR_EQUAL; } static bool IsTargetPlatformARM64(__in IBootstrapperEngine* pEngine) { WCHAR platform[8]; DWORD platformLen = 8; if (FAILED(pEngine->GetVariableString(L"TargetPlatform", platform, &platformLen))) { return S_FALSE; } return ::CompareStringW(LOCALE_NEUTRAL, 0, platform, -1, L"ARM64", -1) == CSTR_EQUAL; } static HRESULT LoadOptionalFeatureStatesFromKey( __in IBootstrapperEngine* pEngine, __in HKEY hkHive, __in LPCWSTR subkey ) { HKEY hKey; LRESULT res; if (IsTargetPlatformx64(pEngine) || IsTargetPlatformARM64(pEngine)) { res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); } else { res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); } if (res == ERROR_FILE_NOT_FOUND) { return S_FALSE; } if (res != ERROR_SUCCESS) { return HRESULT_FROM_WIN32(res); } for (auto p = OPTIONAL_FEATURES; p->regName; ++p) { res = RegQueryValueExW(hKey, p->regName, nullptr, nullptr, nullptr, nullptr); if (res == ERROR_FILE_NOT_FOUND) { pEngine->SetVariableNumeric(p->variableName, 0); } else if (res == ERROR_SUCCESS) { pEngine->SetVariableNumeric(p->variableName, 1); } else { RegCloseKey(hKey); return HRESULT_FROM_WIN32(res); } } RegCloseKey(hKey); return S_OK; } static HRESULT LoadTargetDirFromKey( __in IBootstrapperEngine* pEngine, __in HKEY hkHive, __in LPCWSTR subkey ) { HKEY hKey; LRESULT res; DWORD dataType; BYTE buffer[1024]; DWORD bufferLen = sizeof(buffer); if (IsTargetPlatformx64(pEngine) || IsTargetPlatformARM64(pEngine)) { res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); } else { res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); } if (res == ERROR_FILE_NOT_FOUND) { return S_FALSE; } if (res != ERROR_SUCCESS) { return HRESULT_FROM_WIN32(res); } res = RegQueryValueExW(hKey, nullptr, nullptr, &dataType, buffer, &bufferLen); if (res == ERROR_SUCCESS && dataType == REG_SZ && bufferLen < sizeof(buffer)) { pEngine->SetVariableString(L"TargetDir", reinterpret_cast(buffer)); } RegCloseKey(hKey); return HRESULT_FROM_WIN32(res); } static HRESULT LoadAssociateFilesStateFromKey( __in IBootstrapperEngine* pEngine, __in HKEY hkHive ) { const LPCWSTR subkey = L"Software\\Python\\PyLauncher"; HKEY hKey; LRESULT res; HRESULT hr; res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); if (res == ERROR_FILE_NOT_FOUND) { return S_FALSE; } if (res != ERROR_SUCCESS) { return HRESULT_FROM_WIN32(res); } res = RegQueryValueExW(hKey, L"AssociateFiles", nullptr, nullptr, nullptr, nullptr); if (res == ERROR_FILE_NOT_FOUND) { hr = S_FALSE; } else if (res == ERROR_SUCCESS) { hr = S_OK; } else { hr = HRESULT_FROM_WIN32(res); } RegCloseKey(hKey); return hr; } static void LoadOptionalFeatureStates(__in IBootstrapperEngine* pEngine) { WCHAR subkeyFmt[256]; WCHAR subkey[256]; DWORD subkeyLen; HRESULT hr; HKEY hkHive; BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading state of optional features"); // Get the registry key from the bundle, to save having to duplicate it // in multiple places. subkeyLen = sizeof(subkeyFmt) / sizeof(subkeyFmt[0]); hr = pEngine->GetVariableString(L"OptionalFeaturesRegistryKey", subkeyFmt, &subkeyLen); BalExitOnFailure(hr, "Failed to locate registry key"); subkeyLen = sizeof(subkey) / sizeof(subkey[0]); hr = pEngine->FormatString(subkeyFmt, subkey, &subkeyLen); BalExitOnFailure1(hr, "Failed to format %ls", subkeyFmt); // Check the current user's registry for existing features hkHive = HKEY_CURRENT_USER; hr = LoadOptionalFeatureStatesFromKey(pEngine, hkHive, subkey); BalExitOnFailure1(hr, "Failed to read from HKCU\\%ls", subkey); if (hr == S_FALSE) { // Now check the local machine registry hkHive = HKEY_LOCAL_MACHINE; hr = LoadOptionalFeatureStatesFromKey(pEngine, hkHive, subkey); BalExitOnFailure1(hr, "Failed to read from HKLM\\%ls", subkey); if (hr == S_OK) { // Found a system-wide install, so enable these settings. pEngine->SetVariableNumeric(L"InstallAllUsers", 1); pEngine->SetVariableNumeric(L"CompileAll", 1); } } if (hr == S_OK) { // Cannot change InstallAllUsersState when upgrading. While there's // no good reason to not allow installing a per-user and an all-user // version simultaneously, Burn can't handle the state management // and will need to uninstall the old one. pEngine->SetVariableString(L"InstallAllUsersState", L"disable"); // Get the previous install directory. This can be changed by the // user. subkeyLen = sizeof(subkeyFmt) / sizeof(subkeyFmt[0]); hr = pEngine->GetVariableString(L"TargetDirRegistryKey", subkeyFmt, &subkeyLen); BalExitOnFailure(hr, "Failed to locate registry key"); subkeyLen = sizeof(subkey) / sizeof(subkey[0]); hr = pEngine->FormatString(subkeyFmt, subkey, &subkeyLen); BalExitOnFailure1(hr, "Failed to format %ls", subkeyFmt); LoadTargetDirFromKey(pEngine, hkHive, subkey); } LExit: return; } HRESULT EnsureTargetDir() { LONGLONG installAllUsers; LPWSTR targetDir = nullptr, defaultDir = nullptr; HRESULT hr = BalGetStringVariable(L"TargetDir", &targetDir); if (FAILED(hr) || !targetDir || !targetDir[0]) { ReleaseStr(targetDir); targetDir = nullptr; hr = BalGetNumericVariable(L"InstallAllUsers", &installAllUsers); ExitOnFailure(hr, L"Failed to get install scope"); hr = BalGetStringVariable( installAllUsers ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir", &defaultDir ); BalExitOnFailure(hr, "Failed to get the default install directory"); if (!defaultDir || !defaultDir[0]) { BalLogError(E_INVALIDARG, "Default install directory is blank"); } hr = BalFormatString(defaultDir, &targetDir); BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir); hr = _engine->SetVariableString(L"TargetDir", targetDir); BalExitOnFailure(hr, "Failed to set install target directory"); } LExit: ReleaseStr(defaultDir); ReleaseStr(targetDir); return hr; } void ValidateOperatingSystem() { LOC_STRING *pLocString = nullptr; if (IsWindowsServer()) { if (IsWindowsVersionOrGreater(6, 2, 0)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows Server 2012 or later"); return; } else if (IsWindowsVersionOrGreater(6, 1, 1)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected Windows Server 2008 R2"); } else if (IsWindowsVersionOrGreater(6, 1, 0)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2008 R2"); } else if (IsWindowsVersionOrGreater(6, 0, 0)) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2008"); } else { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2003 or earlier"); } BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Windows Server 2012 or later is required to continue installation"); } else { if (IsWindows10OrGreater()) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows 10 or later"); return; } else if (IsWindows8Point1OrGreater()) { BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows 8.1"); return; } else if (IsWindows8OrGreater()) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows 8"); } else if (IsWindows7OrGreater()) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows 7"); } else if (IsWindowsVistaOrGreater()) { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Vista"); } else { BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows XP or earlier"); } BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Windows 8.1 or later is required to continue installation"); } LocGetString(_wixLoc, L"#(loc.FailureOldOS)", &pLocString); if (pLocString && pLocString->wzText) { BalFormatString(pLocString->wzText, &_failedMessage); } _hrFinal = E_WIXSTDBA_CONDITION_FAILED; } public: // // Constructor - initialize member variables. // PythonBootstrapperApplication( __in HMODULE hModule, __in BOOL fPrereq, __in HRESULT hrHostInitialization, __in IBootstrapperEngine* pEngine, __in const BOOTSTRAPPER_COMMAND* pCommand ) : CBalBaseBootstrapperApplication(pEngine, pCommand, 3, 3000) { _hModule = hModule; memcpy_s(&_command, sizeof(_command), pCommand, sizeof(BOOTSTRAPPER_COMMAND)); LONGLONG llInstalled = 0; HRESULT hr = BalGetNumericVariable(L"WixBundleInstalled", &llInstalled); if (SUCCEEDED(hr) && BOOTSTRAPPER_RESUME_TYPE_REBOOT != _command.resumeType && 0 < llInstalled && BOOTSTRAPPER_ACTION_INSTALL == _command.action) { _command.action = BOOTSTRAPPER_ACTION_MODIFY; } else if (0 == llInstalled && (BOOTSTRAPPER_ACTION_MODIFY == _command.action || BOOTSTRAPPER_ACTION_REPAIR == _command.action)) { _command.action = BOOTSTRAPPER_ACTION_INSTALL; } _plannedAction = BOOTSTRAPPER_ACTION_UNKNOWN; // When resuming from restart doing some install-like operation, try to find the package that forced the // restart. We'll use this information during planning. _nextPackageAfterRestart = nullptr; if (BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType && BOOTSTRAPPER_ACTION_UNINSTALL < _command.action) { // Ensure the forced restart package variable is null when it is an empty string. HRESULT hr = BalGetStringVariable(L"WixBundleForcedRestartPackage", &_nextPackageAfterRestart); if (FAILED(hr) || !_nextPackageAfterRestart || !*_nextPackageAfterRestart) { ReleaseNullStr(_nextPackageAfterRestart); } } _crtInstalledToken = -1; pEngine->SetVariableNumeric(L"CRTInstalled", IsCrtInstalled() ? 1 : 0); _wixLoc = nullptr; memset(&_bundle, 0, sizeof(_bundle)); memset(&_conditions, 0, sizeof(_conditions)); _confirmCloseMessage = nullptr; _failedMessage = nullptr; _language = nullptr; _theme = nullptr; memset(_pageIds, 0, sizeof(_pageIds)); _hUiThread = nullptr; _registered = FALSE; _hWnd = nullptr; _state = PYBA_STATE_INITIALIZING; _visiblePageId = 0; _installPage = PAGE_LOADING; _hrFinal = hrHostInitialization; _downgradingOtherVersion = FALSE; _restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE; _restartRequired = FALSE; _allowRestart = FALSE; _suppressDowngradeFailure = FALSE; _suppressRepair = FALSE; _modifying = FALSE; _upgrading = FALSE; _overridableVariables = nullptr; _taskbarList = nullptr; _taskbarButtonCreatedMessage = UINT_MAX; _taskbarButtonOK = FALSE; _showingInternalUIThisPackage = FALSE; _suppressPaint = FALSE; pEngine->AddRef(); _engine = pEngine; _hBAFModule = nullptr; _baFunction = nullptr; } // // Destructor - release member variables. // ~PythonBootstrapperApplication() { AssertSz(!::IsWindow(_hWnd), "Window should have been destroyed before destructor."); AssertSz(!_theme, "Theme should have been released before destructor."); ReleaseObject(_taskbarList); ReleaseDict(_overridableVariables); ReleaseStr(_failedMessage); ReleaseStr(_confirmCloseMessage); BalConditionsUninitialize(&_conditions); BalInfoUninitialize(&_bundle); LocFree(_wixLoc); ReleaseStr(_language); ReleaseStr(_nextPackageAfterRestart); ReleaseNullObject(_engine); if (_hBAFModule) { ::FreeLibrary(_hBAFModule); _hBAFModule = nullptr; } } private: HMODULE _hModule; BOOTSTRAPPER_COMMAND _command; IBootstrapperEngine* _engine; BOOTSTRAPPER_ACTION _plannedAction; LPWSTR _nextPackageAfterRestart; WIX_LOCALIZATION* _wixLoc; BAL_INFO_BUNDLE _bundle; BAL_CONDITIONS _conditions; LPWSTR _failedMessage; LPWSTR _confirmCloseMessage; LPWSTR _language; THEME* _theme; DWORD _pageIds[countof(PAGE_NAMES)]; HANDLE _hUiThread; BOOL _registered; HWND _hWnd; PYBA_STATE _state; HRESULT _hrFinal; DWORD _visiblePageId; PAGE _installPage; BOOL _startedExecution; DWORD _calculatedCacheProgress; DWORD _calculatedExecuteProgress; BOOL _downgradingOtherVersion; BOOTSTRAPPER_APPLY_RESTART _restartResult; BOOL _restartRequired; BOOL _allowRestart; BOOL _suppressDowngradeFailure; BOOL _suppressRepair; BOOL _modifying; BOOL _upgrading; int _crtInstalledToken; STRINGDICT_HANDLE _overridableVariables; ITaskbarList3* _taskbarList; UINT _taskbarButtonCreatedMessage; BOOL _taskbarButtonOK; BOOL _showingInternalUIThisPackage; BOOL _suppressPaint; HMODULE _hBAFModule; IBootstrapperBAFunction* _baFunction; }; // // CreateBootstrapperApplication - creates a new IBootstrapperApplication object. // HRESULT CreateBootstrapperApplication( __in HMODULE hModule, __in BOOL fPrereq, __in HRESULT hrHostInitialization, __in IBootstrapperEngine* pEngine, __in const BOOTSTRAPPER_COMMAND* pCommand, __out IBootstrapperApplication** ppApplication ) { HRESULT hr = S_OK; if (fPrereq) { hr = E_INVALIDARG; ExitWithLastError(hr, "Failed to create UI thread."); } PythonBootstrapperApplication* pApplication = nullptr; pApplication = new PythonBootstrapperApplication(hModule, fPrereq, hrHostInitialization, pEngine, pCommand); ExitOnNull(pApplication, hr, E_OUTOFMEMORY, "Failed to create new standard bootstrapper application object."); *ppApplication = pApplication; pApplication = nullptr; LExit: ReleaseObject(pApplication); return hr; }