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