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