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,