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