//-------------------------------------------------------------------------------------------------
//
// 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 LPCWSTR PYBA_VARIABLE_LAUNCH_TARGET_PATH = L"LaunchTarget";
static const LPCWSTR PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID = L"LaunchTargetElevatedId";
static const LPCWSTR PYBA_VARIABLE_LAUNCH_ARGUMENTS = L"LaunchArguments";
static const LPCWSTR PYBA_VARIABLE_LAUNCH_HIDDEN = L"LaunchHidden";
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_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_LAUNCH_BUTTON,
ID_SUCCESS_TEXT,
ID_SUCCESS_RESTART_TEXT,
ID_SUCCESS_RESTART_BUTTON,
ID_SUCCESS_CANCEL_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_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_LAUNCH_BUTTON, L"LaunchButton" },
{ ID_SUCCESS_TEXT, L"SuccessText" },
{ ID_SUCCESS_RESTART_TEXT, L"SuccessRestartText" },
{ ID_SUCCESS_RESTART_BUTTON, L"SuccessRestartButton" },
{ ID_SUCCESS_CANCEL_BUTTON, L"SuccessCancelButton" },
{ 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"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();
if (!WillElevate() && !QueryElevateForCrtInstall()) {
break;
}
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 = BalGetStringVariable(L"TargetDir", &targetDir);
if (FAILED(hr) || !targetDir || !targetDir[0]) {
ReleaseStr(targetDir);
targetDir = nullptr;
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");
}
ReleaseStr(targetDir);
OnPlan(BOOTSTRAPPER_ACTION_INSTALL);
break;
case ID_CUSTOM1_BACK_BUTTON:
SavePageSettings();
if (_modifying) {
GoToPage(PAGE_MODIFY);
} 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 = BalGetStringVariable(L"TargetDir", &targetDir);
if (SUCCEEDED(hr)) {
// TODO: Check whether directory exists and contains another installation
ReleaseStr(targetDir);
}
if (!WillElevate() && !QueryElevateForCrtInstall()) {
break;
}
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;
}
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);
}
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);
}
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 or launch button should be enabled.
BOOL showRestartButton = FALSE;
BOOL launchTargetExists = FALSE;
LOC_STRING *successText = nullptr;
HRESULT hr = S_OK;
if (_restartRequired) {
if (BOOTSTRAPPER_RESTART_PROMPT == _command.restart) {
showRestartButton = TRUE;
}
} else if (ThemeControlExists(_theme, ID_LAUNCH_BUTTON)) {
launchTargetExists = BalStringVariableExists(PYBA_VARIABLE_LAUNCH_TARGET_PATH);
}
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_LAUNCH_BUTTON, launchTargetExists && BOOTSTRAPPER_ACTION_UNINSTALL < _plannedAction);
ThemeControlEnable(_theme, ID_SUCCESS_RESTART_TEXT, showRestartButton);
ThemeControlEnable(_theme, ID_SUCCESS_RESTART_BUTTON, showRestartButton);
}
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);
}
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) 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) {
_upgradingOldVersion = TRUE;
// Assume we don't want the launcher or file associations, and if
// they have already been installed then loading the state will
// reactivate these settings.
_engine->SetVariableNumeric(L"Include_launcher", 0);
_engine->SetVariableNumeric(L"AssociateFiles", 0);
auto hr = LoadLauncherStateFromKey(_engine, HKEY_CURRENT_USER);
if (hr == S_FALSE) {
hr = LoadLauncherStateFromKey(_engine, HKEY_LOCAL_MACHINE);
}
} 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)) {
hrStatus = EvaluateConditions();
}
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*/) {
if (HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) == hrStatus) {
//try with ShelExec next time
OnClickLaunchButton();
} else {
::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
}
}
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.");
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.");
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_LAUNCH_BUTTON:
pBA->OnClickLaunchButton();
return 0;
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;
LPWSTR name = nullptr;
LOC_STRING *locText = 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(&name, L"#(loc.%lsNote)", pControl->sczName);
if (SUCCEEDED(hr)) {
hr = LocGetString(_wixLoc, name, &locText);
ReleaseStr(name);
if (SUCCEEDED(hr) && locText && locText->wzText && locText->wzText[0]) {
hr = BalFormatString(locText->wzText, &text);
if (SUCCEEDED(hr) && text && text[0]) {
ThemeSendControlMessage(_theme, pControl->wId, BCM_SETNOTE, 0, (LPARAM)text);
ReleaseStr(text);
text = nullptr;
}
}
}
hr = S_OK;
}
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);
}
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);
}
//
// OnClickLaunchButton - launch the app from the success page.
//
void OnClickLaunchButton() {
HRESULT hr = S_OK;
LPWSTR sczUnformattedLaunchTarget = nullptr;
LPWSTR sczLaunchTarget = nullptr;
LPWSTR sczLaunchTargetElevatedId = nullptr;
LPWSTR sczUnformattedArguments = nullptr;
LPWSTR sczArguments = nullptr;
int nCmdShow = SW_SHOWNORMAL;
hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_TARGET_PATH, &sczUnformattedLaunchTarget);
BalExitOnFailure1(hr, "Failed to get launch target variable '%ls'.", PYBA_VARIABLE_LAUNCH_TARGET_PATH);
hr = BalFormatString(sczUnformattedLaunchTarget, &sczLaunchTarget);
BalExitOnFailure1(hr, "Failed to format launch target variable: %ls", sczUnformattedLaunchTarget);
if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID)) {
hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID, &sczLaunchTargetElevatedId);
BalExitOnFailure1(hr, "Failed to get launch target elevated id '%ls'.", PYBA_VARIABLE_LAUNCH_TARGET_ELEVATED_ID);
}
if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_ARGUMENTS)) {
hr = BalGetStringVariable(PYBA_VARIABLE_LAUNCH_ARGUMENTS, &sczUnformattedArguments);
BalExitOnFailure1(hr, "Failed to get launch arguments '%ls'.", PYBA_VARIABLE_LAUNCH_ARGUMENTS);
}
if (BalStringVariableExists(PYBA_VARIABLE_LAUNCH_HIDDEN)) {
nCmdShow = SW_HIDE;
}
if (sczLaunchTargetElevatedId && !_triedToLaunchElevated) {
_triedToLaunchElevated = TRUE;
hr = _engine->LaunchApprovedExe(_hWnd, sczLaunchTargetElevatedId, sczUnformattedArguments, 0);
if (FAILED(hr)) {
BalLogError(hr, "Failed to launch elevated target: %ls", sczLaunchTargetElevatedId);
//try with ShelExec next time
OnClickLaunchButton();
}
} else {
if (sczUnformattedArguments) {
hr = BalFormatString(sczUnformattedArguments, &sczArguments);
BalExitOnFailure1(hr, "Failed to format launch arguments variable: %ls", sczUnformattedArguments);
}
hr = ShelExec(sczLaunchTarget, sczArguments, L"open", nullptr, nCmdShow, _hWnd, nullptr);
BalExitOnFailure1(hr, "Failed to launch target: %ls", sczLaunchTarget);
::PostMessageW(_hWnd, WM_CLOSE, 0, 0);
}
LExit:
StrSecureZeroFreeString(sczArguments);
ReleaseStr(sczUnformattedArguments);
ReleaseStr(sczLaunchTargetElevatedId);
StrSecureZeroFreeString(sczLaunchTarget);
ReleaseStr(sczUnformattedLaunchTarget);
return;
}
//
// 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 (_upgradingOldVersion) {
_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 (InstallAllUsers or (InstallLauncherAllUsers and Include_launcher))",
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;
}
BOOL QueryElevateForCrtInstall() {
// Called to prompt the user that even though they think they won't need
// to elevate, they actually will because of the CRT install.
if (IsCrtInstalled()) {
// CRT is already installed - no need to prompt
return TRUE;
}
LONGLONG elevated;
HRESULT hr = BalGetNumericVariable(L"WixBundleElevated", &elevated);
if (SUCCEEDED(hr) && elevated) {
// Already elevated - no need to prompt
return TRUE;
}
LOC_STRING *locStr;
hr = LocGetString(_wixLoc, L"#(loc.ElevateForCRTInstall)", &locStr);
if (FAILED(hr)) {
BalLogError(hr, "Failed to get ElevateForCRTInstall string");
return FALSE;
}
return ::MessageBoxW(_hWnd, locStr->wzText, _theme->sczCaption, MB_YESNO) != IDNO;
}
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 HRESULT LoadOptionalFeatureStatesFromKey(
__in IBootstrapperEngine* pEngine,
__in HKEY hkHive,
__in LPCWSTR subkey
) {
HKEY hKey;
LRESULT res;
if (IsTargetPlatformx64(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)) {
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 LoadLauncherStateFromKey(
__in IBootstrapperEngine* pEngine,
__in HKEY hkHive
) {
const LPCWSTR subkey = L"Software\\Python\\PyLauncher";
HKEY hKey;
LRESULT res;
if (IsTargetPlatformx64(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, nullptr, nullptr, nullptr);
if (res == ERROR_FILE_NOT_FOUND) {
pEngine->SetVariableNumeric(L"Include_launcher", 0);
} else if (res == ERROR_SUCCESS) {
pEngine->SetVariableNumeric(L"Include_launcher", 1);
}
res = RegQueryValueExW(hKey, L"AssociateFiles", nullptr, nullptr, nullptr, nullptr);
if (res == ERROR_FILE_NOT_FOUND) {
pEngine->SetVariableNumeric(L"AssociateFiles", 0);
} else if (res == ERROR_SUCCESS) {
pEngine->SetVariableNumeric(L"AssociateFiles", 1);
}
RegCloseKey(hKey);
return S_OK;
}
static void LoadOptionalFeatureStates(__in IBootstrapperEngine* pEngine) {
WCHAR subkeyFmt[256];
WCHAR subkey[256];
DWORD subkeyLen;
HRESULT hr;
HKEY hkHive;
// The launcher installation is separate from the Python install, so we
// check its state later. This also checks the file association option.
// 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;
}
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;
_upgradingOldVersion = FALSE;
_restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE;
_restartRequired = FALSE;
_allowRestart = FALSE;
_suppressDowngradeFailure = FALSE;
_suppressRepair = FALSE;
_modifying = FALSE;
_overridableVariables = nullptr;
_taskbarList = nullptr;
_taskbarButtonCreatedMessage = UINT_MAX;
_taskbarButtonOK = FALSE;
_showingInternalUIThisPackage = FALSE;
_triedToLaunchElevated = FALSE;
_suppressPaint = FALSE;
pEngine->AddRef();
_engine = pEngine;
_hBAFModule = nullptr;
_baFunction = nullptr;
LoadOptionalFeatureStates(pEngine);
}
//
// 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;
BOOL _upgradingOldVersion;
BOOTSTRAPPER_APPLY_RESTART _restartResult;
BOOL _restartRequired;
BOOL _allowRestart;
BOOL _suppressDowngradeFailure;
BOOL _suppressRepair;
BOOL _modifying;
int _crtInstalledToken;
STRINGDICT_HANDLE _overridableVariables;
ITaskbarList3* _taskbarList;
UINT _taskbarButtonCreatedMessage;
BOOL _taskbarButtonOK;
BOOL _showingInternalUIThisPackage;
BOOL _triedToLaunchElevated;
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;
}