diff --git a/README.md b/README.md index 3c31db989..bbd8179df 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,5 @@ This repository contains the PanelSwWix4: A custom WiX Toolset codebase - Support sending custom messages on embedded pipe - Best effort to log premature termination of companion process - [Bundle/@RunAsAdmin](https://github.com/wixtoolset/issues/issues/5309) attribute creates a bootstrapper that requires elevation when launched +- Monitor UX folder and re-extract any UX payloads that were deleted for any reason + diff --git a/src/api/burn/WixToolset.BootstrapperApplicationApi/BootstrapperApplication.cs b/src/api/burn/WixToolset.BootstrapperApplicationApi/BootstrapperApplication.cs index af2e037f6..4d6fbd685 100644 --- a/src/api/burn/WixToolset.BootstrapperApplicationApi/BootstrapperApplication.cs +++ b/src/api/burn/WixToolset.BootstrapperApplicationApi/BootstrapperApplication.cs @@ -275,6 +275,9 @@ public abstract class BootstrapperApplication : MarshalByRefObject, IDefaultBoot /// public event EventHandler CachePackageNonVitalValidationFailure; + /// + public event EventHandler UxPayloadDeleted; + /// /// The default constructor. /// @@ -1425,6 +1428,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) @@ -2211,6 +2227,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.BootstrapperApplicationApi/EventArgs.cs b/src/api/burn/WixToolset.BootstrapperApplicationApi/EventArgs.cs index 6d383a548..a2bd8cf61 100644 --- a/src/api/burn/WixToolset.BootstrapperApplicationApi/EventArgs.cs +++ b/src/api/burn/WixToolset.BootstrapperApplicationApi/EventArgs.cs @@ -2836,4 +2836,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.BootstrapperApplicationApi/IBootstrapperApplication.cs b/src/api/burn/WixToolset.BootstrapperApplicationApi/IBootstrapperApplication.cs index 3ca0b89a0..ed4e2320d 100644 --- a/src/api/burn/WixToolset.BootstrapperApplicationApi/IBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.BootstrapperApplicationApi/IBootstrapperApplication.cs @@ -1048,6 +1048,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 + ); } /// @@ -1759,6 +1771,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.BootstrapperApplicationApi/IDefaultBootstrapperApplication.cs b/src/api/burn/WixToolset.BootstrapperApplicationApi/IDefaultBootstrapperApplication.cs index e6b58e263..b6ffe9bbf 100644 --- a/src/api/burn/WixToolset.BootstrapperApplicationApi/IDefaultBootstrapperApplication.cs +++ b/src/api/burn/WixToolset.BootstrapperApplicationApi/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/BootstrapperApplicationBase.h b/src/api/burn/balutil/inc/BootstrapperApplicationBase.h index 140e0cab0..e8e47aeac 100644 --- a/src/api/burn/balutil/inc/BootstrapperApplicationBase.h +++ b/src/api/burn/balutil/inc/BootstrapperApplicationBase.h @@ -1164,6 +1164,16 @@ class CBootstrapperApplicationBase : 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; + } + protected: // // PromptCancel - prompts the user to close (if not forced). diff --git a/src/api/burn/balutil/inc/IBootstrapperApplication.h b/src/api/burn/balutil/inc/IBootstrapperApplication.h index ac86f6adf..a4c1713c2 100644 --- a/src/api/burn/balutil/inc/IBootstrapperApplication.h +++ b/src/api/burn/balutil/inc/IBootstrapperApplication.h @@ -770,4 +770,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/api/burn/balutil/msg.cpp b/src/api/burn/balutil/msg.cpp index 7fcd905d5..f943ffadc 100644 --- a/src/api/burn/balutil/msg.cpp +++ b/src/api/burn/balutil/msg.cpp @@ -1009,6 +1009,66 @@ static HRESULT OnCachePackageNonVitalValidationFailure( return hr; } +static HRESULT OnUxPayloadDeleted( + __in IBootstrapperApplication* pApplication, + __in BUFF_READER* pReaderArgs, + __in BUFF_READER* pReaderResults, + __in BUFF_BUFFER* pBuffer + ) +{ + HRESULT hr = S_OK; + BA_ONUXPAYLOADDELETED_ARGS args = { }; + BA_ONUXPAYLOADDELETED_RESULTS results = { }; + LPWSTR sczPayloadId = NULL; + LPWSTR sczPayloadPath = NULL; + + // Read args. + hr = BuffReaderReadNumber(pReaderArgs, &args.dwApiVersion); + ExitOnFailure(hr, "Failed to read API version of OnUxPayloadDeleted args."); + + hr = BuffReaderReadString(pReaderArgs, &sczPayloadId); + ExitOnFailure(hr, "Failed to read payload id of OnUxPayloadDeleted args."); + + hr = BuffReaderReadString(pReaderArgs, &sczPayloadPath); + ExitOnFailure(hr, "Failed to read payload path of OnUxPayloadDeleted args."); + + args.wzPayloadId = sczPayloadId; + args.wzPayloadPath = sczPayloadPath; + + hr = BuffReaderReadNumber(pReaderArgs, reinterpret_cast(&args.recommendation)); + ExitOnFailure(hr, "Failed to read recommendation of OnUxPayloadDeleted args."); + + // Read results. + hr = BuffReaderReadNumber(pReaderResults, &results.dwApiVersion); + ExitOnFailure(hr, "Failed to read API version of OnUxPayloadDeleted results."); + + hr = BuffReaderReadNumber(pReaderResults, reinterpret_cast(&results.action)); + ExitOnFailure(hr, "Failed to read action of OnUxPayloadDeleted results."); + + // Callback. + hr = pApplication->BAProc(BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED, &args, &results); + + if (E_NOTIMPL == hr) + { + hr = pApplication->OnUxPayloadDeleted(args.wzPayloadId, args.wzPayloadPath, args.recommendation, &results.action); + } + + pApplication->BAProcFallback(BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED, &args, &results, &hr); + BalExitOnFailure(hr, "BA OnUxPayloadDeleted failed."); + + // Write results. + hr = BuffWriteNumberToBuffer(pBuffer, sizeof(results)); + ExitOnFailure(hr, "Failed to write size of OnUxPayloadDeleted struct."); + + hr = BuffWriteNumberToBuffer(pBuffer, results.action); + ExitOnFailure(hr, "Failed to write action of OnUxPayloadDeleted struct."); + +LExit: + ReleaseStr(sczPayloadId); + ReleaseStr(sczPayloadPath); + return hr; +} + static HRESULT OnCachePayloadExtractBegin( __in IBootstrapperApplication* pApplication, __in BUFF_READER* pReaderArgs, @@ -5344,6 +5404,10 @@ static HRESULT ProcessMessage( hr = OnCachePackageNonVitalValidationFailure(pApplication, &readerArgs, &readerResults, &bufferResponse); break; + case BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED: + hr = OnUxPayloadDeleted(pApplication, &readerArgs, &readerResults, &bufferResponse); + break; + default: hr = E_NOTIMPL; break; diff --git a/src/api/burn/inc/BootstrapperApplicationTypes.h b/src/api/burn/inc/BootstrapperApplicationTypes.h index 30e51b94a..c7bb27099 100644 --- a/src/api/burn/inc/BootstrapperApplicationTypes.h +++ b/src/api/burn/inc/BootstrapperApplicationTypes.h @@ -239,6 +239,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 @@ -278,6 +279,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, @@ -628,6 +636,20 @@ struct BA_ONCACHEPACKAGENONVITALVALIDATIONFAILURE_RESULTS BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION action; }; +struct BA_ONUXPAYLOADDELETED_ARGS +{ + DWORD dwApiVersion; + LPCWSTR wzPayloadId; + LPCWSTR wzPayloadPath; + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION recommendation; +}; + +struct BA_ONUXPAYLOADDELETED_RESULTS +{ + DWORD dwApiVersion; + BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action; +}; + struct BA_ONCACHEPAYLOADEXTRACTBEGIN_ARGS { DWORD dwApiVersion; diff --git a/src/burn/engine/ba.h b/src/burn/engine/ba.h index 3561805ab..eae8cc0f1 100644 --- a/src/burn/engine/ba.h +++ b/src/burn/engine/ba.h @@ -51,6 +51,11 @@ 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; diff --git a/src/burn/engine/bacallback.cpp b/src/burn/engine/bacallback.cpp index db696bee5..5ee8df4c9 100644 --- a/src/burn/engine/bacallback.cpp +++ b/src/burn/engine/bacallback.cpp @@ -1276,6 +1276,81 @@ EXTERN_C HRESULT BACallbackOnCachePackageNonVitalValidationFailure( return hr; } +EXTERN_C BAAPI BACallbackOnUxPayloadDeleted( + __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 = { }; + BUFF_BUFFER bufferArgs = { }; + BUFF_BUFFER bufferResults = { }; + PIPE_RPC_RESULT rpc = { }; + SIZE_T iBuffer = 0; + + args.dwApiVersion = WIX_5_BOOTSTRAPPER_APPLICATION_API_VERSION; + args.wzPayloadId = wzPayloadId; + args.wzPayloadPath = wzPayloadPath; + args.recommendation = *pAction; + + results.dwApiVersion = WIX_5_BOOTSTRAPPER_APPLICATION_API_VERSION; + results.action = *pAction; + + // Send args. + hr = BuffWriteNumberToBuffer(&bufferArgs, args.dwApiVersion); + ExitOnFailure(hr, "Failed to write API version of OnUxPayloadDeleted args."); + + hr = BuffWriteStringToBuffer(&bufferArgs, args.wzPayloadId); + ExitOnFailure(hr, "Failed to write payload id of OnUxPayloadDeleted args."); + + hr = BuffWriteStringToBuffer(&bufferArgs, args.wzPayloadPath); + ExitOnFailure(hr, "Failed to write payload path of OnUxPayloadDeleted args."); + + hr = BuffWriteNumberToBuffer(&bufferArgs, args.recommendation); + ExitOnFailure(hr, "Failed to write recommendation of OnUxPayloadDeleted args."); + + // Send results. + hr = BuffWriteNumberToBuffer(&bufferResults, results.dwApiVersion); + ExitOnFailure(hr, "Failed to write API version of OnUxPayloadDeleted results."); + + hr = BuffWriteNumberToBuffer(&bufferResults, results.action); + ExitOnFailure(hr, "Failed to write action of OnUxPayloadDeleted results."); + + // Callback. + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONUXPAYLOADDELETED, &bufferArgs, &bufferResults, &rpc); + ExitOnFailure(hr, "BA OnUxPayloadDeleted failed."); + + if (S_FALSE == hr) + { + ExitFunction(); + } + + // Read results. + hr = BuffReadNumber(rpc.pbData, rpc.cbData, &iBuffer, &results.dwApiVersion); + ExitOnFailure(hr, "Failed to read size of OnUxPayloadDeleted result."); + + hr = BuffReadNumber(rpc.pbData, rpc.cbData, &iBuffer, reinterpret_cast(&results.action)); + ExitOnFailure(hr, "Failed to read action of OnUxPayloadDeleted result."); + + switch (results.action) + { + case BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_NONE: __fallthrough; + case BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_REACQUIRE: + *pAction = results.action; + break; + } + +LExit: + PipeFreeRpcResult(&rpc); + ReleaseBuffer(bufferResults); + ReleaseBuffer(bufferArgs); + + return hr; +} + EXTERN_C HRESULT BACallbackOnCachePayloadExtractBegin( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z_opt LPCWSTR wzContainerId, diff --git a/src/burn/engine/bacallback.h b/src/burn/engine/bacallback.h index fd61a4e76..679fa8a45 100644 --- a/src/burn/engine/bacallback.h +++ b/src/burn/engine/bacallback.h @@ -111,6 +111,12 @@ HRESULT BACallbackOnCachePackageNonVitalValidationFailure( __in HRESULT hrStatus, __inout BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION* pAction ); +BAAPI BACallbackOnUxPayloadDeleted( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPayloadId, + __in_z LPCWSTR wzPayloadPath, + __inout BOOTSTRAPPER_UXPAYLOADDELETED_ACTION* pAction + ); HRESULT BACallbackOnCachePackageComplete( __in BURN_USER_EXPERIENCE* pUserExperience, __in_z LPCWSTR wzPackageId, diff --git a/src/burn/engine/bootstrapperapplication.cpp b/src/burn/engine/bootstrapperapplication.cpp index 7e4ada2c6..4b4562c5f 100644 --- a/src/burn/engine/bootstrapperapplication.cpp +++ b/src/burn/engine/bootstrapperapplication.cpp @@ -37,6 +37,15 @@ static HRESULT VerifyPipeSecret( __in_z LPCWSTR wzSecret ); +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 @@ -135,6 +144,11 @@ EXTERN_C HRESULT BootstrapperApplicationStart( HANDLE hBAPipe = INVALID_HANDLE_VALUE; HANDLE hBAEnginePipe = INVALID_HANDLE_VALUE; BAENGINE_CONTEXT* pEngineContext = NULL; + MONITOR_UX_FOLDER_PARAMS monitorContxet = { }; + HANDLE rghWait[2] = { NULL,NULL }; + DWORD dwWait = ERROR_SUCCESS; + DWORD dwMonitorThreadExitCode = ERROR_SUCCESS; + BOOL bRes = TRUE; BURN_USER_EXPERIENCE* pUserExperience = &pEngineState->userExperience; BOOTSTRAPPER_COMMAND* pCommand = &pEngineState->command; @@ -145,6 +159,39 @@ EXTERN_C HRESULT BootstrapperApplicationStart( hr = E_UNEXPECTED; ExitOnFailure(hr, "Failed to find bootstrapper application path."); } + // 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 = 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; + } hr = BurnPipeCreateNameAndSecret(&sczBasePipeName, &sczSecret); ExitOnFailure(hr, "Failed to create bootstrapper application pipename and secret"); @@ -216,6 +263,23 @@ EXTERN_C HRESULT BootstrapperApplicationStop( ReleaseHandle(pUserExperience->hBAProcess); } + if (pUserExperience->hUxFolderMonitorThread) + { + if (pUserExperience->hUxFolderStopMonitor) + { + BOOL bRes = ::SetEvent(pUserExperience->hUxFolderStopMonitor); + if (!bRes) + { + ::TerminateThread(pUserExperience->hUxFolderMonitorThread, ::GetLastError()); + } + } + ::WaitForSingleObject(pUserExperience->hUxFolderMonitorThread, INFINITE); + } + + ReleaseHandle(pUserExperience->hUxFolderMonitorStarted); + ReleaseHandle(pUserExperience->hUxFolderStopMonitor); + ReleaseHandle(pUserExperience->hUxFolderMonitorThread); + // If the bootstrapper application process has already requested to reload, no need // to check any further. But if the bootstrapper application process exited // with anything but success then fallback to the other bootstrapper application. @@ -691,3 +755,62 @@ static HRESULT VerifyPipeSecret( 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; + + hr = ContainerReextractUX(pEngineState); + ExitOnFailure(hr, "Failed to re-extract UX container missing payloads"); + + 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/container.cpp b/src/burn/engine/container.cpp index dfa1257bb..57ff6268b 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 = BACallbackOnUxPayloadDeleted(&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 d0397033b..89f02b9bc 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 @@ -179,6 +180,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 b3bca9836..b2727d055 100644 --- a/src/burn/engine/engine.mc +++ b/src/burn/engine/engine.mc @@ -1303,3 +1303,10 @@ SymbolicName=MSG_PLAN_CANCEL_MSI_TRANSACTION Language=English Canceling planned MSI transaction '%1!ls!' because it contains %2!lu! packages. . + +MessageId=704 +Severity=Warning +SymbolicName=MSG_UX_PAYLOAD_MISSING +Language=English +UX payload deletion was detected, Id: '%1!ls!', path: '%2!ls!', action: %3!ls!. +. diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp index 1331469b3..6c619776e 100644 --- a/src/burn/engine/logging.cpp +++ b/src/burn/engine/logging.cpp @@ -1058,6 +1058,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 7933fcf3a..381f91a78 100644 --- a/src/burn/engine/logging.h +++ b/src/burn/engine/logging.h @@ -203,6 +203,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 1d8328e30..9b8aba293 100644 --- a/src/burn/engine/payload.cpp +++ b/src/burn/engine/payload.cpp @@ -295,9 +295,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/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp index 234d086f5..f4dbdb7d2 100644 --- a/src/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp @@ -1704,6 +1704,9 @@ class CWixStandardBootstrapperApplication : public CBootstrapperApplicationBase 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); @@ -2408,6 +2411,13 @@ class CWixStandardBootstrapperApplication : public CBootstrapperApplicationBase 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, diff --git a/src/ext/Bal/wixstdfn/BalBaseBAFunctionsProc.cpp b/src/ext/Bal/wixstdfn/BalBaseBAFunctionsProc.cpp index fd15e3049..a519a4a33 100644 --- a/src/ext/Bal/wixstdfn/BalBaseBAFunctionsProc.cpp +++ b/src/ext/Bal/wixstdfn/BalBaseBAFunctionsProc.cpp @@ -758,6 +758,15 @@ static HRESULT BalBaseBAFunctionsProcOnCachePackageNonVitalValidationFailure( return pBAFunctions->OnCachePackageNonVitalValidationFailure(pArgs->wzPackageId, pArgs->hrStatus, pArgs->recommendation, &pResults->action); } +static HRESULT BalBaseBAFunctionsProcOnUxPayloadDeleted( + __in IBAFunctions* pBAFunctions, + __in BA_ONUXPAYLOADDELETED_ARGS* pArgs, + __inout BA_ONUXPAYLOADDELETED_RESULTS* pResults + ) +{ + return pBAFunctions->OnUxPayloadDeleted(pArgs->wzPayloadId, pArgs->wzPayloadPath, pArgs->recommendation, &pResults->action); +} + static HRESULT BalBaseBAFunctionsProcOnThemeLoaded( __in IBAFunctions* pBAFunctions, __in BA_FUNCTIONS_ONTHEMELOADED_ARGS* pArgs, @@ -1086,6 +1095,9 @@ HRESULT WINAPI BalBaseBAFunctionsProc( case BA_FUNCTIONS_MESSAGE_ONCACHEPACKAGENONVITALVALIDATIONFAILURE: hr = BalBaseBAFunctionsProcOnCachePackageNonVitalValidationFailure(pBAFunctions, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; + case BA_FUNCTIONS_MESSAGE_ONUXPAYLOADDELETED: + hr = BalBaseBAFunctionsProcOnUxPayloadDeleted(pBAFunctions, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; case BA_FUNCTIONS_MESSAGE_ONTHEMELOADED: hr = BalBaseBAFunctionsProcOnThemeLoaded(pBAFunctions, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; diff --git a/src/ext/Bal/wixstdfn/inc/BAFunctions.h b/src/ext/Bal/wixstdfn/inc/BAFunctions.h index 860b6281d..f6ae14024 100644 --- a/src/ext/Bal/wixstdfn/inc/BAFunctions.h +++ b/src/ext/Bal/wixstdfn/inc/BAFunctions.h @@ -96,6 +96,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/ext/Bal/wixstdfn/inc/BalBaseBAFunctions.h b/src/ext/Bal/wixstdfn/inc/BalBaseBAFunctions.h index 4c7d3f78c..db5b44a52 100644 --- a/src/ext/Bal/wixstdfn/inc/BalBaseBAFunctions.h +++ b/src/ext/Bal/wixstdfn/inc/BalBaseBAFunctions.h @@ -931,6 +931,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/internal/SetBuildNumber/Directory.Packages.props.pp b/src/internal/SetBuildNumber/Directory.Packages.props.pp index 8a05a6fc5..fa2597284 100644 --- a/src/internal/SetBuildNumber/Directory.Packages.props.pp +++ b/src/internal/SetBuildNumber/Directory.Packages.props.pp @@ -51,7 +51,7 @@ - + diff --git a/src/test/burn/TestBA/TestBA.cs b/src/test/burn/TestBA/TestBA.cs index 1556acd06..7c0d66203 100644 --- a/src/test/burn/TestBA/TestBA.cs +++ b/src/test/burn/TestBA/TestBA.cs @@ -603,6 +603,11 @@ protected override void OnUnregisterBegin(UnregisterBeginEventArgs args) this.Log("OnUnregisterBegin, default: {0}, requested: {1}", args.RecommendedRegistrationType, args.RegistrationType); } + protected override void OnUxPayloadDeleted(UxPayloadDeletedEventArgs args) + { + args.Action = BOOTSTRAPPER_UXPAYLOADDELETED_ACTION.None; + } + private void TestVariables() { // First make sure we can check and get standard variables of each type. diff --git a/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs b/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs index a86f498fd..2374d95dd 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs +++ b/src/test/burn/TestData/PrereqBaTests/BundleA/BundleA.wxs @@ -17,12 +17,12 @@ - + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs b/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs index 7a84bd5b4..13617fe28 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs +++ b/src/test/burn/TestData/PrereqBaTests/BundleB/BundleB.wxs @@ -16,12 +16,12 @@ - + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs b/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs index e03cad620..12a09ceec 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs +++ b/src/test/burn/TestData/PrereqBaTests/BundleC/BundleC.wxs @@ -16,12 +16,12 @@ - + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs b/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs index 2eed4bd12..37ba16243 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs +++ b/src/test/burn/TestData/PrereqBaTests/BundleD/BundleD.wxs @@ -15,12 +15,12 @@ - + diff --git a/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs b/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs index b6962b656..b5dd5588d 100644 --- a/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs +++ b/src/test/burn/TestData/PrereqBaTests/BundleE/BundleE.wxs @@ -14,11 +14,11 @@ - + diff --git a/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp index 1eaebe63b..69e425f01 100644 --- a/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp +++ b/src/test/burn/TestData/PrereqBaTests/PrereqBaf/PrereqBaf.cpp @@ -42,7 +42,17 @@ class CPrereqBaf : public CBalBaseBAFunctions return hr; } -private: + virtual STDMETHODIMP OnUxPayloadDeleted( + __in_z LPCWSTR /*wzPayloadId*/, + __in_z LPCWSTR /*wzPayloadPath*/, + __in BOOTSTRAPPER_UXPAYLOADDELETED_ACTION /*recommendation*/, + __inout BOOTSTRAPPER_UXPAYLOADDELETED_ACTION* pAction + ) + { + *pAction = BOOTSTRAPPER_UXPAYLOADDELETED_ACTION_NONE; + + return S_OK; + } public: //