diff --git a/README.md b/README.md index 8bb828489..91b38d3d7 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,5 @@ This repository contains the PanelSwWix4: A custom WiX Toolset codebase - [4889](https://github.com/wixtoolset/issues/issues/4889): Support custom container compressions using bundle extensions - Not overwriting log files when retrying to execute a package - Support sending custom messages on embedded pipe -- Best effort to log premature termination of companion process \ No newline at end of file +- Best effort to log premature termination of companion process +- Monitor UX folder and re-extract any UX payloads that were deleted for any reason diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h index e0710f27c..6262910c8 100644 --- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h +++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h @@ -229,6 +229,7 @@ enum BOOTSTRAPPER_APPLICATION_MESSAGE BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE, + BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED, }; enum BOOTSTRAPPER_APPLYCOMPLETE_ACTION @@ -268,6 +269,13 @@ enum BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION_ACQUIRE, }; +enum BOOTSTRAPPER_UXPAYLOADDELETED_ACTION +{ + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_NONE, + // Instructs the engine to try to reacquire the payload. + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_REACQUIRE, +}; + enum BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION { BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_NONE, @@ -591,6 +599,20 @@ struct BA_ONCACHEPACKAGENONVITALVALIDATIONFAILURE_RESULTS BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION action; }; +struct BA_ONUXPAYLOADDELETED_ARGS +{ + DWORD cbSize; + LPCWSTR wzPayloadId; + LPCWSTR wzPayloadPath; + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION recommendation; +}; + +struct BA_ONUXPAYLOADDELETED_RESULTS +{ + DWORD cbSize; + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action; +}; + struct BA_ONCACHEPAYLOADEXTRACTBEGIN_ARGS { DWORD cbSize; diff --git a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs index a69fbc724..122a6fd4c 100644 --- a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs @@ -289,6 +289,9 @@ protected BootstrapperApplication(IEngine engine) /// public event EventHandler CachePackageNonVitalValidationFailure; + /// + public event EventHandler UxPayloadDeleted; + /// /// Entry point that is called when the bootstrapper application is ready to run. /// @@ -1426,6 +1429,19 @@ protected virtual void OnCachePackageNonVitalValidationFailure(CachePackageNonVi } } + /// + /// Called by the engine, raises the event. + /// + /// Additional arguments for this event. + protected virtual void OnUxPayloadDeleted(UxPayloadDeletedEventArgs args) + { + EventHandler handler = this.UxPayloadDeleted; + if (null != handler) + { + handler(this, args); + } + } + #region IBootstrapperApplication Members int IBootstrapperApplication.BAProc(int message, IntPtr pvArgs, IntPtr pvResults, IntPtr pvContext) @@ -2212,6 +2228,15 @@ int IBootstrapperApplication.OnCachePackageNonVitalValidationFailure(string wzPa return args.HResult; } + int IBootstrapperApplication.OnUxPayloadDeleted(string wzPayloadId, string wzPayloadPath, BOOTSTRAPPER_UXPAYLOADDELETED_ACTION recommendation, ref BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action) + { + UxPayloadDeletedEventArgs args = new UxPayloadDeletedEventArgs(wzPayloadId, wzPayloadPath, recommendation, action); + this.OnUxPayloadDeleted(args); + + action = 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 83a1cf669..7cf37b8f6 100644 --- a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs +++ b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs @@ -2831,4 +2831,32 @@ public CachePackageNonVitalValidationFailureEventArgs(string packageId, int hrSt /// public string PackageId { get; private set; } } + + /// + /// Event arguments for + /// + [Serializable] + public class UxPayloadDeletedEventArgs : ActionEventArgs + { + /// + /// This class is for events raised by the engine. + /// It is not intended to be instantiated by user code. + /// + public UxPayloadDeletedEventArgs(string wzPayloadId, string wzPayloadPath, BOOTSTRAPPER_UXPAYLOADDELETED_ACTION recommendation, BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action) + : base(0, recommendation, action) + { + this.PayloadId = wzPayloadId; + this.PayloadPath = wzPayloadPath; + } + + /// + /// Gets the identity of the missing UX payload. + /// + public string PayloadId { get; private set; } + + /// + /// Gets the relative path of the missing UX payload. + /// + public string PayloadPath { get; private set; } + } } diff --git a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs index 318a4d338..fe2749f94 100644 --- a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs @@ -1054,6 +1054,18 @@ int OnCachePackageNonVitalValidationFailure( BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION recommendation, ref BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION action ); + + /// + /// See . + /// + [PreserveSig] + [return: MarshalAs(UnmanagedType.I4)] + int OnUxPayloadDeleted( + [MarshalAs(UnmanagedType.LPWStr)] string wzPayloadId, + [MarshalAs(UnmanagedType.LPWStr)] string wzPayloadPath, + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION recommendation, + ref BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action + ); } /// @@ -1765,6 +1777,22 @@ public enum BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION Acquire, } + /// + /// The available actions for + /// + public enum BOOTSTRAPPER_UXPAYLOADDELETED_ACTION + { + /// + /// Instructs the engine to not take any special action. + /// + None, + + /// + /// Instructs the engine to try to reacquire the UX payload. + /// + Reacquire, + } + /// /// The available actions for . /// diff --git a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs index ee6e647ae..cc837cb32 100644 --- a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs @@ -98,6 +98,12 @@ public interface IDefaultBootstrapperApplication : IBootstrapperApplication /// event EventHandler CachePackageNonVitalValidationFailure; + /// + /// Fired when the engine detects that a UX payloads was deleted. This may happen for example, when a cleaning tool deletes files from %TEMP% folder. + /// Note that, the event might be fired multiple times for each missing payload. + /// + event EventHandler UxPayloadDeleted; + /// /// Fired when the engine begins the extraction of the payload from the container. /// diff --git a/src/api/burn/balutil/inc/BAFunctions.h b/src/api/burn/balutil/inc/BAFunctions.h index be4528eb8..b841a849b 100644 --- a/src/api/burn/balutil/inc/BAFunctions.h +++ b/src/api/burn/balutil/inc/BAFunctions.h @@ -95,6 +95,7 @@ enum BA_FUNCTIONS_MESSAGE BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL = BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, BA_FUNCTIONS_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE, BA_FUNCTIONS_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE, + BA_FUNCTIONS_MESSAGE_ONUXPAYLOADDELETED = BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED, 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 ba197952b..177ae3a57 100644 --- a/src/api/burn/balutil/inc/BalBaseBAFunctions.h +++ b/src/api/burn/balutil/inc/BalBaseBAFunctions.h @@ -933,6 +933,16 @@ class CBalBaseBAFunctions : public IBAFunctions return S_OK; } + virtual STDMETHODIMP OnUxPayloadDeleted( + __in_z LPCWSTR /*wzPayloadId*/, + __in_z LPCWSTR /*wzPayloadPath*/, + __in BOOTSTRAPPER_UXPAYLOADDELETED_ACTION /*recommendation*/, + __inout BOOTSTRAPPER_UXPAYLOADDELETED_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 b56b8ecc3..be7e24b88 100644 --- a/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h +++ b/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h @@ -166,6 +166,7 @@ static HRESULT WINAPI BalBaseBAFunctionsProc( case BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL: case BA_FUNCTIONS_MESSAGE_ONDETECTRELATEDBUNDLEPACKAGE: case BA_FUNCTIONS_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE: + case BA_FUNCTIONS_MESSAGE_ONUXPAYLOADDELETED: 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 a9cb8e940..cb1afa167 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h @@ -1155,6 +1155,16 @@ class CBalBaseBootstrapperApplication : public IBootstrapperApplication return S_OK; } + virtual STDMETHODIMP OnUxPayloadDeleted( + __in_z LPCWSTR /*wzPayloadId*/, + __in_z LPCWSTR /*wzPayloadPath*/, + __in BOOTSTRAPPER_UXPAYLOADDELETED_ACTION /*recommendation*/, + __inout BOOTSTRAPPER_UXPAYLOADDELETED_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 18e6b8662..0c3c7759c 100644 --- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h +++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h @@ -783,6 +783,15 @@ static HRESULT BalBaseBAProcOnCachePackageNonVitalValidationFailure( return pBA->OnCachePackageNonVitalValidationFailure(pArgs->wzPackageId, pArgs->hrStatus, pArgs->recommendation, &pResults->action); } +static HRESULT BalBaseBAProcOnUxPayloadDeleted( + __in IBootstrapperApplication* pBA, + __in BA_ONUXPAYLOADDELETED_ARGS* pArgs, + __inout BA_ONUXPAYLOADDELETED_RESULTS* pResults + ) +{ + return pBA->OnUxPayloadDeleted(pArgs->wzPayloadId, pArgs->wzPayloadPath, pArgs->recommendation, &pResults->action); +} + /******************************************************************* BalBaseBootstrapperApplicationProc - requires pvContext to be of type IBootstrapperApplication. Provides a default mapping between the new message based BA interface and @@ -1060,6 +1069,9 @@ static HRESULT WINAPI BalBaseBootstrapperApplicationProc( case BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE: hr = BalBaseBAProcOnCachePackageNonVitalValidationFailure(pBA, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; + case BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED: + hr = BalBaseBAProcOnUxPayloadDeleted(pBA, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; } } diff --git a/src/api/burn/balutil/inc/IBootstrapperApplication.h b/src/api/burn/balutil/inc/IBootstrapperApplication.h index 7603d82a3..5af116c1f 100644 --- a/src/api/burn/balutil/inc/IBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/IBootstrapperApplication.h @@ -765,4 +765,13 @@ DECLARE_INTERFACE_IID_(IBootstrapperApplication, IUnknown, "53C31D56-49C0-426B-A __in BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION recommendation, __inout BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION* pAction ) = 0; + + // OnUxPayloadDeleted - called when the engine detects that a UX payloads was deleted. This may happen for example, when a cleaning tool deletes files from %TEMP% folder. + // Note that, the event might be fired multiple times for each missing payload. + STDMETHOD(OnUxPayloadDeleted)( + __in_z LPCWSTR wzPayloadId, + __in_z LPCWSTR wzPayloadPath, + __in BOOTSTRAPPER_UXPAYLOADDELETED_ACTION recommendation, + __inout BOOTSTRAPPER_UXPAYLOADDELETED_ACTION* pAction + ) = 0; }; diff --git a/src/burn/engine/container.cpp b/src/burn/engine/container.cpp index dfa1257bb..541177c82 100644 --- a/src/burn/engine/container.cpp +++ b/src/burn/engine/container.cpp @@ -487,3 +487,61 @@ extern "C" HRESULT ContainerFindById( LExit: return hr; } + +HRESULT ContainerReextractUX( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER_CONTEXT containerContext = { }; + LPWSTR sczStreamName = NULL; + int nNumReextracted = 0; + + // Check which payloads are missing + for (DWORD i = 0; i < pEngineState->userExperience.payloads.cPayloads; ++i) + { + BURN_PAYLOAD* pPayload = pEngineState->userExperience.payloads.rgPayloads + i; + + if (pPayload->sczLocalFilePath && *pPayload->sczLocalFilePath && (BURN_PAYLOAD_STATE_ACQUIRED == pPayload->state) && !FileExistsEx(pPayload->sczLocalFilePath, NULL)) + { + // Notify and let BA decide whether or not to extract the payload + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action = BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_REACQUIRE; + + hr = UserExperienceOnUxPayloadDeleted(&pEngineState->userExperience, pPayload->sczKey, pPayload->sczFilePath, &action); + ExitOnFailure(hr, "Failed to get UX action for deleted payload."); + + LogId(REPORT_WARNING, MSG_UX_PAYLOAD_MISSING, pPayload->sczKey, pPayload->sczFilePath, LoggingUxPayloadDeletedAction(action)); + + if (BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_REACQUIRE == action) + { + pPayload->state = BURN_PAYLOAD_STATE_NONE; + ++nNumReextracted; + } + } + } + + if (!nNumReextracted) + { + ExitFunction(); + } + + // Open attached UX container. + hr = ContainerOpenUX(&pEngineState->section, &containerContext); + ExitOnFailure(hr, "Failed to open attached UX container."); + + // Skip manifest. + hr = ContainerNextStream(&containerContext, &sczStreamName); + ExitOnFailure(hr, "Failed to open manifest stream."); + + hr = ContainerSkipStream(&containerContext); + ExitOnFailure(hr, "Failed to skip manifest stream."); + + hr = PayloadExtractUXContainer(&pEngineState->userExperience.payloads, &containerContext, pEngineState->userExperience.sczTempDirectory); + ExitOnFailure(hr, "Failed to extract bootstrapper application payloads."); + +LExit: + ContainerClose(&containerContext); + ReleaseStr(sczStreamName); + + return hr; +} diff --git a/src/burn/engine/container.h b/src/burn/engine/container.h index 314607ace..689bfedd7 100644 --- a/src/burn/engine/container.h +++ b/src/burn/engine/container.h @@ -35,6 +35,7 @@ extern "C" { // Forward declarations typedef struct _BURN_EXTENSION BURN_EXTENSION; typedef struct _BURN_EXTENSIONS BURN_EXTENSIONS; +typedef struct _BURN_ENGINE_STATE BURN_ENGINE_STATE; // constants @@ -180,6 +181,9 @@ HRESULT ContainerOpenUX( __in BURN_SECTION* pSection, __in BURN_CONTAINER_CONTEXT* pContext ); +HRESULT ContainerReextractUX( + __in BURN_ENGINE_STATE* pEngineState + ); HRESULT ContainerOpen( __in BURN_CONTAINER_CONTEXT* pContext, __in BURN_CONTAINER* pContainer, diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc index 1ce424ebe..3fa54f0b4 100644 --- a/src/burn/engine/engine.mc +++ b/src/burn/engine/engine.mc @@ -1309,3 +1309,10 @@ SymbolicName=MSG_PAYLOAD_HARD_LINK_FAILED Language=English Creating a hard link to '%1!ls!' failed with error code %2!u!. Resorting to plain copy. . + +MessageId=706 +Severity=Warning +SymbolicName=MSG_UX_PAYLOAD_MISSING +Language=English +UX payload deletion was detected. Payload Id '%1!ls!'. Payload Path '%2!ls!'. action '%3!ls!'. +. diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp index b0ceceb14..520d19746 100644 --- a/src/burn/engine/logging.cpp +++ b/src/burn/engine/logging.cpp @@ -1061,6 +1061,20 @@ extern "C" LPWSTR LoggingStringOrUnknownIfNull( return wz ? wz : L"Unknown"; } +extern "C" LPCWSTR LoggingUxPayloadDeletedAction( + __in BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action + ) +{ + switch (action) + { + case BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_NONE: + return L"None"; + case BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_REACQUIRE: + return L"Reacquire"; + default: + return L"Invalid"; + } +} // internal function declarations diff --git a/src/burn/engine/logging.h b/src/burn/engine/logging.h index 2a5af76f6..01bbea07f 100644 --- a/src/burn/engine/logging.h +++ b/src/burn/engine/logging.h @@ -204,6 +204,10 @@ LPWSTR LoggingStringOrUnknownIfNull( __in LPCWSTR wz ); +LPCWSTR LoggingUxPayloadDeletedAction( + __in BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action + ); + #if defined(__cplusplus) } diff --git a/src/burn/engine/payload.cpp b/src/burn/engine/payload.cpp index fe3d673e7..bcbd0b2dc 100644 --- a/src/burn/engine/payload.cpp +++ b/src/burn/engine/payload.cpp @@ -294,9 +294,19 @@ extern "C" HRESULT PayloadExtractUXContainer( hr = PayloadFindEmbeddedBySourcePath(pPayloads->sdhPayloads, sczStreamName, &pPayload); ExitOnFailure(hr, "Failed to find embedded payload: %ls", sczStreamName); + if (BURN_PAYLOAD_STATE_ACQUIRED == pPayload->state) + { + hr = ContainerSkipStream(pContainerContext); + ExitOnFailure(hr, "Failed to skip an existing payload: %ls", sczStreamName); + continue; + } + // make file path - hr = PathConcatRelativeToFullyQualifiedBase(wzTargetDir, pPayload->sczFilePath, &pPayload->sczLocalFilePath); - ExitOnFailure(hr, "Failed to concat file paths."); + if (!pPayload->sczLocalFilePath || !*pPayload->sczLocalFilePath) + { + hr = PathConcatRelativeToFullyQualifiedBase(wzTargetDir, pPayload->sczFilePath, &pPayload->sczLocalFilePath); + ExitOnFailure(hr, "Failed to concat file paths."); + } // extract file hr = PathGetDirectory(pPayload->sczLocalFilePath, &sczDirectory); diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp index 0ea3a9300..a3d9faa32 100644 --- a/src/burn/engine/userexperience.cpp +++ b/src/burn/engine/userexperience.cpp @@ -31,6 +31,15 @@ static HRESULT SendBAMessageFromInactiveEngine( __inout LPVOID pvResults ); +typedef struct _MONITOR_UX_FOLDER_PARAMS +{ + BURN_USER_EXPERIENCE* pUserExperience; + BURN_ENGINE_STATE* pEngineState; +} MONITOR_UX_FOLDER_PARAMS; + +static DWORD WINAPI MonitorUxFolderThreadProc( + _In_ LPVOID lpParameter +); // function definitions @@ -99,6 +108,11 @@ extern "C" HRESULT UserExperienceLoad( HRESULT hr = S_OK; BOOTSTRAPPER_CREATE_ARGS args = { }; BOOTSTRAPPER_CREATE_RESULTS results = { }; + MONITOR_UX_FOLDER_PARAMS monitorContxet = { }; + HANDLE rghWait[2] = { NULL,NULL }; + DWORD dwWait = ERROR_SUCCESS; + DWORD dwMonitorThreadExitCode = ERROR_SUCCESS; + BOOL bRes = TRUE; LPCWSTR wzPath = pUserExperience->payloads.rgPayloads[0].sczLocalFilePath; args.cbSize = sizeof(BOOTSTRAPPER_CREATE_ARGS); @@ -109,6 +123,40 @@ extern "C" HRESULT UserExperienceLoad( results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); + // Monitor UX folder for file deletions + pUserExperience->hUxFolderMonitorStarted = ::CreateEventW(NULL, TRUE, FALSE, NULL); + ExitOnNullWithLastError(pUserExperience->hUxFolderMonitorStarted, hr, "Failed to create event"); + + pUserExperience->hUxFolderStopMonitor = ::CreateEventW(NULL, TRUE, FALSE, NULL); + ExitOnNullWithLastError(pUserExperience->hUxFolderStopMonitor, hr, "Failed to create event"); + + monitorContxet.pEngineState = pEngineContext->pEngineState; + monitorContxet.pUserExperience = pUserExperience; + + pUserExperience->hUxFolderMonitorThread = ::CreateThread(NULL, 0, MonitorUxFolderThreadProc, &monitorContxet, 0, NULL); + ExitOnNullWithLastError(pUserExperience->hUxFolderMonitorThread, hr, "Failed to create thread"); + + // Wait for thread termination (error) / hUxFolderMonitorStarted set (success) + rghWait[0] = pUserExperience->hUxFolderMonitorStarted; + rghWait[1] = pUserExperience->hUxFolderMonitorThread; + dwWait = ::WaitForMultipleObjects(2, rghWait, FALSE, INFINITE); + switch (dwWait) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + bRes = ::GetExitCodeThread(pUserExperience->hUxFolderMonitorThread, &dwMonitorThreadExitCode); + ExitOnNullWithLastError(bRes, hr, "UX folder monitor thread has exited prematurely. Failed to get thread exit code."); + ExitOnNull(dwMonitorThreadExitCode, hr, E_FAIL, "UX folder monitor thread has exited prematurely"); + ExitOnWin32Error(dwMonitorThreadExitCode, hr, "UX folder monitor thread has exited prematurely"); + case WAIT_FAILED: + ExitOnLastError(hr, "Failed to wait for UX folder monitor thread"); + default: + hr = E_FAIL; + ExitOnFailure(hr, "Failed to wait for UX folder monitor thread"); + break; + } + // Load BA DLL. pUserExperience->hUXModule = ::LoadLibraryExW(wzPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); ExitOnNullWithLastError(pUserExperience->hUXModule, hr, "Failed to load BA DLL: %ls", wzPath); @@ -138,6 +186,7 @@ extern "C" HRESULT UserExperienceUnload( ) { HRESULT hr = S_OK; + BOOL bRes = TRUE; BOOTSTRAPPER_DESTROY_ARGS args = { }; BOOTSTRAPPER_DESTROY_RESULTS results = { }; @@ -164,7 +213,23 @@ extern "C" HRESULT UserExperienceUnload( pUserExperience->hUXModule = NULL; } -//LExit: + if (pUserExperience->hUxFolderMonitorThread) + { + if (pUserExperience->hUxFolderStopMonitor) + { + bRes = ::SetEvent(pUserExperience->hUxFolderStopMonitor); + if (!bRes) + { + ::TerminateThread(pUserExperience->hUxFolderMonitorThread, ::GetLastError()); + } + } + ::WaitForSingleObject(pUserExperience->hUxFolderMonitorThread, INFINITE); + } + + ReleaseHandle(pUserExperience->hUxFolderMonitorStarted); + ReleaseHandle(pUserExperience->hUxFolderStopMonitor); + ReleaseHandle(pUserExperience->hUxFolderMonitorThread); + return hr; } @@ -828,6 +893,40 @@ EXTERN_C BAAPI UserExperienceOnCachePackageNonVitalValidationFailure( return hr; } +EXTERN_C BAAPI UserExperienceOnUxPayloadDeleted( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPayloadId, + __in_z LPCWSTR wzPayloadPath, + __inout BOOTSTRAPPER_UXPAYLOADDELETED_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BA_ONUXPAYLOADDELETED_ARGS args = { }; + BA_ONUXPAYLOADDELETED_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPayloadId = wzPayloadId; + args.wzPayloadPath = wzPayloadPath; + args.recommendation = *pAction; + + results.cbSize = sizeof(results); + results.action = *pAction; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED, &args, &results); + ExitOnFailure(hr, "BA OnUxPayloadDeleted failed."); + + switch (results.action) + { + case BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_NONE: __fallthrough; + case BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_REACQUIRE: + *pAction = results.action; + break; + } + +LExit: + return hr; +} + EXTERN_C BAAPI UserExperienceOnCachePayloadExtractBegin( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z_opt LPCWSTR wzContainerId, @@ -3079,3 +3178,59 @@ static HRESULT SendBAMessageFromInactiveEngine( LExit: return hr; } + +static DWORD WINAPI MonitorUxFolderThreadProc( + _In_ LPVOID lpParameter +) +{ + HRESULT hr = S_OK; + BOOL bRes = TRUE; + HANDLE hChangeNotification = NULL; + HANDLE rghWait[2]; + MONITOR_UX_FOLDER_PARAMS *pMonitorContxet = (MONITOR_UX_FOLDER_PARAMS*)lpParameter; + BURN_USER_EXPERIENCE* pUserExperience = pMonitorContxet->pUserExperience; + BURN_ENGINE_STATE* pEngineState = pMonitorContxet->pEngineState; + pMonitorContxet = NULL; + + hChangeNotification = ::FindFirstChangeNotificationW(pUserExperience->sczTempDirectory, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME); + ExitOnNullWithLastError(hChangeNotification, hr, "Failed to create a change notification"); + + bRes = ::SetEvent(pUserExperience->hUxFolderMonitorStarted); + ExitOnNullWithLastError(bRes, hr, "Failed to signal monitor start"); + + rghWait[0] = pUserExperience->hUxFolderStopMonitor; + rghWait[1] = hChangeNotification; + + while (true) + { + DWORD dwWait = ::WaitForMultipleObjects(2, rghWait, FALSE, INFINITE); + switch (dwWait) + { + case WAIT_OBJECT_0: + ExitFunction(); + case WAIT_OBJECT_0 + 1: + break; + case WAIT_FAILED: + ExitOnLastError(hr, "Failed to wait for change notification"); + default: + hr = E_FAIL; + ExitOnFailure(hr, "Failed to wait for change notification"); + break; + } + + hr = ContainerReextractUX(pEngineState); + ExitOnFailure(hr, "Failed to re-extract UX container missing payloads"); + + bRes = ::FindNextChangeNotification(hChangeNotification); + ExitOnNullWithLastError(bRes, hr, "Failed to create a next-change notification"); + } + +LExit: + if (hChangeNotification) + { + ::FindCloseChangeNotification(hChangeNotification); + hChangeNotification = NULL; + } + + return HRESULT_CODE(hr); +} diff --git a/src/burn/engine/userexperience.h b/src/burn/engine/userexperience.h index 13d499324..6589138b6 100644 --- a/src/burn/engine/userexperience.h +++ b/src/burn/engine/userexperience.h @@ -43,6 +43,12 @@ typedef struct _BURN_USER_EXPERIENCE // during Detect. DWORD dwExitCode; // Exit code returned by the user experience for the engine overall. + + + // Monitor changes and re-extract UX container if payloads are deleted + HANDLE hUxFolderMonitorThread; + HANDLE hUxFolderMonitorStarted; + HANDLE hUxFolderStopMonitor; } BURN_USER_EXPERIENCE; // functions @@ -201,6 +207,12 @@ BAAPI UserExperienceOnCachePackageNonVitalValidationFailure( __in HRESULT hrStatus, __inout BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION* pAction ); +BAAPI UserExperienceOnUxPayloadDeleted( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPayloadId, + __in_z LPCWSTR wzPayloadPath, + __inout BOOTSTRAPPER_UXPAYLOADDELETED_ACTION* pAction + ); BAAPI UserExperienceOnCachePackageComplete( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp index 0f6ca6618..f3c07ac84 100644 --- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp @@ -1664,6 +1664,9 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati case BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE: OnCachePackageNonVitalValidationFailureFallback(reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; + case BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED: + OnUxPayloadDeletedFallback(reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; default: #ifdef DEBUG BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "WIXSTDBA: Forwarding unknown BA message: %d", message); @@ -2368,6 +2371,13 @@ class CWixStandardBootstrapperApplication : public CBalBaseBootstrapperApplicati m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE, pArgs, pResults, m_pvBAFunctionsProcContext); } + void OnUxPayloadDeletedFallback( + __in BA_ONUXPAYLOADDELETED_ARGS* pArgs, + __inout BA_ONUXPAYLOADDELETED_RESULTS* pResults + ) + { + m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONUXPAYLOADDELETED, pArgs, pResults, m_pvBAFunctionsProcContext); + } HRESULT ShowMsiFilesInUse( __in DWORD cFiles,