From 9e2cda60e3852660f235beb5e0af1c746d0045e6 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 15 Dec 2021 10:48:06 -0600 Subject: FilesInUse messages are too different to unify in the Burn engine. Fixes #6348 --- .../inc/BootstrapperApplication.h | 8 ++++ .../WixToolset.Mba.Core/BootstrapperApplication.cs | 4 +- src/api/burn/WixToolset.Mba.Core/EventArgs.cs | 8 +++- .../IBootstrapperApplication.cs | 26 +++++++--- src/api/burn/balutil/inc/BalBaseBAFunctions.h | 1 + .../balutil/inc/BalBaseBootstrapperApplication.h | 1 + .../inc/BalBaseBootstrapperApplicationProc.h | 2 +- .../burn/balutil/inc/IBootstrapperApplication.h | 22 +++------ src/burn/engine/apply.cpp | 44 ++++++++++------- src/burn/engine/apply.h | 4 +- src/burn/engine/detect.cpp | 4 +- src/burn/engine/elevation.cpp | 36 +++++++++----- src/burn/engine/embedded.cpp | 4 +- src/burn/engine/exeengine.cpp | 2 +- src/burn/engine/msuengine.cpp | 2 +- src/burn/engine/netfxchainer.cpp | 8 ++-- src/burn/engine/userexperience.cpp | 56 ++++++++++++++++++++-- src/burn/engine/userexperience.h | 6 ++- .../SetBuildNumber/Directory.Packages.props.pp | 1 + src/libs/dutil/WixToolset.DUtil/inc/wiutil.h | 4 +- src/libs/dutil/WixToolset.DUtil/wiutil.cpp | 16 ++++--- src/test/burn/TestBA/TestBA.cs | 2 +- .../FilesInUseTests/BundleA/BundleA.wixproj | 18 +++++++ .../TestData/FilesInUseTests/BundleA/BundleA.wxs | 10 ++++ .../FilesInUseTests/PackageA/PackageA.wixproj | 13 +++++ .../TestData/FilesInUseTests/PackageA/PackageA.wxs | 10 ++++ .../TestData/FilesInUseTests/PackageA/license.txt | 1 + .../burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs | 38 +++++++++++++++ .../WixToolsetTest.BurnE2E/TestBAController.cs | 10 ++++ 29 files changed, 277 insertions(+), 84 deletions(-) create mode 100644 src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wixproj create mode 100644 src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wxs create mode 100644 src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wixproj create mode 100644 src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wxs create mode 100644 src/test/burn/TestData/FilesInUseTests/PackageA/license.txt create mode 100644 src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h index e5f924c8..c2641b5f 100644 --- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h +++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h @@ -38,6 +38,13 @@ enum BOOTSTRAPPER_ERROR_TYPE BOOTSTRAPPER_ERROR_TYPE_APPLY, // error occurred during apply. }; +enum BOOTSTRAPPER_FILES_IN_USE_TYPE +{ + BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI, // INSTALLMESSAGE_FILESINUSE + BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI_RM, // INSTALLMESSAGE_RMFILESINUSE + BOOTSTRAPPER_FILES_IN_USE_TYPE_NETFX, // MMIO_CLOSE_APPS +}; + enum BOOTSTRAPPER_RELATED_OPERATION { BOOTSTRAPPER_RELATED_OPERATION_NONE, @@ -874,6 +881,7 @@ struct BA_ONEXECUTEFILESINUSE_ARGS DWORD cFiles; LPCWSTR* rgwzFiles; int nRecommendation; + BOOTSTRAPPER_FILES_IN_USE_TYPE source; }; struct BA_ONEXECUTEFILESINUSE_RESULTS diff --git a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs index a78bf43f..b1fcaea4 100644 --- a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs @@ -1712,9 +1712,9 @@ namespace WixToolset.Mba.Core return args.HResult; } - int IBootstrapperApplication.OnExecuteFilesInUse(string wzPackageId, int cFiles, string[] rgwzFiles, Result nRecommendation, ref Result pResult) + int IBootstrapperApplication.OnExecuteFilesInUse(string wzPackageId, int cFiles, string[] rgwzFiles, Result nRecommendation, FilesInUseType source, ref Result pResult) { - ExecuteFilesInUseEventArgs args = new ExecuteFilesInUseEventArgs(wzPackageId, rgwzFiles, nRecommendation, pResult); + ExecuteFilesInUseEventArgs args = new ExecuteFilesInUseEventArgs(wzPackageId, rgwzFiles, nRecommendation, source, pResult); this.OnExecuteFilesInUse(args); pResult = args.Result; diff --git a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs index 55c9e74c..65169b25 100644 --- a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs +++ b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs @@ -1613,11 +1613,12 @@ namespace WixToolset.Mba.Core public class ExecuteFilesInUseEventArgs : ResultEventArgs { /// - public ExecuteFilesInUseEventArgs(string packageId, string[] files, Result recommendation, Result result) + public ExecuteFilesInUseEventArgs(string packageId, string[] files, Result recommendation, FilesInUseType source, Result result) : base(recommendation, result) { this.PackageId = packageId; this.Files = new ReadOnlyCollection(files ?? new string[] { }); + this.Source = source; } /// @@ -1629,6 +1630,11 @@ namespace WixToolset.Mba.Core /// Gets the list of files in use. /// public IList Files { get; private set; } + + /// + /// Gets the source of the message. + /// + public FilesInUseType Source { get; private set; } } /// diff --git a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs index c6a8fc14..3df54bde 100644 --- a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs @@ -777,12 +777,6 @@ namespace WixToolset.Mba.Core /// /// See . /// - /// - /// - /// - /// - /// - /// [PreserveSig] [return: MarshalAs(UnmanagedType.I4)] int OnExecuteFilesInUse( @@ -790,6 +784,7 @@ namespace WixToolset.Mba.Core [MarshalAs(UnmanagedType.U4)] int cFiles, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1, ArraySubType = UnmanagedType.LPWStr), In] string[] rgwzFiles, [MarshalAs(UnmanagedType.I4)] Result nRecommendation, + [MarshalAs(UnmanagedType.I4)] FilesInUseType source, [MarshalAs(UnmanagedType.I4)] ref Result pResult ); @@ -1445,6 +1440,25 @@ namespace WixToolset.Mba.Core Apply, }; + /// + /// Indicates the source of the FilesInUse message. + /// + public enum FilesInUseType + { + /// + /// Generated from INSTALLMESSAGE_FILESINUSE. + /// + Msi, + /// + /// Generated from INSTALLMESSAGE_RMFILESINUSE. + /// + MsiRm, + /// + /// Generated from MMIO_CLOSE_APPS. + /// + Netfx, + } + /// /// The calculated operation for the related bundle. /// diff --git a/src/api/burn/balutil/inc/BalBaseBAFunctions.h b/src/api/burn/balutil/inc/BalBaseBAFunctions.h index 8d9bddca..7f52f76e 100644 --- a/src/api/burn/balutil/inc/BalBaseBAFunctions.h +++ b/src/api/burn/balutil/inc/BalBaseBAFunctions.h @@ -583,6 +583,7 @@ public: // IBootstrapperApplication __in DWORD /*cFiles*/, __in_ecount_z(cFiles) LPCWSTR* /*rgwzFiles*/, __in int /*nRecommendation*/, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE /*source*/, __inout int* /*pResult*/ ) { diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h index 4d043dfe..5665fee3 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h @@ -738,6 +738,7 @@ public: // IBootstrapperApplication __in DWORD /*cFiles*/, __in_ecount_z(cFiles) LPCWSTR* /*rgwzFiles*/, __in int /*nRecommendation*/, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE /*source*/, __inout int* pResult ) { diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h index 59bfc1f8..2292cd64 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h @@ -438,7 +438,7 @@ static HRESULT BalBaseBAProcOnExecuteFilesInUse( __inout BA_ONEXECUTEFILESINUSE_RESULTS* pResults ) { - return pBA->OnExecuteFilesInUse(pArgs->wzPackageId, pArgs->cFiles, pArgs->rgwzFiles, pArgs->nRecommendation, &pResults->nResult); + return pBA->OnExecuteFilesInUse(pArgs->wzPackageId, pArgs->cFiles, pArgs->rgwzFiles, pArgs->nRecommendation, pArgs->source, &pResults->nResult); } static HRESULT BalBaseBAProcOnExecutePackageComplete( diff --git a/src/api/burn/balutil/inc/IBootstrapperApplication.h b/src/api/burn/balutil/inc/IBootstrapperApplication.h index be9b7b6e..2ba1f503 100644 --- a/src/api/burn/balutil/inc/IBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/IBootstrapperApplication.h @@ -488,27 +488,19 @@ DECLARE_INTERFACE_IID_(IBootstrapperApplication, IUnknown, "53C31D56-49C0-426B-A __inout int* pResult ) = 0; - // OnExecuteFilesInUse - called when the engine encounters files in use while - // executing a package. + // OnExecuteFilesInUse - called when the engine receives a files in use message + // while executing a package. // - // Return: - // IDOK instructs the engine to let the Restart Manager attempt to close the - // applications to avoid a restart. - // - // IDCANCEL instructs the engine to abort the execution and start rollback. - // - // IDIGNORE instructs the engine to ignore the running applications. A restart will be - // required. - // - // IDRETRY instructs the engine to check if the applications are still running again. - // - // IDNOACTION is equivalent to ignoring the running applications. A restart will be - // required. + // Return value depends on the source: + // BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI: https://docs.microsoft.com/en-us/windows/win32/msi/installvalidate-action + // BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI_RM: https://docs.microsoft.com/en-us/windows/win32/msi/using-restart-manager-with-an-external-ui- + // BOOTSTRAPPER_FILES_IN_USE_TYPE_NETFX: https://docs.microsoft.com/en-us/dotnet/framework/deployment/how-to-get-progress-from-the-dotnet-installer STDMETHOD(OnExecuteFilesInUse)( __in_z LPCWSTR wzPackageId, __in DWORD cFiles, __in_ecount_z(cFiles) LPCWSTR* rgwzFiles, __in int nRecommendation, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE source, __inout int* pResult ) = 0; diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp index dfaba3f2..9e552ee0 100644 --- a/src/burn/engine/apply.cpp +++ b/src/burn/engine/apply.cpp @@ -1931,8 +1931,8 @@ static HRESULT WINAPI AuthenticationRequired( APPLY_AUTHENTICATION_REQUIRED_DATA* authenticationData = reinterpret_cast(pData); - UserExperienceOnError(authenticationData->pUX, errorType, authenticationData->wzPackageOrContainerId, ERROR_ACCESS_DENIED, sczError, MB_RETRYTRYAGAIN, 0, NULL, &nResult); // ignore return value; - nResult = UserExperienceCheckExecuteResult(authenticationData->pUX, FALSE, MB_RETRYTRYAGAIN, nResult); + UserExperienceOnError(authenticationData->pUX, errorType, authenticationData->wzPackageOrContainerId, ERROR_ACCESS_DENIED, sczError, MB_RETRYCANCEL, 0, NULL, &nResult); // ignore return value; + nResult = UserExperienceCheckExecuteResult(authenticationData->pUX, FALSE, BURN_MB_RETRYTRYAGAIN, nResult); if (IDTRYAGAIN == nResult && authenticationData->pUX->hwndApply) { er = ::InternetErrorDlg(authenticationData->pUX->hwndApply, hUrl, ERROR_INTERNET_INCORRECT_PASSWORD, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, NULL); @@ -2495,10 +2495,10 @@ static HRESULT ExecuteExePackage( ExitOnRootFailure(hr, "BA aborted execute EXE package begin."); message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + message.dwUIHint = MB_OKCANCEL; message.progress.dwPercentage = fRollback ? 100 : 0; nResult = GenericExecuteMessageHandler(&message, pContext); - hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwUIHint, nResult); ExitOnRootFailure(hr, "BA aborted EXE progress."); fExecuted = TRUE; @@ -2516,10 +2516,10 @@ static HRESULT ExecuteExePackage( } message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + message.dwUIHint = MB_OKCANCEL; message.progress.dwPercentage = fRollback ? 0 : 100; nResult = GenericExecuteMessageHandler(&message, pContext); - hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwUIHint, nResult); ExitOnRootFailure(hr, "BA aborted EXE progress."); pContext->cExecutedPackages += fRollback ? -1 : 1; @@ -2712,10 +2712,10 @@ static HRESULT ExecuteMsuPackage( ExitOnRootFailure(hr, "BA aborted execute MSU package begin."); message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + message.dwUIHint = MB_OKCANCEL; message.progress.dwPercentage = fRollback ? 100 : 0; nResult = GenericExecuteMessageHandler(&message, pContext); - hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwUIHint, nResult); ExitOnRootFailure(hr, "BA aborted MSU progress."); fExecuted = TRUE; @@ -2733,10 +2733,10 @@ static HRESULT ExecuteMsuPackage( } message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + message.dwUIHint = MB_OKCANCEL; message.progress.dwPercentage = fRollback ? 0 : 100; nResult = GenericExecuteMessageHandler(&message, pContext); - hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwUIHint, nResult); ExitOnRootFailure(hr, "BA aborted MSU progress."); pContext->cExecutedPackages += fRollback ? -1 : 1; @@ -3047,6 +3047,7 @@ static int GenericExecuteMessageHandler( ) { BURN_EXECUTE_CONTEXT* pContext = (BURN_EXECUTE_CONTEXT*)pvContext; + DWORD dwAllowedResults = pMessage->dwUIHint & MB_TYPEMASK; int nResult = IDNOACTION; switch (pMessage->type) @@ -3059,15 +3060,16 @@ static int GenericExecuteMessageHandler( break; case GENERIC_EXECUTE_MESSAGE_ERROR: - UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwAllowedResults, 0, NULL, &nResult); // ignore return value. + UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, 0, NULL, &nResult); // ignore return value. break; - case GENERIC_EXECUTE_MESSAGE_FILES_IN_USE: - UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->filesInUse.cFiles, pMessage->filesInUse.rgwzFiles, &nResult); // ignore return value. + case GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE: + UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->filesInUse.cFiles, pMessage->filesInUse.rgwzFiles, BOOTSTRAPPER_FILES_IN_USE_TYPE_NETFX, &nResult); // ignore return value. + dwAllowedResults = BURN_MB_NETFX_FILES_IN_USE; break; } - nResult = UserExperienceCheckExecuteResult(pContext->pUX, pContext->fRollback, pMessage->dwAllowedResults, nResult); + nResult = UserExperienceCheckExecuteResult(pContext->pUX, pContext->fRollback, dwAllowedResults, nResult); return nResult; } @@ -3077,7 +3079,9 @@ static int MsiExecuteMessageHandler( ) { BURN_EXECUTE_CONTEXT* pContext = (BURN_EXECUTE_CONTEXT*)pvContext; + DWORD dwAllowedResults = pMessage->dwUIHint & MB_TYPEMASK; int nResult = IDNOACTION; + BOOL fRestartManager = FALSE; switch (pMessage->type) { @@ -3090,20 +3094,24 @@ static int MsiExecuteMessageHandler( case WIU_MSI_EXECUTE_MESSAGE_ERROR: nResult = pMessage->nResultRecommendation; - UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwAllowedResults, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. + UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. break; case WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE: nResult = pMessage->nResultRecommendation; - UserExperienceOnExecuteMsiMessage(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiMessage.mt, pMessage->dwAllowedResults, pMessage->msiMessage.wzMessage, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. + UserExperienceOnExecuteMsiMessage(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiMessage.mt, pMessage->dwUIHint, pMessage->msiMessage.wzMessage, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. break; + case WIU_MSI_EXECUTE_MESSAGE_MSI_RM_FILES_IN_USE: + fRestartManager = TRUE; + __fallthrough; case WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE: - UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiFilesInUse.cFiles, pMessage->msiFilesInUse.rgwzFiles, &nResult); // ignore return value. + UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiFilesInUse.cFiles, pMessage->msiFilesInUse.rgwzFiles, fRestartManager ? BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI_RM : BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI, &nResult); // ignore return value. + dwAllowedResults = fRestartManager ? BURN_MB_MSI_RM_FILES_IN_USE : BURN_MB_MSI_FILES_IN_USE; break; } - nResult = UserExperienceCheckExecuteResult(pContext->pUX, pContext->fRollback, pMessage->dwAllowedResults, nResult); + nResult = UserExperienceCheckExecuteResult(pContext->pUX, pContext->fRollback, dwAllowedResults, nResult); return nResult; } diff --git a/src/burn/engine/apply.h b/src/burn/engine/apply.h index 45270f92..1717a71a 100644 --- a/src/burn/engine/apply.h +++ b/src/burn/engine/apply.h @@ -12,7 +12,7 @@ enum GENERIC_EXECUTE_MESSAGE_TYPE GENERIC_EXECUTE_MESSAGE_NONE, GENERIC_EXECUTE_MESSAGE_ERROR, GENERIC_EXECUTE_MESSAGE_PROGRESS, - GENERIC_EXECUTE_MESSAGE_FILES_IN_USE, + GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE, }; typedef struct _APPLY_AUTHENTICATION_REQUIRED_DATA @@ -25,7 +25,7 @@ typedef struct _APPLY_AUTHENTICATION_REQUIRED_DATA typedef struct _GENERIC_EXECUTE_MESSAGE { GENERIC_EXECUTE_MESSAGE_TYPE type; - DWORD dwAllowedResults; + DWORD dwUIHint; union { diff --git a/src/burn/engine/detect.cpp b/src/burn/engine/detect.cpp index 5f68a240..4eda240e 100644 --- a/src/burn/engine/detect.cpp +++ b/src/burn/engine/detect.cpp @@ -306,8 +306,8 @@ static HRESULT WINAPI AuthenticationRequired( hr = StrAllocFromError(&sczError, HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED), NULL); ExitOnFailure(hr, "Failed to allocation error string."); - UserExperienceOnError(pAuthenticationData->pUX, errorType, pAuthenticationData->wzPackageOrContainerId, ERROR_ACCESS_DENIED, sczError, MB_RETRYTRYAGAIN, 0, NULL, &nResult); // ignore return value. - nResult = UserExperienceCheckExecuteResult(pAuthenticationData->pUX, FALSE, MB_RETRYTRYAGAIN, nResult); + UserExperienceOnError(pAuthenticationData->pUX, errorType, pAuthenticationData->wzPackageOrContainerId, ERROR_ACCESS_DENIED, sczError, MB_RETRYCANCEL, 0, NULL, &nResult); // ignore return value. + nResult = UserExperienceCheckExecuteResult(pAuthenticationData->pUX, FALSE, BURN_MB_RETRYTRYAGAIN, nResult); if (IDTRYAGAIN == nResult && pAuthenticationData->pUX->hwndDetect) { er = ::InternetErrorDlg(pAuthenticationData->pUX->hwndDetect, hUrl, ERROR_INTERNET_INCORRECT_PASSWORD, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, NULL); diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp index 12c9f296..6c4a775f 100644 --- a/src/burn/engine/elevation.cpp +++ b/src/burn/engine/elevation.cpp @@ -42,7 +42,8 @@ typedef enum _BURN_ELEVATION_MESSAGE_TYPE BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE, - BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_FILES_IN_USE, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_NETFX_FILES_IN_USE, BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE_PROCESSID, BURN_ELEVATION_MESSAGE_TYPE_PROGRESS_ROUTINE, } BURN_ELEVATION_MESSAGE_TYPE; @@ -1618,7 +1619,7 @@ static HRESULT ProcessGenericExecuteMessages( LPWSTR* rgwzFiles = NULL; GENERIC_EXECUTE_MESSAGE message = { }; - hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.dwAllowedResults); + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.dwUIHint); ExitOnFailure(hr, "Failed to allowed results."); // Process the message. @@ -1645,8 +1646,8 @@ static HRESULT ProcessGenericExecuteMessages( message.error.wzMessage = sczMessage; break; - case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE: - message.type = GENERIC_EXECUTE_MESSAGE_FILES_IN_USE; + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_NETFX_FILES_IN_USE: + message.type = GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE; // read message parameters hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &cFiles); @@ -1701,6 +1702,7 @@ static HRESULT ProcessMsiPackageMessages( LPWSTR* rgwzMsiData = NULL; BURN_ELEVATION_MSI_MESSAGE_CONTEXT* pContext = static_cast(pvContext); LPWSTR sczMessage = NULL; + BOOL fRestartManager = FALSE; // Read MSI extended message data. hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &cMsiData); @@ -1721,7 +1723,7 @@ static HRESULT ProcessMsiPackageMessages( message.rgwzData = (LPCWSTR*)rgwzMsiData; } - hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.dwAllowedResults); + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.dwUIHint); ExitOnFailure(hr, "Failed to read UI flags."); // Process the rest of the message. @@ -1759,8 +1761,11 @@ static HRESULT ProcessMsiPackageMessages( message.msiMessage.wzMessage = sczMessage; break; - case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE: - message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE; + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_FILES_IN_USE: + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, (DWORD*)&fRestartManager); + ExitOnFailure(hr, "Failed to read fRestartManager."); + + message.type = fRestartManager ? WIU_MSI_EXECUTE_MESSAGE_MSI_RM_FILES_IN_USE : WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE; message.msiFilesInUse.cFiles = cMsiData; message.msiFilesInUse.rgwzFiles = (LPCWSTR*)rgwzMsiData; break; @@ -3006,7 +3011,7 @@ static int GenericExecuteMessageHandler( SIZE_T cbData = 0; DWORD dwMessage = 0; - hr = BuffWriteNumber(&pbData, &cbData, pMessage->dwAllowedResults); + hr = BuffWriteNumber(&pbData, &cbData, pMessage->dwUIHint); ExitOnFailure(hr, "Failed to write UI flags."); switch(pMessage->type) @@ -3030,7 +3035,7 @@ static int GenericExecuteMessageHandler( dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR; break; - case GENERIC_EXECUTE_MESSAGE_FILES_IN_USE: + case GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE: hr = BuffWriteNumber(&pbData, &cbData, pMessage->filesInUse.cFiles); ExitOnFailure(hr, "Failed to count of files in use to message buffer."); @@ -3040,7 +3045,7 @@ static int GenericExecuteMessageHandler( ExitOnFailure(hr, "Failed to write file in use to message buffer."); } - dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE; + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_NETFX_FILES_IN_USE; break; } @@ -3065,6 +3070,7 @@ static int MsiExecuteMessageHandler( BYTE* pbData = NULL; SIZE_T cbData = 0; DWORD dwMessage = 0; + BOOL fRestartManager = FALSE; // Always send any extra data via the struct first. hr = BuffWriteNumber(&pbData, &cbData, pMessage->cData); @@ -3076,7 +3082,7 @@ static int MsiExecuteMessageHandler( ExitOnFailure(hr, "Failed to write MSI data to message buffer."); } - hr = BuffWriteNumber(&pbData, &cbData, pMessage->dwAllowedResults); + hr = BuffWriteNumber(&pbData, &cbData, pMessage->dwUIHint); ExitOnFailure(hr, "Failed to write UI flags."); switch (pMessage->type) @@ -3114,11 +3120,15 @@ static int MsiExecuteMessageHandler( dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE; break; + case WIU_MSI_EXECUTE_MESSAGE_MSI_RM_FILES_IN_USE: + fRestartManager = TRUE; + __fallthrough; case WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE: - // NOTE: we do not serialize other message data here because all the "files in use" are in the data above. + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)fRestartManager); + ExitOnFailure(hr, "Failed to write fRestartManager to message buffer."); // set message id - dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE; + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_FILES_IN_USE; break; default: diff --git a/src/burn/engine/embedded.cpp b/src/burn/engine/embedded.cpp index 03898ebd..1c295d59 100644 --- a/src/burn/engine/embedded.cpp +++ b/src/burn/engine/embedded.cpp @@ -161,7 +161,7 @@ static HRESULT OnEmbeddedErrorMessage( message.error.wzMessage = sczMessage; - hr = BuffReadNumber(pbData, cbData, &iData, &message.dwAllowedResults); + hr = BuffReadNumber(pbData, cbData, &iData, &message.dwUIHint); ExitOnFailure(hr, "Failed to read UI hint from buffer."); *pdwResult = (DWORD)pfnMessageHandler(&message, pvContext); @@ -185,7 +185,7 @@ static HRESULT OnEmbeddedProgress( GENERIC_EXECUTE_MESSAGE message = { }; message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + message.dwUIHint = MB_OKCANCEL; hr = BuffReadNumber(pbData, cbData, &iData, &message.progress.dwPercentage); ExitOnFailure(hr, "Failed to read progress from buffer."); diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp index 67da3bdd..b728f599 100644 --- a/src/burn/engine/exeengine.cpp +++ b/src/burn/engine/exeengine.cpp @@ -533,7 +533,7 @@ extern "C" HRESULT ExeEngineExecutePackage( do { message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + 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); diff --git a/src/burn/engine/msuengine.cpp b/src/burn/engine/msuengine.cpp index 1ce2dd11..693bb64b 100644 --- a/src/burn/engine/msuengine.cpp +++ b/src/burn/engine/msuengine.cpp @@ -339,7 +339,7 @@ extern "C" HRESULT MsuEngineExecutePackage( do { message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + 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); diff --git a/src/burn/engine/netfxchainer.cpp b/src/burn/engine/netfxchainer.cpp index 4e7a7720..af4f03f5 100644 --- a/src/burn/engine/netfxchainer.cpp +++ b/src/burn/engine/netfxchainer.cpp @@ -233,8 +233,8 @@ static HRESULT OnNetFxFilesInUse( } // send message - message.type = GENERIC_EXECUTE_MESSAGE_FILES_IN_USE; - message.dwAllowedResults = MB_ABORTRETRYIGNORE; + message.type = GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE; + message.dwUIHint = MB_ABORTRETRYIGNORE; message.filesInUse.cFiles = cFiles; message.filesInUse.rgwzFiles = (LPCWSTR*)rgwzFiles; dwResponse = (DWORD)pfnMessageHandler(&message, pvContext); @@ -259,7 +259,7 @@ static HRESULT OnNetFxProgress( // send message message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + message.dwUIHint = MB_OKCANCEL; message.progress.dwPercentage = 100 * (DWORD)bProgress / BYTE_MAX; dwResponse = (DWORD)pfnMessageHandler(&message, pvContext); @@ -283,7 +283,7 @@ static HRESULT OnNetFxError( // send message message.type = GENERIC_EXECUTE_MESSAGE_ERROR; - message.dwAllowedResults = MB_OK; + message.dwUIHint = MB_OK; message.error.dwErrorCode = hrError; message.error.wzMessage = NULL; dwResponse = (DWORD)pfnMessageHandler(&message, pvContext); diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp index a6d670ea..7cc6f049 100644 --- a/src/burn/engine/userexperience.cpp +++ b/src/burn/engine/userexperience.cpp @@ -1494,6 +1494,7 @@ EXTERN_C BAAPI UserExperienceOnExecuteFilesInUse( __in_z LPCWSTR wzPackageId, __in DWORD cFiles, __in_ecount_z_opt(cFiles) LPCWSTR* rgwzFiles, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE source, __inout int* pnResult ) { @@ -1506,6 +1507,7 @@ EXTERN_C BAAPI UserExperienceOnExecuteFilesInUse( args.cFiles = cFiles; args.rgwzFiles = rgwzFiles; args.nRecommendation = *pnResult; + args.source = source; results.cbSize = sizeof(results); results.nResult = *pnResult; @@ -2492,13 +2494,12 @@ static int FilterResult( __in int nResult ) { - DWORD dwFilteredAllowedResults = dwAllowedResults & MB_TYPEMASK; if (IDNOACTION == nResult || IDERROR == nResult) // do nothing and errors pass through. { } else { - switch (dwFilteredAllowedResults) + switch (dwAllowedResults) { case MB_OK: nResult = IDOK; @@ -2606,7 +2607,28 @@ static int FilterResult( } break; - case WIU_MB_OKIGNORECANCELRETRY: // custom Windows Installer utility return code. + case BURN_MB_MSI_FILES_IN_USE: + // https://docs.microsoft.com/en-us/windows/win32/msi/installvalidate-action + if (IDRETRY == nResult || IDTRYAGAIN == nResult) + { + nResult = IDRETRY; + } + else if (IDCANCEL == nResult || IDABORT == nResult) + { + nResult = IDCANCEL; + } + else if (IDCONTINUE == nResult || IDIGNORE == nResult) + { + nResult = IDIGNORE; + } + else + { + nResult = IDNOACTION; + } + break; + + case BURN_MB_MSI_RM_FILES_IN_USE: + // https://docs.microsoft.com/en-us/windows/win32/msi/using-restart-manager-with-an-external-ui- if (IDOK == nResult || IDYES == nResult) { nResult = IDOK; @@ -2615,11 +2637,15 @@ static int FilterResult( { nResult = IDIGNORE; } + else if (IDNO == nResult) + { + nResult = IDNO; + } else if (IDCANCEL == nResult || IDABORT == nResult) { nResult = IDCANCEL; } - else if (IDRETRY == nResult || IDTRYAGAIN == nResult || IDNO == nResult) + else if (IDRETRY == nResult || IDTRYAGAIN == nResult) { nResult = IDRETRY; } @@ -2629,13 +2655,33 @@ static int FilterResult( } break; - case MB_RETRYTRYAGAIN: // custom return code. + case BURN_MB_RETRYTRYAGAIN: // custom return code. if (IDRETRY != nResult && IDTRYAGAIN != nResult) { nResult = IDNOACTION; } break; + case BURN_MB_NETFX_FILES_IN_USE: + // https://docs.microsoft.com/en-us/dotnet/framework/deployment/how-to-get-progress-from-the-dotnet-installer + if (IDOK == nResult || IDYES == nResult) + { + nResult = IDYES; + } + else if (IDRETRY == nResult || IDTRYAGAIN == nResult) + { + nResult = IDRETRY; + } + else if (IDCANCEL == nResult || IDABORT == nResult) + { + nResult = IDCANCEL; + } + else + { + nResult = IDNO; + } + break; + default: AssertSz(FALSE, "Unknown allowed results."); break; diff --git a/src/burn/engine/userexperience.h b/src/burn/engine/userexperience.h index f7ac962c..75723afa 100644 --- a/src/burn/engine/userexperience.h +++ b/src/burn/engine/userexperience.h @@ -10,7 +10,10 @@ extern "C" { // constants -const DWORD MB_RETRYTRYAGAIN = 0xF; +const DWORD BURN_MB_RETRYTRYAGAIN = 0x10; +const DWORD BURN_MB_MSI_FILES_IN_USE = 0x11; +const DWORD BURN_MB_MSI_RM_FILES_IN_USE = 0x12; +const DWORD BURN_MB_NETFX_FILES_IN_USE = 0x13; // structs @@ -356,6 +359,7 @@ BAAPI UserExperienceOnExecuteFilesInUse( __in_z LPCWSTR wzPackageId, __in DWORD cFiles, __in_ecount_z_opt(cFiles) LPCWSTR* rgwzFiles, + __in BOOTSTRAPPER_FILES_IN_USE_TYPE source, __inout int* pnResult ); BAAPI UserExperienceOnExecuteMsiMessage( diff --git a/src/internal/SetBuildNumber/Directory.Packages.props.pp b/src/internal/SetBuildNumber/Directory.Packages.props.pp index 4743fb2e..86f9d8cb 100644 --- a/src/internal/SetBuildNumber/Directory.Packages.props.pp +++ b/src/internal/SetBuildNumber/Directory.Packages.props.pp @@ -29,6 +29,7 @@ + diff --git a/src/libs/dutil/WixToolset.DUtil/inc/wiutil.h b/src/libs/dutil/WixToolset.DUtil/inc/wiutil.h index 9c2de209..0ddcd893 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/wiutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/wiutil.h @@ -9,7 +9,6 @@ extern "C" { // constants #define IDNOACTION 0 -#define WIU_MB_OKIGNORECANCELRETRY 0xE #define MAX_DARWIN_KEY 73 #define MAX_DARWIN_COLUMN 255 @@ -37,6 +36,7 @@ typedef enum WIU_MSI_EXECUTE_MESSAGE_TYPE WIU_MSI_EXECUTE_MESSAGE_ERROR, WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE, WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE, + WIU_MSI_EXECUTE_MESSAGE_MSI_RM_FILES_IN_USE, } WIU_MSI_EXECUTE_MESSAGE_TYPE; @@ -45,7 +45,7 @@ typedef enum WIU_MSI_EXECUTE_MESSAGE_TYPE typedef struct _WIU_MSI_EXECUTE_MESSAGE { WIU_MSI_EXECUTE_MESSAGE_TYPE type; - DWORD dwAllowedResults; + DWORD dwUIHint; DWORD cData; LPCWSTR* rgwzData; diff --git a/src/libs/dutil/WixToolset.DUtil/wiutil.cpp b/src/libs/dutil/WixToolset.DUtil/wiutil.cpp index 7414ac42..da7cffe7 100644 --- a/src/libs/dutil/WixToolset.DUtil/wiutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/wiutil.cpp @@ -113,6 +113,7 @@ static INT SendErrorMessage( ); static INT SendFilesInUseMessage( __in WIU_MSI_EXECUTE_CONTEXT* pContext, + __in UINT uiFlags, __in_opt MSIHANDLE hRecord, __in BOOL fRestartManagerRequest ); @@ -1161,7 +1162,7 @@ Trace(REPORT_STANDARD, "MSI install[%x]: %ls", pContext->dwCurrentProgressIndex, case INSTALLMESSAGE_FILESINUSE: case INSTALLMESSAGE_RMFILESINUSE: - nResult = SendFilesInUseMessage(pContext, hRecord, INSTALLMESSAGE_RMFILESINUSE == mt); + nResult = SendFilesInUseMessage(pContext, uiFlags, hRecord, INSTALLMESSAGE_RMFILESINUSE == mt); break; /* @@ -1401,7 +1402,7 @@ static INT SendMsiMessage( InitializeMessageData(hRecord, &rgsczData, &cData); message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE; - message.dwAllowedResults = uiFlags; + message.dwUIHint = uiFlags; message.cData = cData; message.rgwzData = (LPCWSTR*)rgsczData; message.msiMessage.mt = mt; @@ -1445,7 +1446,7 @@ static INT SendErrorMessage( InitializeMessageData(hRecord, &rgsczData, &cData); message.type = WIU_MSI_EXECUTE_MESSAGE_ERROR; - message.dwAllowedResults = uiFlags; + message.dwUIHint = uiFlags; message.nResultRecommendation = nResult; message.cData = cData; message.rgwzData = (LPCWSTR*)rgsczData; @@ -1459,8 +1460,9 @@ static INT SendErrorMessage( static INT SendFilesInUseMessage( __in WIU_MSI_EXECUTE_CONTEXT* pContext, + __in UINT uiFlags, __in_opt MSIHANDLE hRecord, - __in BOOL /*fRestartManagerRequest*/ + __in BOOL fRestartManagerRequest ) { INT nResult = IDNOACTION; @@ -1470,8 +1472,8 @@ static INT SendFilesInUseMessage( InitializeMessageData(hRecord, &rgsczData, &cData); - message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE; - message.dwAllowedResults = WIU_MB_OKIGNORECANCELRETRY; + message.type = fRestartManagerRequest ? WIU_MSI_EXECUTE_MESSAGE_MSI_RM_FILES_IN_USE : WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE; + message.dwUIHint = uiFlags; message.cData = cData; message.rgwzData = (LPCWSTR*)rgsczData; message.msiFilesInUse.cFiles = message.cData; // point the files in use information to the message record information. @@ -1527,7 +1529,7 @@ static INT SendProgressUpdate( #endif message.type = WIU_MSI_EXECUTE_MESSAGE_PROGRESS; - message.dwAllowedResults = MB_OKCANCEL; + message.dwUIHint = MB_OKCANCEL; message.progress.dwPercentage = dwPercentage; nResult = pContext->pfnMessageHandler(&message, pContext->pvContext); diff --git a/src/test/burn/TestBA/TestBA.cs b/src/test/burn/TestBA/TestBA.cs index 5c70253d..5ef26253 100644 --- a/src/test/burn/TestBA/TestBA.cs +++ b/src/test/burn/TestBA/TestBA.cs @@ -399,7 +399,7 @@ namespace WixToolset.Test.BA protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args) { - this.Log("OnExecuteFilesInUse() - package: {0}, retries remaining: {1}, data: {2}", args.PackageId, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray())); + this.Log("OnExecuteFilesInUse() - package: {0}, source: {1}, retries remaining: {2}, data: {3}", args.PackageId, args.Source, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray())); if (this.retryExecuteFilesInUse > 0) { diff --git a/src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wixproj b/src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wixproj new file mode 100644 index 00000000..c71410c2 --- /dev/null +++ b/src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wixproj @@ -0,0 +1,18 @@ + + + + Bundle + {6A348108-8ACE-4D13-A352-D8F76785BFE4} + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wxs b/src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wxs new file mode 100644 index 00000000..bd164a29 --- /dev/null +++ b/src/test/burn/TestData/FilesInUseTests/BundleA/BundleA.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wixproj b/src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wixproj new file mode 100644 index 00000000..57825f7b --- /dev/null +++ b/src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wixproj @@ -0,0 +1,13 @@ + + + + true + {C94C8FC9-1347-44CE-B1FB-0A6196928921} + + + + + + + + \ No newline at end of file diff --git a/src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wxs b/src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wxs new file mode 100644 index 00000000..a96c2a11 --- /dev/null +++ b/src/test/burn/TestData/FilesInUseTests/PackageA/PackageA.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/burn/TestData/FilesInUseTests/PackageA/license.txt b/src/test/burn/TestData/FilesInUseTests/PackageA/license.txt new file mode 100644 index 00000000..2e65efe2 --- /dev/null +++ b/src/test/burn/TestData/FilesInUseTests/PackageA/license.txt @@ -0,0 +1 @@ +a \ No newline at end of file diff --git a/src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs new file mode 100644 index 00000000..6ad68d22 --- /dev/null +++ b/src/test/burn/WixToolsetTest.BurnE2E/FilesInUseTests.cs @@ -0,0 +1,38 @@ +// 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. + +namespace WixToolsetTest.BurnE2E +{ + using System.IO; + using WixTestTools; + using Xunit; + using Xunit.Abstractions; + + public class FilesInUseTests : BurnE2ETests + { + public FilesInUseTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + + [Fact] + public void CanCancelInstallAfterRetryingLockedFile() + { + var packageA = this.CreatePackageInstaller("PackageA"); + var bundleA = this.CreateBundleInstaller("BundleA"); + var testBAController = this.CreateTestBAController(); + + testBAController.SetPackageRetryExecuteFilesInUse("PackageA", 1); + + packageA.VerifyInstalled(false); + + // Lock the file that will be installed. + string targetInstallFile = packageA.GetInstalledFilePath("Package.wxs"); + Directory.CreateDirectory(Path.GetDirectoryName(targetInstallFile)); + using (FileStream lockTargetFile = new FileStream(targetInstallFile, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose)) + { + bundleA.Install(expectedExitCode: (int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_USEREXIT); + } + + bundleA.VerifyUnregisteredAndRemovedFromPackageCache(); + + packageA.VerifyInstalled(false); + } + } +} diff --git a/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs b/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs index 6e4fe6c6..d2e8a1ca 100644 --- a/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs +++ b/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs @@ -117,6 +117,16 @@ namespace WixToolsetTest.BurnE2E this.SetPackageState(packageId, "CancelOnProgressAtProgress", cancelPoint.HasValue ? cancelPoint.ToString() : null); } + /// + /// Retries the files in use one or more times before canceling. + /// + /// Package identity. + /// Sets or removes the retry count on a package's file in use message. + public void SetPackageRetryExecuteFilesInUse(string packageId, int? retryCount) + { + this.SetPackageState(packageId, "RetryExecuteFilesInUse", retryCount.HasValue ? retryCount.ToString() : null); + } + /// /// Sets the requested state for a package that the TestBA will return to the engine during plan. /// -- cgit v1.2.3-55-g6feb