| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. |
| 2 | |
| 3 | #include "precomp.h" |
| 4 | |
| 5 | static const LPCWSTR WIXIUIBA_WINDOW_CLASS = L"WixInternalUIBA"; |
| 6 | |
| 7 | enum WM_WIXIUIBA |
| 8 | { |
| 9 | WM_WIXIUIBA_DETECT_PACKAGES = WM_APP + 100, |
| 10 | WM_WIXIUIBA_PLAN_PACKAGES, |
| 11 | WM_WIXIUIBA_APPLY_PACKAGES, |
| 12 | WM_WIXIUIBA_DETECT_FOR_CLEANUP, |
| 13 | WM_WIXIUIBA_PLAN_PACKAGES_FOR_CLEANUP, |
| 14 | }; |
| 15 | |
| 16 | |
| 17 | class CWixInternalUIBootstrapperApplication : public CBootstrapperApplicationBase |
| 18 | { |
| 19 | public: // IBootstrapperApplication |
| 20 | virtual STDMETHODIMP OnCreate( |
| 21 | __in IBootstrapperEngine* pEngine, |
| 22 | __in BOOTSTRAPPER_COMMAND* pCommand |
| 23 | ) |
| 24 | { |
| 25 | HRESULT hr = S_OK; |
| 26 | |
| 27 | hr = __super::OnCreate(pEngine, pCommand); |
| 28 | BalExitOnFailure(hr, "CBootstrapperApplicationBase initialization failed."); |
| 29 | |
| 30 | m_commandAction = pCommand->action; |
| 31 | m_commandDisplay = pCommand->display; |
| 32 | |
| 33 | hr = InitializeData(); |
| 34 | BalExitOnFailure(hr, "Failed to initialize data in internal UI bootstrapper application."); |
| 35 | |
| 36 | LExit: |
| 37 | return hr; |
| 38 | } |
| 39 | |
| 40 | virtual STDMETHODIMP OnStartup() |
| 41 | { |
| 42 | HRESULT hr = S_OK; |
| 43 | DWORD dwUIThreadId = 0; |
| 44 | |
| 45 | // create UI thread |
| 46 | m_hUiThread = ::CreateThread(NULL, 0, UiThreadProc, this, 0, &dwUIThreadId); |
| 47 | if (!m_hUiThread) |
| 48 | { |
| 49 | BalExitWithLastError(hr, "Failed to create UI thread."); |
| 50 | } |
| 51 | |
| 52 | LExit: |
| 53 | return hr; |
| 54 | } |
| 55 | |
| 56 | |
| 57 | virtual STDMETHODIMP OnShutdown( |
| 58 | __inout BOOTSTRAPPER_SHUTDOWN_ACTION* pAction |
| 59 | ) |
| 60 | { |
| 61 | // wait for UI thread to terminate |
| 62 | if (m_hUiThread) |
| 63 | { |
| 64 | ::WaitForSingleObject(m_hUiThread, INFINITE); |
| 65 | ReleaseHandle(m_hUiThread); |
| 66 | } |
| 67 | |
| 68 | if (m_fFailedToLoadPackage) |
| 69 | { |
| 70 | Assert(FAILED(m_hrFinal)); |
| 71 | // TODO: Should we really do what this error message says? Going back to the prereq BA |
| 72 | // to show the error dialog is pretty overkill vs. showing an error dialog in this BA. |
| 73 | BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load primary package as the BA. The bootstrapper application will be reloaded to show the error."); |
| 74 | *pAction = BOOTSTRAPPER_SHUTDOWN_ACTION_RELOAD_BOOTSTRAPPER; |
| 75 | } |
| 76 | |
| 77 | return S_OK; |
| 78 | } |
| 79 | |
| 80 | |
| 81 | virtual STDMETHODIMP OnDetectPackageComplete( |
| 82 | __in_z LPCWSTR wzPackageId, |
| 83 | __in HRESULT hrStatus, |
| 84 | __in BOOTSTRAPPER_PACKAGE_STATE state, |
| 85 | __in BOOL fCached |
| 86 | ) |
| 87 | { |
| 88 | BAL_INFO_PACKAGE* pPackage = NULL; |
| 89 | |
| 90 | if (SUCCEEDED(hrStatus) && SUCCEEDED(BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage)) && |
| 91 | BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT == pPackage->primaryPackageType) |
| 92 | { |
| 93 | BOOL fInstalled = BOOTSTRAPPER_PACKAGE_STATE_ABSENT < state; |
| 94 | |
| 95 | // Maybe modify the action state if the primary package is or is not already installed. |
| 96 | if (fInstalled && BOOTSTRAPPER_ACTION_INSTALL == m_commandAction) |
| 97 | { |
| 98 | m_commandAction = BOOTSTRAPPER_ACTION_MODIFY; |
| 99 | } |
| 100 | else if (!fInstalled && (BOOTSTRAPPER_ACTION_MODIFY == m_commandAction || BOOTSTRAPPER_ACTION_REPAIR == m_commandAction)) |
| 101 | { |
| 102 | m_commandAction = BOOTSTRAPPER_ACTION_INSTALL; |
| 103 | } |
| 104 | |
| 105 | if (m_fApplied && !fInstalled && fCached) |
| 106 | { |
| 107 | m_fAutomaticRemoval = TRUE; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | return __super::OnDetectPackageComplete(wzPackageId, hrStatus, state, fCached); |
| 112 | } |
| 113 | |
| 114 | |
| 115 | virtual STDMETHODIMP OnDetectComplete( |
| 116 | __in HRESULT hrStatus, |
| 117 | __in BOOL fEligibleForCleanup |
| 118 | ) |
| 119 | { |
| 120 | if (m_fAutomaticRemoval && SUCCEEDED(hrStatus)) |
| 121 | { |
| 122 | ::PostMessageW(m_hWnd, WM_WIXIUIBA_PLAN_PACKAGES_FOR_CLEANUP, 0, BOOTSTRAPPER_ACTION_UNINSTALL); |
| 123 | ExitFunction(); |
| 124 | } |
| 125 | else if (m_fApplied) |
| 126 | { |
| 127 | ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); |
| 128 | ExitFunction(); |
| 129 | } |
| 130 | |
| 131 | // If we're performing an action that modifies machine state then evaluate conditions. |
| 132 | BOOL fEvaluateConditions = SUCCEEDED(hrStatus) && |
| 133 | (BOOTSTRAPPER_ACTION_LAYOUT < m_commandAction && BOOTSTRAPPER_ACTION_UPDATE_REPLACE > m_commandAction); |
| 134 | |
| 135 | if (fEvaluateConditions) |
| 136 | { |
| 137 | hrStatus = EvaluateConditions(); |
| 138 | } |
| 139 | |
| 140 | if (SUCCEEDED(hrStatus)) |
| 141 | { |
| 142 | ::PostMessageW(m_hWnd, WM_WIXIUIBA_PLAN_PACKAGES, 0, m_commandAction); |
| 143 | } |
| 144 | else |
| 145 | { |
| 146 | SetLoadPackageFailure(hrStatus); |
| 147 | } |
| 148 | |
| 149 | LExit: |
| 150 | return __super::OnDetectComplete(hrStatus, fEligibleForCleanup); |
| 151 | } |
| 152 | |
| 153 | |
| 154 | virtual STDMETHODIMP OnPlanPackageBegin( |
| 155 | __in_z LPCWSTR wzPackageId, |
| 156 | __in BOOTSTRAPPER_PACKAGE_STATE state, |
| 157 | __in BOOL fCached, |
| 158 | __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT installCondition, |
| 159 | __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT repairCondition, |
| 160 | __in BOOTSTRAPPER_REQUEST_STATE recommendedState, |
| 161 | __in BOOTSTRAPPER_CACHE_TYPE recommendedCacheType, |
| 162 | __inout BOOTSTRAPPER_REQUEST_STATE* pRequestState, |
| 163 | __inout BOOTSTRAPPER_CACHE_TYPE* pRequestedCacheType, |
| 164 | __inout BOOL* pfCancel |
| 165 | ) |
| 166 | { |
| 167 | HRESULT hr = S_OK; |
| 168 | BAL_INFO_PACKAGE* pPackage = NULL; |
| 169 | |
| 170 | hr = BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage); |
| 171 | if (FAILED(hr)) |
| 172 | { |
| 173 | // Non-chain package, keep default. |
| 174 | } |
| 175 | else if (BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT != pPackage->primaryPackageType) |
| 176 | { |
| 177 | // Only the primary package should be cached or executed. |
| 178 | if (BOOTSTRAPPER_CACHE_TYPE_FORCE == *pRequestedCacheType) |
| 179 | { |
| 180 | *pRequestedCacheType = BOOTSTRAPPER_CACHE_TYPE_KEEP; |
| 181 | } |
| 182 | |
| 183 | *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; |
| 184 | } |
| 185 | else if (BOOTSTRAPPER_DISPLAY_FULL == m_commandDisplay && !m_fAutomaticRemoval) |
| 186 | { |
| 187 | // Make sure the MSI UI is shown regardless of the current state of the package. |
| 188 | *pRequestState = BOOTSTRAPPER_REQUEST_STATE_FORCE_PRESENT; |
| 189 | } |
| 190 | |
| 191 | return __super::OnPlanPackageBegin(wzPackageId, state, fCached, installCondition, repairCondition, recommendedState, recommendedCacheType, pRequestState, pRequestedCacheType, pfCancel); |
| 192 | } |
| 193 | |
| 194 | |
| 195 | virtual STDMETHODIMP OnPlanMsiPackage( |
| 196 | __in_z LPCWSTR wzPackageId, |
| 197 | __in BOOL fExecute, |
| 198 | __in BOOTSTRAPPER_ACTION_STATE action, |
| 199 | __in BOOTSTRAPPER_MSI_FILE_VERSIONING recommendedFileVersioning, |
| 200 | __inout BOOL* pfCancel, |
| 201 | __inout BURN_MSI_PROPERTY* pActionMsiProperty, |
| 202 | __inout INSTALLUILEVEL* pUiLevel, |
| 203 | __inout BOOL* pfDisableExternalUiHandler, |
| 204 | __inout BOOTSTRAPPER_MSI_FILE_VERSIONING* pFileVersioning |
| 205 | ) |
| 206 | { |
| 207 | INSTALLUILEVEL uiLevel = INSTALLUILEVEL_NOCHANGE; |
| 208 | |
| 209 | if (m_fAutomaticRemoval) |
| 210 | { |
| 211 | ExitFunction(); |
| 212 | } |
| 213 | |
| 214 | switch (m_commandDisplay) |
| 215 | { |
| 216 | case BOOTSTRAPPER_DISPLAY_FULL: |
| 217 | uiLevel = INSTALLUILEVEL_FULL; |
| 218 | break; |
| 219 | |
| 220 | case BOOTSTRAPPER_DISPLAY_PASSIVE: |
| 221 | uiLevel = INSTALLUILEVEL_REDUCED; |
| 222 | break; |
| 223 | } |
| 224 | |
| 225 | if (INSTALLUILEVEL_NOCHANGE != uiLevel) |
| 226 | { |
| 227 | *pUiLevel = uiLevel; |
| 228 | } |
| 229 | |
| 230 | *pActionMsiProperty = BURN_MSI_PROPERTY_NONE; |
| 231 | *pfDisableExternalUiHandler = TRUE; |
| 232 | |
| 233 | LExit: |
| 234 | return __super::OnPlanMsiPackage(wzPackageId, fExecute, action, recommendedFileVersioning, pfCancel, pActionMsiProperty, pUiLevel, pfDisableExternalUiHandler, pFileVersioning); |
| 235 | } |
| 236 | |
| 237 | |
| 238 | virtual STDMETHODIMP OnPlanComplete( |
| 239 | __in HRESULT hrStatus |
| 240 | ) |
| 241 | { |
| 242 | if (SUCCEEDED(hrStatus)) |
| 243 | { |
| 244 | ::PostMessageW(m_hWnd, WM_WIXIUIBA_APPLY_PACKAGES, 0, 0); |
| 245 | } |
| 246 | else if (m_fAutomaticRemoval) |
| 247 | { |
| 248 | ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); |
| 249 | } |
| 250 | else |
| 251 | { |
| 252 | SetLoadPackageFailure(hrStatus); |
| 253 | } |
| 254 | |
| 255 | return __super::OnPlanComplete(hrStatus); |
| 256 | } |
| 257 | |
| 258 | |
| 259 | virtual STDMETHODIMP OnApplyBegin( |
| 260 | __in DWORD dwPhaseCount, |
| 261 | __inout BOOL* pfCancel |
| 262 | ) |
| 263 | { |
| 264 | m_fApplying = TRUE; |
| 265 | return __super::OnApplyBegin(dwPhaseCount, pfCancel); |
| 266 | } |
| 267 | |
| 268 | |
| 269 | virtual STDMETHODIMP OnCacheComplete( |
| 270 | __in HRESULT hrStatus |
| 271 | ) |
| 272 | { |
| 273 | if (FAILED(hrStatus) && !m_fAutomaticRemoval) |
| 274 | { |
| 275 | SetLoadPackageFailure(hrStatus); |
| 276 | } |
| 277 | |
| 278 | return __super::OnCacheComplete(hrStatus); |
| 279 | } |
| 280 | |
| 281 | |
| 282 | virtual STDMETHODIMP OnExecuteBegin( |
| 283 | __in DWORD cExecutingPackages, |
| 284 | __in BOOL* pfCancel |
| 285 | ) |
| 286 | { |
| 287 | m_pEngine->CloseSplashScreen(); |
| 288 | |
| 289 | return __super::OnExecuteBegin(cExecutingPackages, pfCancel); |
| 290 | } |
| 291 | |
| 292 | |
| 293 | virtual STDMETHODIMP OnApplyComplete( |
| 294 | __in HRESULT hrStatus, |
| 295 | __in BOOTSTRAPPER_APPLY_RESTART restart, |
| 296 | __in BOOTSTRAPPER_APPLYCOMPLETE_ACTION recommendation, |
| 297 | __inout BOOTSTRAPPER_APPLYCOMPLETE_ACTION* pAction |
| 298 | ) |
| 299 | { |
| 300 | HRESULT hr = __super::OnApplyComplete(hrStatus, restart, recommendation, pAction); |
| 301 | |
| 302 | *pAction = BOOTSTRAPPER_APPLYCOMPLETE_ACTION_NONE; |
| 303 | m_fApplying = FALSE; |
| 304 | |
| 305 | if (m_fAutomaticRemoval) |
| 306 | { |
| 307 | ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); |
| 308 | } |
| 309 | else |
| 310 | { |
| 311 | m_restartResult = restart; // remember the restart result so we return the correct error code. |
| 312 | m_fApplied = TRUE; |
| 313 | |
| 314 | if (FAILED(hrStatus)) |
| 315 | { |
| 316 | m_hrFinal = hrStatus; |
| 317 | } |
| 318 | |
| 319 | ::PostMessageW(m_hWnd, WM_WIXIUIBA_DETECT_FOR_CLEANUP, 0, 0); |
| 320 | } |
| 321 | |
| 322 | return hr; |
| 323 | } |
| 324 | |
| 325 | |
| 326 | private: |
| 327 | // |
| 328 | // UiThreadProc - entrypoint for UI thread. |
| 329 | // |
| 330 | static DWORD WINAPI UiThreadProc( |
| 331 | __in LPVOID pvContext |
| 332 | ) |
| 333 | { |
| 334 | HRESULT hr = S_OK; |
| 335 | CWixInternalUIBootstrapperApplication* pThis = (CWixInternalUIBootstrapperApplication*)pvContext; |
| 336 | BOOL fComInitialized = FALSE; |
| 337 | BOOL fRet = FALSE; |
| 338 | MSG msg = { }; |
| 339 | DWORD dwQuit = 0; |
| 340 | |
| 341 | // Initialize COM. |
| 342 | hr = ::CoInitialize(NULL); |
| 343 | BalExitOnFailure(hr, "Failed to initialize COM."); |
| 344 | fComInitialized = TRUE; |
| 345 | |
| 346 | // Create main window. |
| 347 | hr = pThis->CreateMainWindow(); |
| 348 | BalExitOnFailure(hr, "Failed to create internal UI bootstrapper application main window."); |
| 349 | |
| 350 | ::PostMessageW(pThis->m_hWnd, WM_WIXIUIBA_DETECT_PACKAGES, 0, 0); |
| 351 | |
| 352 | // message pump |
| 353 | while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) |
| 354 | { |
| 355 | if (-1 == fRet) |
| 356 | { |
| 357 | hr = E_UNEXPECTED; |
| 358 | BalExitOnFailure(hr, "Unexpected return value from message pump."); |
| 359 | } |
| 360 | else if (!::IsDialogMessageW(pThis->m_hWnd, &msg)) |
| 361 | { |
| 362 | ::TranslateMessage(&msg); |
| 363 | ::DispatchMessageW(&msg); |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | // Succeeded thus far, check to see if anything went wrong while actually |
| 368 | // executing changes. |
| 369 | if (FAILED(pThis->m_hrFinal)) |
| 370 | { |
| 371 | hr = pThis->m_hrFinal; |
| 372 | } |
| 373 | else if (pThis->CheckCanceled()) |
| 374 | { |
| 375 | hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); |
| 376 | } |
| 377 | |
| 378 | LExit: |
| 379 | // destroy main window |
| 380 | pThis->DestroyMainWindow(); |
| 381 | |
| 382 | if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == pThis->m_restartResult) |
| 383 | { |
| 384 | dwQuit = SUCCEEDED(hr) ? ERROR_SUCCESS_REBOOT_INITIATED : ERROR_FAIL_REBOOT_INITIATED; |
| 385 | } |
| 386 | else if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == pThis->m_restartResult) |
| 387 | { |
| 388 | dwQuit = SUCCEEDED(hr) ? ERROR_SUCCESS_REBOOT_REQUIRED : ERROR_FAIL_REBOOT_REQUIRED; |
| 389 | } |
| 390 | else if (SEVERITY_ERROR == HRESULT_SEVERITY(hr) && FACILITY_WIN32 == HRESULT_FACILITY(hr)) |
| 391 | { |
| 392 | // Convert Win32 HRESULTs back to the error code. |
| 393 | dwQuit = HRESULT_CODE(hr); |
| 394 | } |
| 395 | else |
| 396 | { |
| 397 | dwQuit = hr; |
| 398 | } |
| 399 | |
| 400 | // initiate engine shutdown |
| 401 | pThis->m_pEngine->Quit(dwQuit); |
| 402 | |
| 403 | // uninitialize COM |
| 404 | if (fComInitialized) |
| 405 | { |
| 406 | ::CoUninitialize(); |
| 407 | } |
| 408 | |
| 409 | return hr; |
| 410 | } |
| 411 | |
| 412 | |
| 413 | // |
| 414 | // InitializeData - initializes all the package and prerequisite information. |
| 415 | // |
| 416 | HRESULT InitializeData() |
| 417 | { |
| 418 | HRESULT hr = S_OK; |
| 419 | IXMLDOMDocument* pixdManifest = NULL; |
| 420 | |
| 421 | hr = XmlInitialize(); |
| 422 | BalExitOnFailure(hr, "Failed to initialize XML."); |
| 423 | |
| 424 | hr = BalManifestLoad(m_hModule, &pixdManifest); |
| 425 | BalExitOnFailure(hr, "Failed to load bootstrapper application manifest."); |
| 426 | |
| 427 | hr = BalInfoParseFromXml(&m_Bundle, pixdManifest); |
| 428 | BalExitOnFailure(hr, "Failed to load bundle information."); |
| 429 | |
| 430 | hr = EnsureSinglePrimaryPackage(); |
| 431 | BalExitOnFailure(hr, "Failed to ensure single primary package."); |
| 432 | |
| 433 | hr = ProcessCommandLine(); |
| 434 | ExitOnFailure(hr, "Unknown commandline parameters."); |
| 435 | |
| 436 | hr = BalConditionsParseFromXml(&m_Conditions, pixdManifest, NULL); |
| 437 | BalExitOnFailure(hr, "Failed to load conditions from XML."); |
| 438 | |
| 439 | LExit: |
| 440 | ReleaseObject(pixdManifest); |
| 441 | |
| 442 | return hr; |
| 443 | } |
| 444 | |
| 445 | |
| 446 | // |
| 447 | // ProcessCommandLine - process the provided command line arguments. |
| 448 | // |
| 449 | HRESULT ProcessCommandLine() |
| 450 | { |
| 451 | HRESULT hr = S_OK; |
| 452 | int argc = 0; |
| 453 | LPWSTR* argv = NULL; |
| 454 | |
| 455 | argc = m_BalInfoCommand.cUnknownArgs; |
| 456 | argv = m_BalInfoCommand.rgUnknownArgs; |
| 457 | |
| 458 | for (int i = 0; i < argc; ++i) |
| 459 | { |
| 460 | BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]); |
| 461 | } |
| 462 | |
| 463 | hr = BalSetOverridableVariablesFromEngine(&m_Bundle.overridableVariables, &m_BalInfoCommand, m_pEngine); |
| 464 | BalExitOnFailure(hr, "Failed to set overridable variables from the command line."); |
| 465 | |
| 466 | LExit: |
| 467 | return hr; |
| 468 | } |
| 469 | |
| 470 | HRESULT EnsureSinglePrimaryPackage() |
| 471 | { |
| 472 | HRESULT hr = S_OK; |
| 473 | BAL_INFO_PACKAGE* pDefaultPackage = NULL; |
| 474 | BOOL fPrimaryArchSpecific = FALSE; |
| 475 | USHORT usNativeMachine = 0; |
| 476 | BAL_INFO_PRIMARY_PACKAGE_TYPE nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_NONE; |
| 477 | |
| 478 | hr = ProcNativeMachine(::GetCurrentProcess(), &usNativeMachine); |
| 479 | BalExitOnFailure(hr, "Failed to get native machine value."); |
| 480 | |
| 481 | if (S_FALSE != hr) |
| 482 | { |
| 483 | switch (usNativeMachine) |
| 484 | { |
| 485 | case IMAGE_FILE_MACHINE_I386: |
| 486 | nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X86; |
| 487 | break; |
| 488 | case IMAGE_FILE_MACHINE_AMD64: |
| 489 | nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X64; |
| 490 | break; |
| 491 | case IMAGE_FILE_MACHINE_ARM64: |
| 492 | nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_ARM64; |
| 493 | break; |
| 494 | } |
| 495 | } |
| 496 | else |
| 497 | { |
| 498 | #if !defined(_WIN64) |
| 499 | BOOL fIsWow64 = FALSE; |
| 500 | |
| 501 | ProcWow64(::GetCurrentProcess(), &fIsWow64); |
| 502 | if (!fIsWow64) |
| 503 | { |
| 504 | nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X86; |
| 505 | } |
| 506 | else |
| 507 | #endif |
| 508 | { |
| 509 | nativeType = BAL_INFO_PRIMARY_PACKAGE_TYPE_X64; |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | for (DWORD i = 0; i < m_Bundle.packages.cPackages; ++i) |
| 514 | { |
| 515 | BAL_INFO_PACKAGE* pPackage = m_Bundle.packages.rgPackages + i; |
| 516 | |
| 517 | if (BAL_INFO_PRIMARY_PACKAGE_TYPE_NONE == pPackage->primaryPackageType) |
| 518 | { |
| 519 | // Skip. |
| 520 | } |
| 521 | else if (nativeType == pPackage->primaryPackageType) |
| 522 | { |
| 523 | if (fPrimaryArchSpecific) |
| 524 | { |
| 525 | BalExitWithRootFailure(hr, E_INVALIDDATA, "Bundle contains multiple primary packages for same architecture: %u.", nativeType); |
| 526 | } |
| 527 | |
| 528 | pPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT; |
| 529 | fPrimaryArchSpecific = TRUE; |
| 530 | } |
| 531 | else if (BAL_INFO_PRIMARY_PACKAGE_TYPE_DEFAULT == pPackage->primaryPackageType) |
| 532 | { |
| 533 | if (pDefaultPackage) |
| 534 | { |
| 535 | BalExitWithRootFailure(hr, E_INVALIDDATA, "Bundle contains multiple default primary packages."); |
| 536 | } |
| 537 | |
| 538 | pDefaultPackage = pPackage; |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | BalExitOnNull(pDefaultPackage, hr, E_INVALIDSTATE, "Bundle did not contain default primary package."); |
| 543 | |
| 544 | if (fPrimaryArchSpecific) |
| 545 | { |
| 546 | pDefaultPackage->primaryPackageType = BAL_INFO_PRIMARY_PACKAGE_TYPE_NONE; |
| 547 | } |
| 548 | |
| 549 | LExit: |
| 550 | return hr; |
| 551 | } |
| 552 | |
| 553 | |
| 554 | // |
| 555 | // CreateMainWindow - creates the main install window. |
| 556 | // |
| 557 | HRESULT CreateMainWindow() |
| 558 | { |
| 559 | HRESULT hr = S_OK; |
| 560 | WNDCLASSW wc = { }; |
| 561 | DWORD dwWindowStyle = WS_POPUP; |
| 562 | |
| 563 | wc.lpfnWndProc = CWixInternalUIBootstrapperApplication::WndProc; |
| 564 | wc.hInstance = m_hModule; |
| 565 | wc.lpszClassName = WIXIUIBA_WINDOW_CLASS; |
| 566 | |
| 567 | if (!::RegisterClassW(&wc)) |
| 568 | { |
| 569 | ExitWithLastError(hr, "Failed to register window."); |
| 570 | } |
| 571 | |
| 572 | m_fRegistered = TRUE; |
| 573 | |
| 574 | // If the UI should be visible, allow it to be visible and activated so we are the foreground window. |
| 575 | // This allows the UAC prompt and MSI UI to automatically be activated. |
| 576 | if (BOOTSTRAPPER_DISPLAY_NONE < m_commandDisplay) |
| 577 | { |
| 578 | dwWindowStyle |= WS_VISIBLE; |
| 579 | } |
| 580 | |
| 581 | m_hWnd = ::CreateWindowExW(WS_EX_TOOLWINDOW, wc.lpszClassName, NULL, dwWindowStyle, 0, 0, 0, 0, HWND_DESKTOP, NULL, m_hModule, this); |
| 582 | ExitOnNullWithLastError(m_hWnd, hr, "Failed to create internal UI main window."); |
| 583 | |
| 584 | LExit: |
| 585 | return hr; |
| 586 | } |
| 587 | |
| 588 | // |
| 589 | // DestroyMainWindow - clean up all the window registration. |
| 590 | // |
| 591 | void DestroyMainWindow() |
| 592 | { |
| 593 | if (::IsWindow(m_hWnd)) |
| 594 | { |
| 595 | ::DestroyWindow(m_hWnd); |
| 596 | m_hWnd = NULL; |
| 597 | } |
| 598 | |
| 599 | if (m_fRegistered) |
| 600 | { |
| 601 | ::UnregisterClassW(WIXIUIBA_WINDOW_CLASS, m_hModule); |
| 602 | m_fRegistered = FALSE; |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | // |
| 607 | // WndProc - standard windows message handler. |
| 608 | // |
| 609 | static LRESULT CALLBACK WndProc( |
| 610 | __in HWND hWnd, |
| 611 | __in UINT uMsg, |
| 612 | __in WPARAM wParam, |
| 613 | __in LPARAM lParam |
| 614 | ) |
| 615 | { |
| 616 | #pragma warning(suppress:4312) |
| 617 | CWixInternalUIBootstrapperApplication* pBA = reinterpret_cast<CWixInternalUIBootstrapperApplication*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); |
| 618 | |
| 619 | switch (uMsg) |
| 620 | { |
| 621 | case WM_NCCREATE: |
| 622 | { |
| 623 | LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); |
| 624 | pBA = reinterpret_cast<CWixInternalUIBootstrapperApplication*>(lpcs->lpCreateParams); |
| 625 | #pragma warning(suppress:4244) |
| 626 | ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA)); |
| 627 | } |
| 628 | break; |
| 629 | |
| 630 | case WM_NCDESTROY: |
| 631 | { |
| 632 | LRESULT lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); |
| 633 | ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); |
| 634 | ::PostQuitMessage(0); |
| 635 | return lres; |
| 636 | } |
| 637 | |
| 638 | case WM_CLOSE: |
| 639 | // If the user chose not to close, do *not* let the default window proc handle the message. |
| 640 | if (!pBA->OnClose()) |
| 641 | { |
| 642 | return 0; |
| 643 | } |
| 644 | break; |
| 645 | |
| 646 | case WM_WIXIUIBA_DETECT_PACKAGES: __fallthrough; |
| 647 | case WM_WIXIUIBA_DETECT_FOR_CLEANUP: |
| 648 | pBA->OnDetect(); |
| 649 | return 0; |
| 650 | |
| 651 | case WM_WIXIUIBA_PLAN_PACKAGES: |
| 652 | case WM_WIXIUIBA_PLAN_PACKAGES_FOR_CLEANUP: |
| 653 | pBA->OnPlan(static_cast<BOOTSTRAPPER_ACTION>(lParam)); |
| 654 | return 0; |
| 655 | |
| 656 | case WM_WIXIUIBA_APPLY_PACKAGES: |
| 657 | pBA->OnApply(); |
| 658 | return 0; |
| 659 | } |
| 660 | |
| 661 | return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); |
| 662 | } |
| 663 | |
| 664 | |
| 665 | // |
| 666 | // OnDetect - start the processing of packages. |
| 667 | // |
| 668 | void OnDetect() |
| 669 | { |
| 670 | HRESULT hr = S_OK; |
| 671 | |
| 672 | hr = m_pEngine->Detect(); |
| 673 | BalExitOnFailure(hr, "Failed to start detecting chain."); |
| 674 | |
| 675 | LExit: |
| 676 | if (FAILED(hr)) |
| 677 | { |
| 678 | SetLoadPackageFailure(hr); |
| 679 | } |
| 680 | } |
| 681 | |
| 682 | |
| 683 | // |
| 684 | // OnPlan - plan the detected changes. |
| 685 | // |
| 686 | void OnPlan( |
| 687 | __in BOOTSTRAPPER_ACTION action |
| 688 | ) |
| 689 | { |
| 690 | HRESULT hr = S_OK; |
| 691 | |
| 692 | m_plannedAction = action; |
| 693 | |
| 694 | hr = m_pEngine->Plan(action); |
| 695 | BalExitOnFailure(hr, "Failed to start planning packages."); |
| 696 | |
| 697 | LExit: |
| 698 | if (FAILED(hr)) |
| 699 | { |
| 700 | SetLoadPackageFailure(hr); |
| 701 | } |
| 702 | } |
| 703 | |
| 704 | |
| 705 | // |
| 706 | // OnApply - apply the packages. |
| 707 | // |
| 708 | void OnApply() |
| 709 | { |
| 710 | HRESULT hr = S_OK; |
| 711 | |
| 712 | hr = m_pEngine->Apply(m_hWnd); |
| 713 | BalExitOnFailure(hr, "Failed to start applying packages."); |
| 714 | |
| 715 | LExit: |
| 716 | if (FAILED(hr)) |
| 717 | { |
| 718 | SetLoadPackageFailure(hr); |
| 719 | } |
| 720 | } |
| 721 | |
| 722 | |
| 723 | // |
| 724 | // OnClose - called when the window is trying to be closed. |
| 725 | // |
| 726 | BOOL OnClose() |
| 727 | { |
| 728 | BOOL fClose = FALSE; |
| 729 | |
| 730 | // If we've already applied, just close. |
| 731 | if (m_fApplied) |
| 732 | { |
| 733 | fClose = TRUE; |
| 734 | } |
| 735 | else |
| 736 | { |
| 737 | PromptCancel(m_hWnd, TRUE, NULL, NULL); |
| 738 | |
| 739 | // If we're inside Apply then we never close, we just cancel to let rollback occur. |
| 740 | fClose = !m_fApplying; |
| 741 | } |
| 742 | |
| 743 | return fClose; |
| 744 | } |
| 745 | |
| 746 | |
| 747 | HRESULT EvaluateConditions() |
| 748 | { |
| 749 | HRESULT hr = S_OK; |
| 750 | BOOL fResult = FALSE; |
| 751 | |
| 752 | for (DWORD i = 0; i < m_Conditions.cConditions; ++i) |
| 753 | { |
| 754 | BAL_CONDITION* pCondition = m_Conditions.rgConditions + i; |
| 755 | |
| 756 | hr = BalConditionEvaluate(pCondition, m_pEngine, &fResult, &m_sczFailedMessage); |
| 757 | BalExitOnFailure(hr, "Failed to evaluate condition."); |
| 758 | |
| 759 | if (!fResult) |
| 760 | { |
| 761 | hr = E_WIXSTDBA_CONDITION_FAILED; |
| 762 | BalExitOnFailure(hr, "%ls", m_sczFailedMessage); |
| 763 | } |
| 764 | } |
| 765 | |
| 766 | ReleaseNullStrSecure(m_sczFailedMessage); |
| 767 | |
| 768 | LExit: |
| 769 | return hr; |
| 770 | } |
| 771 | |
| 772 | |
| 773 | void SetLoadPackageFailure( |
| 774 | __in HRESULT hrStatus |
| 775 | ) |
| 776 | { |
| 777 | Assert(FAILED(hrStatus)); |
| 778 | |
| 779 | if (!m_fApplied) |
| 780 | { |
| 781 | m_hrFinal = hrStatus; |
| 782 | m_fFailedToLoadPackage = TRUE; |
| 783 | } |
| 784 | |
| 785 | // Quietly exit. |
| 786 | ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); |
| 787 | } |
| 788 | |
| 789 | |
| 790 | public: |
| 791 | // |
| 792 | // Constructor - initialize member variables. |
| 793 | // |
| 794 | CWixInternalUIBootstrapperApplication( |
| 795 | __in HMODULE hModule |
| 796 | ) : CBootstrapperApplicationBase(3, 3000) |
| 797 | { |
| 798 | m_hModule = hModule; |
| 799 | m_commandAction = BOOTSTRAPPER_ACTION_UNKNOWN; |
| 800 | m_commandDisplay = BOOTSTRAPPER_DISPLAY_UNKNOWN; |
| 801 | |
| 802 | m_plannedAction = BOOTSTRAPPER_ACTION_UNKNOWN; |
| 803 | |
| 804 | m_Bundle = { }; |
| 805 | m_Conditions = { }; |
| 806 | m_sczConfirmCloseMessage = NULL; |
| 807 | m_sczFailedMessage = NULL; |
| 808 | |
| 809 | m_hUiThread = NULL; |
| 810 | m_fRegistered = FALSE; |
| 811 | m_hWnd = NULL; |
| 812 | |
| 813 | m_hrFinal = S_OK; |
| 814 | |
| 815 | m_restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE; |
| 816 | |
| 817 | m_fApplying = FALSE; |
| 818 | m_fApplied = FALSE; |
| 819 | m_fAutomaticRemoval = FALSE; |
| 820 | m_fFailedToLoadPackage = FALSE; |
| 821 | } |
| 822 | |
| 823 | |
| 824 | // |
| 825 | // Destructor - release member variables. |
| 826 | // |
| 827 | ~CWixInternalUIBootstrapperApplication() |
| 828 | { |
| 829 | ReleaseStr(m_sczFailedMessage); |
| 830 | ReleaseStr(m_sczConfirmCloseMessage); |
| 831 | BalConditionsUninitialize(&m_Conditions); |
| 832 | BalInfoUninitialize(&m_Bundle); |
| 833 | |
| 834 | ReleaseNullObject(m_pEngine); |
| 835 | } |
| 836 | |
| 837 | private: |
| 838 | HMODULE m_hModule; |
| 839 | BOOTSTRAPPER_ACTION m_commandAction; |
| 840 | BOOTSTRAPPER_DISPLAY m_commandDisplay; |
| 841 | |
| 842 | BOOTSTRAPPER_ACTION m_plannedAction; |
| 843 | |
| 844 | BAL_INFO_BUNDLE m_Bundle; |
| 845 | BAL_CONDITIONS m_Conditions; |
| 846 | LPWSTR m_sczFailedMessage; |
| 847 | LPWSTR m_sczConfirmCloseMessage; |
| 848 | |
| 849 | HANDLE m_hUiThread; |
| 850 | BOOL m_fRegistered; |
| 851 | HWND m_hWnd; |
| 852 | |
| 853 | HRESULT m_hrFinal; |
| 854 | |
| 855 | BOOTSTRAPPER_APPLY_RESTART m_restartResult; |
| 856 | |
| 857 | BOOL m_fApplying; |
| 858 | BOOL m_fApplied; |
| 859 | BOOL m_fAutomaticRemoval; |
| 860 | BOOL m_fFailedToLoadPackage; |
| 861 | }; |
| 862 | |
| 863 | |
| 864 | // |
| 865 | // CreateBootstrapperApplication - creates a new IBootstrapperApplication object. |
| 866 | // |
| 867 | EXTERN_C HRESULT CreateWixInternalUIBootstrapperApplication( |
| 868 | __in HMODULE hInstance, |
| 869 | __out IBootstrapperApplication** ppApplication |
| 870 | ) |
| 871 | { |
| 872 | HRESULT hr = S_OK; |
| 873 | |
| 874 | CWixInternalUIBootstrapperApplication* pApplication = new CWixInternalUIBootstrapperApplication(hInstance); |
| 875 | ExitOnNull(pApplication, hr, E_OUTOFMEMORY, "Failed to create new internal UI bootstrapper application."); |
| 876 | |
| 877 | hr = pApplication->QueryInterface(IID_PPV_ARGS(ppApplication)); |
| 878 | ExitOnRootFailure(hr, "Failed to query for IBootstrapperApplication."); |
| 879 | |
| 880 | LExit: |
| 881 | ReleaseObject(pApplication); |
| 882 | |
| 883 | return hr; |
| 884 | } |