From fb54576f1d05e82ba47cd718c4c4f8b3bad624c9 Mon Sep 17 00:00:00 2001 From: Sean Hall <r.sean.hall@gmail.com> Date: Fri, 18 Mar 2022 20:15:33 -0500 Subject: Give BA process id and option to wait for cancelled process to exit. --- .../inc/BootstrapperApplication.h | 27 +++ .../WixToolset.Mba.Core/BootstrapperApplication.cs | 25 +++ src/api/burn/WixToolset.Mba.Core/EventArgs.cs | 36 ++++ .../IBootstrapperApplication.cs | 32 ++++ .../IDefaultBootstrapperApplication.cs | 5 + src/api/burn/balutil/inc/BAFunctions.h | 1 + src/api/burn/balutil/inc/BalBaseBAFunctions.h | 10 ++ src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h | 1 + .../balutil/inc/BalBaseBootstrapperApplication.h | 10 ++ .../inc/BalBaseBootstrapperApplicationProc.h | 12 ++ .../burn/balutil/inc/IBootstrapperApplication.h | 9 + 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 + .../WixStandardBootstrapperApplication.cpp | 8 + src/test/burn/TestBA/TestBA.cs | 91 ++++++---- .../TestData/FailureTests/BundleD/BundleD.wixproj | 19 ++ .../burn/TestData/FailureTests/BundleD/BundleD.wxs | 19 ++ .../burn/WixToolsetTest.BurnE2E/FailureTests.cs | 42 +++++ .../WixToolsetTest.BurnE2E/TestBAController.cs | 15 ++ 31 files changed, 716 insertions(+), 124 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 create mode 100644 src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj create mode 100644 src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs (limited to 'src') diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h index 0b81b35a..df8cac76 100644 --- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h +++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h @@ -224,6 +224,7 @@ enum BOOTSTRAPPER_APPLICATION_MESSAGE BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE, BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE, + BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, }; enum BOOTSTRAPPER_APPLYCOMPLETE_ACTION @@ -282,6 +283,18 @@ enum BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_SUSPEND, }; +enum BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION +{ + // Instructs the engine to stop waiting for the process to exit. + // The package is immediately considered to have failed with ERROR_INSTALL_USEREXIT. + // The engine will never rollback the package. + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_ABANDON, + // Instructs the engine to wait for the process to exit. + // Once the process has exited, the package is considered to have failed with ERROR_INSTALL_USEREXIT. + // This allows the engine to rollback the package if necessary. + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_WAIT, +}; + enum BOOTSTRAPPER_SHUTDOWN_ACTION { BOOTSTRAPPER_SHUTDOWN_ACTION_NONE, @@ -997,6 +1010,20 @@ struct BA_ONEXECUTEPATCHTARGET_RESULTS BOOL fCancel; }; +struct BA_ONEXECUTEPROCESSCANCEL_ARGS +{ + DWORD cbSize; + LPCWSTR wzPackageId; + DWORD dwProcessId; + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation; +}; + +struct BA_ONEXECUTEPROCESSCANCEL_RESULTS +{ + DWORD cbSize; + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action; +}; + struct BA_ONEXECUTEPROGRESS_ARGS { DWORD cbSize; diff --git a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs index 8a2e0e93..5ed064fa 100644 --- a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs @@ -280,6 +280,9 @@ namespace WixToolset.Mba.Core /// <inheritdoc/> public event EventHandler<PlanRestoreRelatedBundleEventArgs> PlanRestoreRelatedBundle; + /// <inheritdoc/> + public event EventHandler<ExecuteProcessCancelEventArgs> ExecuteProcessCancel; + /// <summary> /// Entry point that is called when the bootstrapper application is ready to run. /// </summary> @@ -1369,6 +1372,19 @@ namespace WixToolset.Mba.Core } } + /// <summary> + /// Called by the engine, raises the <see cref="ExecuteProcessCancel"/> event. + /// </summary> + /// <param name="args">Additional arguments for this event.</param> + protected virtual void OnExecuteProcessCancel(ExecuteProcessCancelEventArgs args) + { + EventHandler<ExecuteProcessCancelEventArgs> handler = this.ExecuteProcessCancel; + if (null != handler) + { + handler(this, args); + } + } + #region IBootstrapperApplication Members int IBootstrapperApplication.BAProc(int message, IntPtr pvArgs, IntPtr pvResults, IntPtr pvContext) @@ -2119,6 +2135,15 @@ namespace WixToolset.Mba.Core return args.HResult; } + int IBootstrapperApplication.OnExecuteProcessCancel(string wzPackageId, int processId, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, ref BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION pAction) + { + ExecuteProcessCancelEventArgs args = new ExecuteProcessCancelEventArgs(wzPackageId, processId, recommendation, pAction); + this.OnExecuteProcessCancel(args); + + pAction = args.Action; + return args.HResult; + } + #endregion } } diff --git a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs index c93c2885..c2c73067 100644 --- a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs +++ b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs @@ -2488,4 +2488,40 @@ namespace WixToolset.Mba.Core /// </summary> public RequestState State { get; set; } } + + /// <summary> + /// Event arguments for <see cref="IDefaultBootstrapperApplication.ExecuteProcessCancel"/> + /// </summary> + [Serializable] + public class ExecuteProcessCancelEventArgs : HResultEventArgs + { + /// <summary /> + public ExecuteProcessCancelEventArgs(string packageId, int processId, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action) + { + this.PackageId = packageId; + this.ProcessId = processId; + this.Recommendation = recommendation; + this.Action = action; + } + + /// <summary> + /// Gets the identity of the package. + /// </summary> + public string PackageId { get; private set; } + + /// <summary> + /// Gets the process id. + /// </summary> + public int ProcessId { get; private set; } + + /// <summary> + /// Gets the recommended action from the engine. + /// </summary> + public BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION Recommendation { get; private set; } + + /// <summary> + /// Gets or sets the action to be performed. This is passed back to the engine. + /// </summary> + public BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION Action { get; set; } + } } diff --git a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs index d4fe8320..1786eecd 100644 --- a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs @@ -1170,6 +1170,18 @@ namespace WixToolset.Mba.Core [MarshalAs(UnmanagedType.I4)] int hrRecommended, [MarshalAs(UnmanagedType.I4)] ref int hrStatus ); + + /// <summary> + /// See <see cref="IDefaultBootstrapperApplication.ExecuteProcessCancel"/>. + /// </summary> + [PreserveSig] + [return: MarshalAs(UnmanagedType.I4)] + int OnExecuteProcessCancel( + [MarshalAs(UnmanagedType.LPWStr)] string wzPackageId, + int processId, + [MarshalAs(UnmanagedType.I4)] BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, + [MarshalAs(UnmanagedType.I4)] ref BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION pAction + ); } /// <summary> @@ -1906,6 +1918,26 @@ namespace WixToolset.Mba.Core Suspend, } + /// <summary> + /// The available actions for <see cref="IDefaultBootstrapperApplication.ExecuteProcessCancel"/>. + /// </summary> + public enum BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION + { + /// <summary> + /// Instructs the engine to stop waiting for the process to exit. + /// The package is immediately considered to have failed with ERROR_INSTALL_USEREXIT. + /// The engine will never rollback the package. + /// </summary> + Abandon, + + /// <summary> + /// Instructs the engine to wait for the process to exit. + /// Once the process has exited, the package is considered to have failed with ERROR_INSTALL_USEREXIT. + /// This allows the engine to rollback the package if necessary. + /// </summary> + Wait, + } + /// <summary> /// The result of evaluating a condition from a package. /// </summary> diff --git a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs index c9284b69..21d99b32 100644 --- a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs @@ -243,6 +243,11 @@ namespace WixToolset.Mba.Core /// </summary> event EventHandler<ExecutePackageCompleteEventArgs> ExecutePackageComplete; + /// <summary> + /// Fired when a package that spawned a process is cancelled. + /// </summary> + event EventHandler<ExecuteProcessCancelEventArgs> ExecuteProcessCancel; + /// <summary> /// Fired when the engine executes one or more patches targeting a product. /// </summary> diff --git a/src/api/burn/balutil/inc/BAFunctions.h b/src/api/burn/balutil/inc/BAFunctions.h index 58c26166..158e65b5 100644 --- a/src/api/burn/balutil/inc/BAFunctions.h +++ b/src/api/burn/balutil/inc/BAFunctions.h @@ -91,6 +91,7 @@ enum BA_FUNCTIONS_MESSAGE BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE, BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE, BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE, + BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL = BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, BA_FUNCTIONS_MESSAGE_ONTHEMELOADED = 1024, BA_FUNCTIONS_MESSAGE_WNDPROC, diff --git a/src/api/burn/balutil/inc/BalBaseBAFunctions.h b/src/api/burn/balutil/inc/BalBaseBAFunctions.h index fe5c99ba..614d4bcf 100644 --- a/src/api/burn/balutil/inc/BalBaseBAFunctions.h +++ b/src/api/burn/balutil/inc/BalBaseBAFunctions.h @@ -877,6 +877,16 @@ public: // IBootstrapperApplication return S_OK; } + virtual STDMETHODIMP OnExecuteProcessCancel( + __in_z LPCWSTR /*wzPackageId*/, + __in DWORD /*dwProcessId*/, + __in BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION /*recommendation*/, + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* /*pAction*/ + ) + { + return S_OK; + } + public: // IBAFunctions virtual STDMETHODIMP OnPlan( ) diff --git a/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h b/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h index 100e5c30..b96a180c 100644 --- a/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h +++ b/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h @@ -162,6 +162,7 @@ static HRESULT WINAPI BalBaseBAFunctionsProc( case BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE: case BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE: case BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE: + case BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL: hr = BalBaseBootstrapperApplicationProc((BOOTSTRAPPER_APPLICATION_MESSAGE)message, pvArgs, pvResults, pvContext); break; case BA_FUNCTIONS_MESSAGE_ONTHEMELOADED: diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h index fd06a83f..25570ffd 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h @@ -1077,6 +1077,16 @@ public: // IBootstrapperApplication return S_OK; } + virtual STDMETHODIMP OnExecuteProcessCancel( + __in_z LPCWSTR /*wzPackageId*/, + __in DWORD /*dwProcessId*/, + __in BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION /*recommendation*/, + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* /*pAction*/ + ) + { + return S_OK; + } + public: //CBalBaseBootstrapperApplication virtual STDMETHODIMP Initialize( __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h index 4e413e4e..b196d183 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h @@ -486,6 +486,15 @@ static HRESULT BalBaseBAProcOnExecutePackageComplete( return pBA->OnExecutePackageComplete(pArgs->wzPackageId, pArgs->hrStatus, pArgs->restart, pArgs->recommendation, &pResults->action); } +static HRESULT BalBaseBAProcOnExecuteProcessCancel( + __in IBootstrapperApplication* pBA, + __in BA_ONEXECUTEPROCESSCANCEL_ARGS* pArgs, + __inout BA_ONEXECUTEPROCESSCANCEL_RESULTS* pResults + ) +{ + return pBA->OnExecuteProcessCancel(pArgs->wzPackageId, pArgs->dwProcessId, pArgs->recommendation, &pResults->action); +} + static HRESULT BalBaseBAProcOnExecuteComplete( __in IBootstrapperApplication* pBA, __in BA_ONEXECUTECOMPLETE_ARGS* pArgs, @@ -1012,6 +1021,9 @@ static HRESULT WINAPI BalBaseBootstrapperApplicationProc( case BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE: hr = BalBaseBAProcOnApplyDowngrade(pBA, reinterpret_cast<BA_ONAPPLYDOWNGRADE_ARGS*>(pvArgs), reinterpret_cast<BA_ONAPPLYDOWNGRADE_RESULTS*>(pvResults)); break; + case BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL: + hr = BalBaseBAProcOnExecuteProcessCancel(pBA, reinterpret_cast<BA_ONEXECUTEPROCESSCANCEL_ARGS*>(pvArgs), reinterpret_cast<BA_ONEXECUTEPROCESSCANCEL_RESULTS*>(pvResults)); + break; } } diff --git a/src/api/burn/balutil/inc/IBootstrapperApplication.h b/src/api/burn/balutil/inc/IBootstrapperApplication.h index c9cf3126..6174c290 100644 --- a/src/api/burn/balutil/inc/IBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/IBootstrapperApplication.h @@ -714,4 +714,13 @@ DECLARE_INTERFACE_IID_(IBootstrapperApplication, IUnknown, "53C31D56-49C0-426B-A __in HRESULT hrRecommended, __inout HRESULT* phrStatus ) = 0; + + // OnExecuteProcessCancel - called when a package that spawned a process is cancelled. + // + STDMETHOD(OnExecuteProcessCancel)( + __in_z LPCWSTR wzPackageId, + __in DWORD dwProcessId, + __in BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction + ) = 0; }; 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 @@ <ItemGroup> <None Include="TestData\CacheTest\CacheSignatureTest.File" CopyToOutputDirectory="PreserveNewest" /> <None Include="TestData\PlanTest\BasicFunctionality_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> + <None Include="TestData\PlanTest\Failure_BundleD_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> <None Include="TestData\PlanTest\MsiTransaction_BundleAv1_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> + <None Include="TestData\PlanTest\MsuPackageFixture_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> <None Include="TestData\PlanTest\Slipstream_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> <None Include="TestData\PlanTest\Slipstream_BundleA_modified_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> 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<DWORD>(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<BOOL>(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal<BOOL>(TRUE, pPlan->fPerMachine); + Assert::Equal<BOOL>(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal<BOOL>(FALSE, pPlan->fDisableRollback); + Assert::Equal<BOOL>(FALSE, pPlan->fDisallowRemoval); + Assert::Equal<BOOL>(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<DWORD>(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<BOOL>(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal<BOOL>(TRUE, pPlan->fPerMachine); + Assert::Equal<BOOL>(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal<BOOL>(FALSE, pPlan->fDisableRollback); + Assert::Equal<BOOL>(FALSE, pPlan->fDisallowRemoval); + Assert::Equal<BOOL>(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<DWORD>(BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->msuPackage.pPackage->sczId); + Assert::Equal<DWORD>(action, pAction->msuPackage.action); + Assert::Equal<BOOL>(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 @@ +<?xml version="1.0" encoding="utf-8"?><BurnManifest xmlns="http://wixtoolset.org/schemas/v4/2008/Burn"><Log PathVariable="WixBundleLog" Prefix="~FailureTests_BundleD" Extension=".log" /><RelatedBundle Id="{3C1A4842-81AC-4C90-8B35-A5E18F034C8D}" Action="Upgrade" /><Variable Id="TestGroupName" Value="FailureTests" Type="string" Hidden="no" Persisted="no" /><Variable Id="WixBundleInProgressName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSource" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSourceFolder" Hidden="no" Persisted="yes" /><Variable Id="WixBundleLastUsedSource" Hidden="no" Persisted="yes" /><RegistrySearch Id="NETFRAMEWORK45" Variable="NETFRAMEWORK45" Root="HKLM" Key="SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" Value="Release" Type="value" VariableType="string" /><RegistrySearch Id="wrsQ7JTGqvaQuDYjfHJoyjxtkLlR6c" Variable="ExeA_Version" Root="HKLM" Key="Software\WiX\Tests\FailureTests\ExeA" Value="Version" Type="value" VariableType="string" /><UX><Payload Id="WixManagedBootstrapperApplicationHost" FilePath="mbahost.dll" SourcePath="u30" /><Payload Id="payO60IVK4ATGzPpMz3rwVbUWl6DyU" FilePath="WixToolset.Mba.Host.config" SourcePath="u0" /><Payload Id="payxj4zDAKL2NVlz4ohp0GvwFHepyI" FilePath="TestBA.dll" SourcePath="u1" /><Payload Id="pay1hOSAUC8_D633cD2TXpIXCL30OU" FilePath="mbanative.dll" SourcePath="u2" /><Payload Id="payujy6Izl_BlUNfHt2eI.ADfjYAv4" FilePath="WixToolset.Mba.Core.dll" SourcePath="u3" /><Payload Id="payR4EbR4OTDZpPEycWaSSM_gZRBWM" FilePath="mbapreq.thm" SourcePath="u4" /><Payload Id="paylVCy2Ecl8pHPdJTCQZryUG4T9us" FilePath="mbapreq.png" SourcePath="u5" /><Payload Id="payTaG4B_lob1aLcKFaOqSSG3MPMpU" FilePath="mbapreq.wxl" SourcePath="u6" /><Payload Id="payZwIGuiezVTitZOoZKxyh2DdRSGs" FilePath="1028\mbapreq.wxl" SourcePath="u7" /><Payload Id="pay.herBWX.LlOh8jLsx24aWdunV_0" FilePath="1029\mbapreq.wxl" SourcePath="u8" /><Payload Id="pay8DkMszYsoxxdgX14huLDMYXylQg" FilePath="1030\mbapreq.wxl" SourcePath="u9" /><Payload Id="payPaHpoTeOdkW.TK99IDwktNLhTAg" FilePath="1031\mbapreq.wxl" SourcePath="u10" /><Payload Id="pay45AtAzterLTMzZgdxxtuYvaiXwU" FilePath="1032\mbapreq.wxl" SourcePath="u11" /><Payload Id="payA2VEKIqhePyNIEmr14eyH3JoVLc" FilePath="1035\mbapreq.wxl" SourcePath="u12" /><Payload Id="payvre23ObscjzhcaFIifUAkXMdPa8" FilePath="1036\mbapreq.wxl" SourcePath="u13" /><Payload Id="paytxUV3vuBbG2c.a9c.d_sZX2x6wA" FilePath="1038\mbapreq.wxl" SourcePath="u14" /><Payload Id="payYvMWRK9xelo5.sQn7jRkJIaBp9A" FilePath="1040\mbapreq.wxl" SourcePath="u15" /><Payload Id="pay68KKSApyQimbA25t6kSbqhdeH10" FilePath="1041\mbapreq.wxl" SourcePath="u16" /><Payload Id="paypiqxaHpYZqx.9eDVjQrj1igLbRY" FilePath="1042\mbapreq.wxl" SourcePath="u17" /><Payload Id="payTO0YwZzxKpbqdrBVUcVRTu3BFe8" FilePath="1043\mbapreq.wxl" SourcePath="u18" /><Payload Id="payIXg2ldBJukRzhqWolJVOEbTmF34" FilePath="1044\mbapreq.wxl" SourcePath="u19" /><Payload Id="payOHIZbSkIvrpwKkkXI173tv3u3B4" FilePath="1045\mbapreq.wxl" SourcePath="u20" /><Payload Id="payQRQ_UZl_R2UtV0xDXB2yeH2bg3E" FilePath="1046\mbapreq.wxl" SourcePath="u21" /><Payload Id="payhrejLLBfc1i27iN._QPhQ4K337I" FilePath="1049\mbapreq.wxl" SourcePath="u22" /><Payload Id="payqEzaDNzxB68vGp29jgDcCos6dvg" FilePath="1051\mbapreq.wxl" SourcePath="u23" /><Payload Id="paydz8Vk8xSTyYohgGXTSIxWGXL5.Q" FilePath="1053\mbapreq.wxl" SourcePath="u24" /><Payload Id="pay0HRUZTlbC3taSOffJBsEj92Br8Y" FilePath="1055\mbapreq.wxl" SourcePath="u25" /><Payload Id="payIvUOkc_EMH7laMFehefNolV8hZo" FilePath="1060\mbapreq.wxl" SourcePath="u26" /><Payload Id="payLFhOb.rHuk4sW5CYAPMShG0NjGI" FilePath="2052\mbapreq.wxl" SourcePath="u27" /><Payload Id="payqIKCmERK7Nhxx_nNXvRxdKqKDbI" FilePath="2070\mbapreq.wxl" SourcePath="u28" /><Payload Id="payqeWUzIVaEqjuRXN0z8ECC3Y4tCc" FilePath="3082\mbapreq.wxl" SourcePath="u29" /><Payload Id="paylfeHEjJSSTnNzY9QMZM2Ye3Ipy4" FilePath="mbapreq.dll" SourcePath="u31" /><Payload Id="payDPxs6uy8nbky.R7zhir2RRAfc.c" FilePath="WixToolset.Mba.Host.dll" SourcePath="u32" /><Payload Id="uxTxMXPVMXwQrPTMIGa5WGt93w0Ns" FilePath="BootstrapperApplicationData.xml" SourcePath="u33" /><Payload Id="uxYRbgitOs0K878jn5L_z7LdJ21KI" FilePath="BundleExtensionData.xml" SourcePath="u34" /></UX><Container Id="WixAttachedContainer" FileSize="24029" Hash="03F9C95A2ADA5563D3D937C0161F22A76E12F2F0AF2AA6BE567292D0AB122E2C42990E97CA9C1EE9A5F43A571B01C4ED7A3EA5759A6836AC8BFD959D7FFDCB18" FilePath="BundleD.exe" AttachedIndex="1" Attached="yes" Primary="yes" /><Payload Id="NetFx48Web" FilePath="redist\ndp48-web.exe" FileSize="1439328" CertificateRootPublicKeyIdentifier="F49F9B33E25E33CCA0BFB15A62B7C29FFAB3880B" CertificateRootThumbprint="ABDCA79AF9DD48A0EA702AD45260B3C03093FB4B" DownloadUrl="https://go.microsoft.com/fwlink/?LinkId=2085155" Packaging="external" SourcePath="redist\ndp48-web.exe" /><Payload Id="TestExe.exe" FilePath="TestExe.exe" FileSize="23552" Hash="4344604ECBA4DFE5DE7C680CB1AA5BD6FAA29BF95CE07740F02878C2BB1EF6DE6432944A0DB79B034D1C6F68CF80842EEE442EA8A551816E52D3F68901C50AB9" Packaging="embedded" SourcePath="a0" Container="WixAttachedContainer" /><Payload Id="paygJp32KbpyjbVEQFNbl5_izmhdZw" FilePath="TestExe.exe.config" FileSize="387" Hash="8C819A9E835F3921FA80C5C783AB0C42DDAADF0C0F2BEF8630EA122ABCB9DC8EAF0B14E061C46B37C92F55114BB09A8D5B1B613947A76A648953F2C63C0ACA63" Packaging="embedded" SourcePath="a1" Container="WixAttachedContainer" /><RollbackBoundary Id="WixDefaultBoundary" Vital="yes" Transaction="no" /><Registration Id="{9C184683-04FB-49AD-9D79-65101BDC3EE3}" ExecutableName="BundleD.exe" PerMachine="yes" Tag="" Version="1.0.0.0" ProviderKey="{9C184683-04FB-49AD-9D79-65101BDC3EE3}"><Arp Register="yes" DisplayName="~FailureTests - BundleD" DisplayVersion="1.0.0.0" /></Registration><Chain><ExePackage Id="NetFx48Web" Cache="remove" CacheId="642721C60D52051C7F3434D8710FE3406A7CFE10B2B39E90EA847719ED1697D7C614F2DF44AD50412B1DF8C98DD78FDC57CA1D047D28C81AC158092E5FB18040" InstallSize="1439328" Size="1439328" PerMachine="yes" Permanent="yes" Vital="yes" RollbackBoundaryForward="WixDefaultBoundary" LogPathVariable="NetFx48WebLog" RollbackLogPathVariable="WixBundleRollbackLog_NetFx48Web" DetectCondition="NETFRAMEWORK45 >= 528040" InstallArguments="/q /norestart /log "[NetFx48WebLog].html"" UninstallArguments="" Uninstallable="no" RepairArguments="" Repairable="no" Protocol="netfx4"><PayloadRef Id="NetFx48Web" /></ExePackage><ExePackage Id="ExeA" Cache="remove" CacheId="4344604ECBA4DFE5DE7C680CB1AA5BD6FAA29BF95CE07740F02878C2BB1EF6DE6432944A0DB79B034D1C6F68CF80842EEE442EA8A551816E52D3F68901C50AB9" InstallSize="23939" Size="23939" PerMachine="yes" Permanent="no" Vital="yes" RollbackBoundaryBackward="WixDefaultBoundary" LogPathVariable="WixBundleLog_ExeA" RollbackLogPathVariable="WixBundleRollbackLog_ExeA" DetectCondition="ExeA_Version AND ExeA_Version >= v1.0.0.0" InstallArguments="/s 5000 /regw "HKLM\Software\WiX\Tests\FailureTests\ExeA,Version,String,1.0.0.0"" UninstallArguments="/regd "HKLM\Software\WiX\Tests\FailureTests\ExeA,Version"" Uninstallable="yes" RepairArguments="/regw "HKLM\Software\WiX\Tests\FailureTests\ExeA,Version,String,1.0.0.0"" Repairable="yes"><PayloadRef Id="TestExe.exe" /><PayloadRef Id="paygJp32KbpyjbVEQFNbl5_izmhdZw" /></ExePackage></Chain><CommandLine Variables="upperCase" /></BurnManifest> \ 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 @@ +<?xml version="1.0" encoding="utf-8"?><BurnManifest xmlns="http://wixtoolset.org/schemas/v4/2008/Burn"><Log PathVariable="WixBundleLog" Prefix="BurnBundle" Extension="log" /><RelatedBundle Id="{B94478B1-E1F3-4700-9CE8-6AA090854AEC}" Action="Upgrade" /><Variable Id="WixBundleInProgressName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSource" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSourceFolder" Hidden="no" Persisted="yes" /><Variable Id="WixBundleLastUsedSource" Hidden="no" Persisted="yes" /><UX><Payload Id="payaQenPi7_8hq6T._EXtBW0NvR7gA" FilePath="fakeba.dll" SourcePath="u0" /><Payload Id="uxTxMXPVMXwQrPTMIGa5WGt93w0Ns" FilePath="BootstrapperApplicationData.xml" SourcePath="u1" /><Payload Id="uxYRbgitOs0K878jn5L_z7LdJ21KI" FilePath="BundleExtensionData.xml" SourcePath="u2" /></UX><Container Id="WixAttachedContainer" FileSize="119" Hash="06D28293FD57CD231E125EF9C82418A488928A98832A6937A77A3283A17A5C37F8D619C51759319A57E8F8A948FA73E8C5814185A0114130F3213AB268073555" FilePath="test.exe" AttachedIndex="1" Attached="yes" Primary="yes" /><Payload Id="test.msu" FilePath="test.msu" FileSize="28" Hash="B040F02D2F90E04E9AFBDC91C00CEB5DF97D48E205D96DC0A44E10AF8870794DAE62CA70224F12BE9112AA730BBE470CA81FB5617AAC690E832F3F84510E92BA" Packaging="embedded" SourcePath="a0" Container="WixAttachedContainer" /><RollbackBoundary Id="WixDefaultBoundary" Vital="yes" Transaction="no" /><Registration Id="{06077C60-DC46-4F4A-8D3C-05F869187191}" ExecutableName="test.exe" PerMachine="yes" Tag="" Version="1.0.0.0" ProviderKey="{06077C60-DC46-4F4A-8D3C-05F869187191}"><Arp Register="yes" DisplayName="BurnBundle" DisplayVersion="1.0.0.0" Publisher="Example Corporation" /></Registration><Chain><MsuPackage Id="test.msu" Cache="keep" CacheId="B040F02D2F90E04E9AFBDC91C00CEB5DF97D48E205D96DC0A44E10AF8870794DAE62CA70224F12BE9112AA730BBE470CA81FB5617AAC690E832F3F84510E92BA" InstallSize="28" Size="28" PerMachine="yes" Permanent="no" Vital="yes" RollbackBoundaryForward="WixDefaultBoundary" RollbackBoundaryBackward="WixDefaultBoundary" DetectCondition="DetectedTheMsu" KB="xyz"><PayloadRef Id="test.msu" /></MsuPackage></Chain><CommandLine Variables="upperCase" /></BurnManifest> \ No newline at end of file diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp index 84e32867..444a917f 100644 --- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp @@ -2170,6 +2170,14 @@ private: // privates m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE, pArgs, pResults, m_pvBAFunctionsProcContext); } + void OnExecuteProcessCancelFallback( + __in BA_ONEXECUTEPROCESSCANCEL_ARGS* pArgs, + __inout BA_ONEXECUTEPROCESSCANCEL_RESULTS* pResults + ) + { + m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL, pArgs, pResults, m_pvBAFunctionsProcContext); + } + public: //CBalBaseBootstrapperApplication virtual STDMETHODIMP Initialize( diff --git a/src/test/burn/TestBA/TestBA.cs b/src/test/burn/TestBA/TestBA.cs index 3688e028..c219ce9c 100644 --- a/src/test/burn/TestBA/TestBA.cs +++ b/src/test/burn/TestBA/TestBA.cs @@ -21,7 +21,7 @@ namespace WixToolset.Test.BA private Form dummyWindow; private IntPtr windowHandle; private LaunchAction action; - private ManualResetEvent wait; + private readonly ManualResetEvent wait; private int result; private string updateBundlePath; @@ -397,6 +397,35 @@ namespace WixToolset.Test.BA } } + protected override void OnExecutePackageComplete(ExecutePackageCompleteEventArgs args) + { + bool logTestRegistryValue; + string recordTestRegistryValue = this.ReadPackageAction(args.PackageId, "RecordTestRegistryValue"); + if (!String.IsNullOrEmpty(recordTestRegistryValue) && Boolean.TryParse(recordTestRegistryValue, out logTestRegistryValue) && logTestRegistryValue) + { + var value = this.ReadTestRegistryValue(args.PackageId); + this.Log("TestRegistryValue: {0}, Version, '{1}'", args.PackageId, value); + } + } + + protected override void OnExecuteProcessCancel(ExecuteProcessCancelEventArgs args) + { + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action; + string actionValue = this.ReadPackageAction(args.PackageId, "ProcessCancelAction"); + if (actionValue != null && TryParseEnum<BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION>(actionValue, out action)) + { + args.Action = action; + } + + if (args.Action == BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION.Abandon) + { + // Give time to the process to start before its files are deleted. + Thread.Sleep(2000); + } + + this.Log("OnExecuteProcessCancel({0})", args.Action); + } + protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args) { this.Log("OnExecuteFilesInUse() - package: {0}, source: {1}, retries remaining: {2}, data: {3}", args.PackageId, args.Source, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray())); @@ -488,43 +517,34 @@ namespace WixToolset.Test.BA private void TestVariables() { // First make sure we can check and get standard variables of each type. + if (this.Engine.ContainsVariable("WindowsFolder")) { - string value = null; - if (this.Engine.ContainsVariable("WindowsFolder")) - { - value = this.Engine.GetVariableString("WindowsFolder"); - this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a string variable: WindowsFolder"); - } - else - { - throw new Exception("Engine did not define a standard variable: WindowsFolder"); - } + string value = this.Engine.GetVariableString("WindowsFolder"); + this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a string variable: WindowsFolder '{0}'", value)); + } + else + { + throw new Exception("Engine did not define a standard variable: WindowsFolder"); } + if (this.Engine.ContainsVariable("NTProductType")) { - long value = 0; - if (this.Engine.ContainsVariable("NTProductType")) - { - value = this.Engine.GetVariableNumeric("NTProductType"); - this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a numeric variable: NTProductType"); - } - else - { - throw new Exception("Engine did not define a standard variable: NTProductType"); - } + long value = this.Engine.GetVariableNumeric("NTProductType"); + this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a numeric variable: NTProductType '{0}'", value)); + } + else + { + throw new Exception("Engine did not define a standard variable: NTProductType"); } + if (this.Engine.ContainsVariable("VersionMsi")) { - string value = null; - if (this.Engine.ContainsVariable("VersionMsi")) - { - value = this.Engine.GetVariableVersion("VersionMsi"); - this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a version variable: VersionMsi"); - } - else - { - throw new Exception("Engine did not define a standard variable: VersionMsi"); - } + string value = this.Engine.GetVariableVersion("VersionMsi"); + this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a version variable: VersionMsi '{0}'", value)); + } + else + { + throw new Exception("Engine did not define a standard variable: VersionMsi"); } // Now validate that Contians returns false for non-existant variables of each type. @@ -596,6 +616,15 @@ namespace WixToolset.Test.BA } } + private string ReadTestRegistryValue(string name) + { + string testName = this.Engine.GetVariableString("TestGroupName"); + using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\{0}\{1}", testName, name))) + { + return testKey == null ? null : testKey.GetValue("Version") as string; + } + } + private static bool TryParseEnum<T>(string value, out T t) { try diff --git a/src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj new file mode 100644 index 00000000..7b7408c6 --- /dev/null +++ b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj @@ -0,0 +1,19 @@ +<!-- 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. --> +<Project Sdk="WixToolset.Sdk"> + <PropertyGroup> + <OutputType>Bundle</OutputType> + <UpgradeCode>{3C1A4842-81AC-4C90-8B35-A5E18F034C8D}</UpgradeCode> + <Version>1.0.0.0</Version> + </PropertyGroup> + <ItemGroup> + <Compile Include="..\..\Templates\Bundle.wxs" Link="Bundle.wxs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" /> + </ItemGroup> + <ItemGroup> + <PackageReference Include="WixToolset.Bal.wixext" /> + <PackageReference Include="WixToolset.NetFx.wixext" /> + <PackageReference Include="WixToolset.Util.wixext" /> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs new file mode 100644 index 00000000..ca70236d --- /dev/null +++ b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs @@ -0,0 +1,19 @@ +<!-- 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. --> + +<?define TestExeRegistryKey = Software\WiX\Tests\$(var.TestGroupName)\ExeA?> + +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"> + <Fragment> + <util:RegistrySearch Root="HKLM" Key="$(var.TestExeRegistryKey)" Value="Version" Variable="ExeA_Version" /> + + <PackageGroup Id="BundlePackages"> + <ExePackage Id="ExeA" Cache="remove" PerMachine="yes" + DetectCondition="ExeA_Version AND ExeA_Version >= v$(var.Version)" + InstallArguments="/s 5000 /regw "HKLM\$(var.TestExeRegistryKey),Version,String,$(var.Version)"" + RepairArguments="/regw "HKLM\$(var.TestExeRegistryKey),Version,String,$(var.Version)"" + UninstallArguments="/regd "HKLM\$(var.TestExeRegistryKey),Version""> + <PayloadGroupRef Id="TestExePayloads" /> + </ExePackage> + </PackageGroup> + </Fragment> +</Wix> diff --git a/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs index d8428a54..bbc0b387 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs @@ -2,7 +2,9 @@ namespace WixToolsetTest.BurnE2E { + using System.Threading; using WixTestTools; + using WixToolset.Mba.Core; using Xunit; using Xunit.Abstractions; @@ -10,6 +12,46 @@ namespace WixToolsetTest.BurnE2E { public FailureTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + [Fact] + public void CanCancelExePackageAndAbandonIt() + { + var bundleD = this.CreateBundleInstaller("BundleD"); + var testBAController = this.CreateTestBAController(); + + // Cancel package ExeA after it starts. + testBAController.SetPackageCancelExecuteAtProgress("ExeA", 1); + testBAController.SetPackageRecordTestRegistryValue("ExeA"); + + var logPath = bundleD.Install((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_USEREXIT); + bundleD.VerifyUnregisteredAndRemovedFromPackageCache(); + + Assert.True(LogVerifier.MessageInLogFile(logPath, "TestRegistryValue: ExeA, Version, ''")); + + // Make sure ExeA finishes running. + Thread.Sleep(3000); + + bundleD.VerifyExeTestRegistryValue("ExeA", "1.0.0.0"); + } + + [Fact] + public void CanCancelExePackageAndWaitUntilItCompletes() + { + var bundleD = this.CreateBundleInstaller("BundleD"); + var testBAController = this.CreateTestBAController(); + + // Cancel package ExeA after it starts. + testBAController.SetPackageCancelExecuteAtProgress("ExeA", 1); + testBAController.SetPackageProcessCancelAction("ExeA", BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION.Wait); + testBAController.SetPackageRecordTestRegistryValue("ExeA"); + + var logPath = bundleD.Install((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_USEREXIT); + bundleD.VerifyUnregisteredAndRemovedFromPackageCache(); + + Assert.True(LogVerifier.MessageInLogFile(logPath, "TestRegistryValue: ExeA, Version, '1.0.0.0'")); + + bundleD.VerifyExeTestRegistryValue("ExeA", "1.0.0.0"); + } + [Fact] public void CanCancelMsiPackageVeryEarly() { diff --git a/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs b/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs index d2e8a1ca..fa553919 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs @@ -147,6 +147,21 @@ namespace WixToolsetTest.BurnE2E this.SetPackageState(packageId, String.Concat(featureId, "Requested"), state.ToString()); } + /// <summary> + /// Requests the BA to log the test registry value for the specified package. + /// </summary> + /// <param name="packageId"></param> + /// <param name="value"></param> + public void SetPackageRecordTestRegistryValue(string packageId, string value = "true") + { + this.SetPackageState(packageId, "RecordTestRegistryValue", value); + } + + public void SetPackageProcessCancelAction(string packageId, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action) + { + this.SetPackageState(packageId, "ProcessCancelAction", action.ToString()); + } + /// <summary> /// Sets the number of times to re-run the Detect phase. /// </summary> -- cgit v1.2.3-55-g6feb