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:
//