From fb54576f1d05e82ba47cd718c4c4f8b3bad624c9 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 18 Mar 2022 20:15:33 -0500 Subject: Give BA process id and option to wait for cancelled process to exit. --- src/burn/engine/apply.cpp | 8 + src/burn/engine/apply.h | 5 + src/burn/engine/bundlepackageengine.cpp | 28 +-- src/burn/engine/elevation.cpp | 17 +- src/burn/engine/engine.mc | 7 + src/burn/engine/exeengine.cpp | 132 ++++++++++---- src/burn/engine/exeengine.h | 9 + src/burn/engine/msuengine.cpp | 32 +--- src/burn/engine/userexperience.cpp | 30 +++- src/burn/engine/userexperience.h | 6 + src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj | 2 + src/burn/test/BurnUnitTest/PlanTest.cpp | 200 +++++++++++++++++++++ .../TestData/PlanTest/Failure_BundleD_manifest.xml | 1 + .../PlanTest/MsuPackageFixture_manifest.xml | 1 + 14 files changed, 385 insertions(+), 93 deletions(-) create mode 100644 src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml create mode 100644 src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml (limited to 'src/burn') diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp index 3ad22e9b..73b5b396 100644 --- a/src/burn/engine/apply.cpp +++ b/src/burn/engine/apply.cpp @@ -3326,6 +3326,14 @@ static int GenericExecuteMessageHandler( } break; + case GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL: + { + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action = BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_ABANDON; + UserExperienceOnExecuteProcessCancel(pContext->pUX, pContext->wzExecutingPackageId, pMessage->processCancel.dwProcessId, &action); // ignore return value. + nResult = BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_WAIT == action ? IDRETRY : IDIGNORE; + } + break; + case GENERIC_EXECUTE_MESSAGE_ERROR: UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->wzExecutingPackageId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, 0, NULL, &nResult); // ignore return value. break; diff --git a/src/burn/engine/apply.h b/src/burn/engine/apply.h index 1717a71a..47f0ece6 100644 --- a/src/burn/engine/apply.h +++ b/src/burn/engine/apply.h @@ -13,6 +13,7 @@ enum GENERIC_EXECUTE_MESSAGE_TYPE GENERIC_EXECUTE_MESSAGE_ERROR, GENERIC_EXECUTE_MESSAGE_PROGRESS, GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE, + GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL, }; typedef struct _APPLY_AUTHENTICATION_REQUIRED_DATA @@ -43,6 +44,10 @@ typedef struct _GENERIC_EXECUTE_MESSAGE DWORD cFiles; LPCWSTR* rgwzFiles; } filesInUse; + struct + { + DWORD dwProcessId; + } processCancel; }; } GENERIC_EXECUTE_MESSAGE; diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp index 88a00f5e..0bee054f 100644 --- a/src/burn/engine/bundlepackageengine.cpp +++ b/src/burn/engine/bundlepackageengine.cpp @@ -251,7 +251,6 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle( ) { HRESULT hr = S_OK; - int nResult = IDNOACTION; LPCWSTR wzArguments = NULL; LPWSTR sczArguments = NULL; LPWSTR sczArgumentsFormatted = NULL; @@ -420,31 +419,10 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle( hr = EmbeddedRunBundle(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode); ExitOnFailure(hr, "Failed to run bundle as embedded from path: %ls", sczExecutablePath); } - else // create and wait for the executable process while sending fake progress to allow cancel. + else { - // Make the cache location of the executable the current directory to help those executables - // that expect stuff to be relative to them. - si.cb = sizeof(si); - if (!::CreateProcessW(sczExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, sczCachedDirectory, &si, &pi)) - { - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczExecutablePath); - } - - do - { - message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwUIHint = MB_OKCANCEL; - message.progress.dwPercentage = 50; - nResult = pfnGenericMessageHandler(&message, pvContext); - hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); - ExitOnRootFailure(hr, "Bootstrapper application aborted during BUNDLE progress."); - - hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode); - if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr) - { - ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczExecutablePath); - } - } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr); + hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczExecutablePath, sczCommand, sczCachedDirectory, &dwExitCode); + ExitOnFailure(hr, "Failed to run BUNDLE process"); } hr = ExeEngineHandleExitCode(pPackage->Bundle.rgExitCodes, pPackage->Bundle.cExitCodes, dwExitCode, pRestart); diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp index 636d67ce..3c2872f1 100644 --- a/src/burn/engine/elevation.cpp +++ b/src/burn/engine/elevation.cpp @@ -43,6 +43,7 @@ typedef enum _BURN_ELEVATION_MESSAGE_TYPE BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE, BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_FILES_IN_USE, @@ -1812,7 +1813,14 @@ static HRESULT ProcessGenericExecuteMessages( // read message parameters hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.progress.dwPercentage); - ExitOnFailure(hr, "Failed to progress."); + ExitOnFailure(hr, "Failed to read progress."); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL: + message.type = GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL; + + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.processCancel.dwProcessId); + ExitOnFailure(hr, "Failed to read processId."); break; case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR: @@ -3450,6 +3458,13 @@ static int GenericExecuteMessageHandler( dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS; break; + case GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL: + hr = BuffWriteNumber(&pbData, &cbData, pMessage->processCancel.dwProcessId); + ExitOnFailure(hr, "Failed to write progress percentage to message buffer."); + + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL; + break; + case GENERIC_EXECUTE_MESSAGE_ERROR: // serialize message data hr = BuffWriteNumber(&pbData, &cbData, pMessage->error.dwErrorCode); diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc index 53e6b256..9e139661 100644 --- a/src/burn/engine/engine.mc +++ b/src/burn/engine/engine.mc @@ -933,6 +933,13 @@ Language=English Could not create system restore point, error: 0x%1!x!. Continuing... . +MessageId=364 +Severity=Success +SymbolicName=MSG_EXECUTE_PROCESS_DELAYED_CANCEL_REQUESTED +Language=English +Bootstrapper application requested delayed cancel during package process progress, id: %1!ls!. Waiting... +. + MessageId=370 Severity=Success SymbolicName=MSG_SESSION_BEGIN diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp index c984f5a7..4c3c6fb0 100644 --- a/src/burn/engine/exeengine.cpp +++ b/src/burn/engine/exeengine.cpp @@ -317,7 +317,6 @@ extern "C" HRESULT ExeEngineExecutePackage( ) { HRESULT hr = S_OK; - int nResult = IDNOACTION; LPCWSTR wzArguments = NULL; LPWSTR sczArguments = NULL; LPWSTR sczArgumentsFormatted = NULL; @@ -327,10 +326,7 @@ extern "C" HRESULT ExeEngineExecutePackage( LPWSTR sczCommand = NULL; LPWSTR sczCommandObfuscated = NULL; HANDLE hExecutableFile = INVALID_HANDLE_VALUE; - STARTUPINFOW si = { }; - PROCESS_INFORMATION pi = { }; DWORD dwExitCode = 0; - GENERIC_EXECUTE_MESSAGE message = { }; BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage; BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; @@ -442,37 +438,10 @@ extern "C" HRESULT ExeEngineExecutePackage( hr = NetFxRunChainer(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode); ExitOnFailure(hr, "Failed to run netfx chainer: %ls", sczExecutablePath); } - else // create and wait for the executable process while sending fake progress to allow cancel. + else { - // Make the cache location of the executable the current directory to help those executables - // that expect stuff to be relative to them. - si.cb = sizeof(si); - if (!::CreateProcessW(sczExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, sczCachedDirectory, &si, &pi)) - { - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczExecutablePath); - } - - if (pPackage->Exe.fFireAndForget) - { - ::WaitForInputIdle(pi.hProcess, 5000); - ExitFunction(); - } - - do - { - message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwUIHint = MB_OKCANCEL; - message.progress.dwPercentage = 50; - nResult = pfnGenericMessageHandler(&message, pvContext); - hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); - ExitOnRootFailure(hr, "Bootstrapper application aborted during EXE progress."); - - hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode); - if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr) - { - ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczExecutablePath); - } - } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr); + hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczExecutablePath, sczCommand, sczCachedDirectory, &dwExitCode); + ExitOnFailure(hr, "Failed to run EXE process"); } hr = ExeEngineHandleExitCode(pPackage->Exe.rgExitCodes, pPackage->Exe.cExitCodes, dwExitCode, pRestart); @@ -487,8 +456,6 @@ LExit: StrSecureZeroFreeString(sczCommand); ReleaseStr(sczCommandObfuscated); - ReleaseHandle(pi.hThread); - ReleaseHandle(pi.hProcess); ReleaseFileHandle(hExecutableFile); // Best effort to clear the execute package cache folder and action variables. @@ -498,6 +465,99 @@ LExit: return hr; } +extern "C" HRESULT ExeEngineRunProcess( + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __in BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzExecutablePath, + __in_z LPWSTR wzCommand, + __in_z_opt LPCWSTR wzCachedDirectory, + __inout DWORD* pdwExitCode + ) +{ + HRESULT hr = S_OK; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + GENERIC_EXECUTE_MESSAGE message = { }; + int nResult = IDNOACTION; + DWORD dwProcessId = 0; + BOOL fDelayedCancel = FALSE; + BOOL fFireAndForget = BURN_PACKAGE_TYPE_EXE == pPackage->type && pPackage->Exe.fFireAndForget; + BOOL fInheritHandles = BURN_PACKAGE_TYPE_BUNDLE == pPackage->type; + + // Make the cache location of the executable the current directory to help those executables + // that expect stuff to be relative to them. + si.cb = sizeof(si); + if (!::CreateProcessW(wzExecutablePath, wzCommand, NULL, NULL, fInheritHandles, CREATE_NO_WINDOW, NULL, wzCachedDirectory, &si, &pi)) + { + ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", wzExecutablePath); + } + + if (fFireAndForget) + { + ::WaitForInputIdle(pi.hProcess, 5000); + ExitFunction(); + } + + dwProcessId = ::GetProcessId(pi.hProcess); + + // Wait for the executable process while sending fake progress to allow cancel. + do + { + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwUIHint = MB_OKCANCEL; + message.progress.dwPercentage = 50; + nResult = pfnGenericMessageHandler(&message, pvContext); + + if (IDCANCEL == nResult) + { + memset(&message, 0, sizeof(message)); + message.type = GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL; + message.dwUIHint = MB_ABORTRETRYIGNORE; + message.processCancel.dwProcessId = dwProcessId; + nResult = pfnGenericMessageHandler(&message, pvContext); + + if (IDIGNORE == nResult) // abandon + { + nResult = IDCANCEL; + fDelayedCancel = FALSE; + } + //else if (IDABORT == nResult) // kill + else // wait + { + if (!fDelayedCancel) + { + fDelayedCancel = TRUE; + + LogId(REPORT_STANDARD, MSG_EXECUTE_PROCESS_DELAYED_CANCEL_REQUESTED, pPackage->sczId); + } + + nResult = IDNOACTION; + } + } + + hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); + ExitOnRootFailure(hr, "Bootstrapper application aborted during package process progress."); + + hr = ProcWaitForCompletion(pi.hProcess, 500, pdwExitCode); + if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr) + { + ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", wzExecutablePath); + } + } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr); + + if (fDelayedCancel) + { + ExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT), "Bootstrapper application cancelled during package process progress, exit code: 0x%x", *pdwExitCode); + } + +LExit: + ReleaseHandle(pi.hThread); + ReleaseHandle(pi.hProcess); + + return hr; +} + extern "C" void ExeEngineUpdateInstallRegistrationState( __in BURN_EXECUTE_ACTION* pAction, __in HRESULT hrExecute diff --git a/src/burn/engine/exeengine.h b/src/burn/engine/exeengine.h index 743621b7..636988f1 100644 --- a/src/burn/engine/exeengine.h +++ b/src/burn/engine/exeengine.h @@ -42,6 +42,15 @@ HRESULT ExeEngineExecutePackage( __in LPVOID pvContext, __out BOOTSTRAPPER_APPLY_RESTART* pRestart ); +HRESULT ExeEngineRunProcess( + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __in BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzExecutablePath, + __in_z LPWSTR wzCommand, + __in_z_opt LPCWSTR wzCachedDirectory, + __inout DWORD* pdwExitCode + ); void ExeEngineUpdateInstallRegistrationState( __in BURN_EXECUTE_ACTION* pAction, __in HRESULT hrExecute diff --git a/src/burn/engine/msuengine.cpp b/src/burn/engine/msuengine.cpp index 091bbe62..2f1fb61c 100644 --- a/src/burn/engine/msuengine.cpp +++ b/src/burn/engine/msuengine.cpp @@ -264,7 +264,6 @@ extern "C" HRESULT MsuEngineExecutePackage( ) { HRESULT hr = S_OK; - int nResult = IDNOACTION; LPWSTR sczCachedDirectory = NULL; LPWSTR sczMsuPath = NULL; LPWSTR sczWindowsPath = NULL; @@ -350,35 +349,8 @@ extern "C" HRESULT MsuEngineExecutePackage( hr = EnsureWUServiceEnabled(fStopWusaService, &schWu, &fWuWasDisabled); ExitOnFailure(hr, "Failed to ensure WU service was enabled to install MSU package."); - // create process - si.cb = sizeof(si); - if (!::CreateProcessW(sczWusaPath, sczCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) - { - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczWusaPath); - } - - do - { - message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwUIHint = MB_OKCANCEL; - message.progress.dwPercentage = 50; - nResult = pfnGenericMessageHandler(&message, pvContext); - hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); - ExitOnRootFailure(hr, "Bootstrapper application aborted during MSU progress."); - - // wait for process to terminate - hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode); - if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr) - { - ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczWusaPath); - } - } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr); - - // get process exit code - if (!::GetExitCodeProcess(pi.hProcess, &dwExitCode)) - { - ExitWithLastError(hr, "Failed to get process exit code."); - } + hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczWusaPath, sczCommand, NULL, &dwExitCode); + ExitOnFailure(hr, "Failed to run MSU process"); // We'll normalize the restart required error code from wusa.exe just in case. Most likely // that on reboot we'll actually get WU_S_REBOOT_REQUIRED. diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp index 81ce8bb9..06f87363 100644 --- a/src/burn/engine/userexperience.cpp +++ b/src/burn/engine/userexperience.cpp @@ -104,7 +104,7 @@ extern "C" HRESULT UserExperienceLoad( args.pCommand = pCommand; args.pfnBootstrapperEngineProc = EngineForApplicationProc; args.pvBootstrapperEngineProcContext = pEngineContext; - args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 14, 0); + args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 17, 0); results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); @@ -1701,6 +1701,34 @@ LExit: return hr; } +BAAPI UserExperienceOnExecuteProcessCancel( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD dwProcessId, + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEPROCESSCANCEL_ARGS args = { }; + BA_ONEXECUTEPROCESSCANCEL_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.dwProcessId = dwProcessId; + args.recommendation = *pAction; + + results.cbSize = sizeof(results); + results.action = *pAction; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, &args, &results); + ExitOnFailure(hr, "BA OnExecuteProcessCancel failed."); + + *pAction = results.action; + +LExit: + return hr; +} + EXTERN_C BAAPI UserExperienceOnExecuteProgress( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/burn/engine/userexperience.h b/src/burn/engine/userexperience.h index 2f18acdd..de558ad5 100644 --- a/src/burn/engine/userexperience.h +++ b/src/burn/engine/userexperience.h @@ -398,6 +398,12 @@ BAAPI UserExperienceOnExecutePatchTarget( __in_z LPCWSTR wzPackageId, __in_z LPCWSTR wzTargetProductCode ); +BAAPI UserExperienceOnExecuteProcessCancel( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD dwProcessId, + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction + ); BAAPI UserExperienceOnExecuteProgress( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj index 7375af86..35415dc3 100644 --- a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj @@ -80,7 +80,9 @@ + + diff --git a/src/burn/test/BurnUnitTest/PlanTest.cpp b/src/burn/test/BurnUnitTest/PlanTest.cpp index 4d726fb4..770922b4 100644 --- a/src/burn/test/BurnUnitTest/PlanTest.cpp +++ b/src/burn/test/BurnUnitTest/PlanTest.cpp @@ -10,7 +10,9 @@ static HRESULT WINAPI PlanTestBAProc( ); static LPCWSTR wzMsiTransactionManifestFileName = L"MsiTransaction_BundleAv1_manifest.xml"; +static LPCWSTR wzSingleExeManifestFileName = L"Failure_BundleD_manifest.xml"; static LPCWSTR wzSingleMsiManifestFileName = L"BasicFunctionality_BundleA_manifest.xml"; +static LPCWSTR wzSingleMsuManifestFileName = L"MsuPackageFixture_manifest.xml"; static LPCWSTR wzSlipstreamManifestFileName = L"Slipstream_BundleA_manifest.xml"; static LPCWSTR wzSlipstreamModifiedManifestFileName = L"Slipstream_BundleA_modified_manifest.xml"; @@ -658,6 +660,97 @@ namespace Bootstrapper ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); } + [Fact] + void SingleExeInstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleExeManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPermanentPackagesAsPresentAndCached(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + NativeAssert::StringEqual(L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", pPlan->wzBundleId); + NativeAssert::StringEqual(L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", pPlan->wzBundleProviderKey); + Assert::Equal(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal(FALSE, pPlan->fDisableRollback); + Assert::Equal(FALSE, pPlan->fDisallowRemoval); + Assert::Equal(FALSE, pPlan->fDowngrade); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}"); + Assert::Equal(dwIndex, pPlan->cRegistrationActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}"); + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions); + + fRollback = FALSE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"ExeA"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(1463267ull, pPlan->qwEstimatedSize); + Assert::Equal(119695ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"ExeA"); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"ExeA", BOOTSTRAPPER_ACTION_STATE_INSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"ExeA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(1ul, pPlan->cExecutePackagesTotal); + Assert::Equal(2ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"NetFx48Web"); + ValidateCleanAction(pPlan, dwIndex++, L"ExeA"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(2ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"ExeA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + } + [Fact] void SingleMsiCacheTest() { @@ -1532,6 +1625,98 @@ namespace Bootstrapper ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); } + [Fact] + void SingleMsuInstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsuManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPackagesAsAbsent(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + NativeAssert::StringEqual(L"{06077C60-DC46-4F4A-8D3C-05F869187191}", pPlan->wzBundleId); + NativeAssert::StringEqual(L"{06077C60-DC46-4F4A-8D3C-05F869187191}", pPlan->wzBundleProviderKey); + Assert::Equal(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal(FALSE, pPlan->fDisableRollback); + Assert::Equal(FALSE, pPlan->fDisallowRemoval); + Assert::Equal(FALSE, pPlan->fDowngrade); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{06077C60-DC46-4F4A-8D3C-05F869187191}", L"{06077C60-DC46-4F4A-8D3C-05F869187191}"); + Assert::Equal(dwIndex, pPlan->cRegistrationActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{06077C60-DC46-4F4A-8D3C-05F869187191}", L"{06077C60-DC46-4F4A-8D3C-05F869187191}"); + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions); + + fRollback = FALSE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"test.msu"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateCacheRollbackPackage(pPlan, fRollback, dwIndex++, L"test.msu"); + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(56ull, pPlan->qwEstimatedSize); + Assert::Equal(140ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"test.msu"); + ValidateExecuteMsuPackage(pPlan, fRollback, dwIndex++, L"test.msu", BOOTSTRAPPER_ACTION_STATE_INSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"test.msu"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsuPackage(pPlan, fRollback, dwIndex++, L"test.msu", BOOTSTRAPPER_ACTION_STATE_UNINSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(1ul, pPlan->cExecutePackagesTotal); + Assert::Equal(2ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{06077C60-DC46-4F4A-8D3C-05F869187191}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"test.msu", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + } + [Fact] void SlipstreamInstallTest() { @@ -2571,6 +2756,21 @@ namespace Bootstrapper NativeAssert::StringEqual(wzPackageId, pOrderedPatch->pPackage->sczId); } + void ValidateExecuteMsuPackage( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId, + __in BOOTSTRAPPER_ACTION_STATE action + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->msuPackage.pPackage->sczId); + Assert::Equal(action, pAction->msuPackage.action); + Assert::Equal(FALSE, pAction->fDeleted); + } + void ValidateExecutePackageDependency( __in BURN_PLAN* pPlan, __in BOOL fRollback, diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml new file mode 100644 index 00000000..6afb0108 --- /dev/null +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml new file mode 100644 index 00000000..fb6afa88 --- /dev/null +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.3-55-g6feb