Skip to content

Commit

Permalink
Monitor UX folder and re-extract any UX payloads that were deleted fo…
Browse files Browse the repository at this point in the history
…r any reason
  • Loading branch information
nirbar committed Oct 28, 2024
1 parent 8aba5c4 commit ccb6cb3
Show file tree
Hide file tree
Showing 31 changed files with 562 additions and 14 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ public abstract class BootstrapperApplication : MarshalByRefObject, IDefaultBoot
/// <inheritdoc/>
public event EventHandler<CachePackageNonVitalValidationFailureEventArgs> CachePackageNonVitalValidationFailure;

/// <inheritdoc/>
public event EventHandler<UxPayloadDeletedEventArgs> UxPayloadDeleted;

/// <summary>
/// The default constructor.
/// </summary>
Expand Down Expand Up @@ -1425,6 +1428,19 @@ protected virtual void OnCachePackageNonVitalValidationFailure(CachePackageNonVi
}
}

/// <summary>
/// Called by the engine, raises the <see cref="UxPayloadDeleted"/> event.
/// </summary>
/// <param name="args">Additional arguments for this event.</param>
protected virtual void OnUxPayloadDeleted(UxPayloadDeletedEventArgs args)
{
EventHandler<UxPayloadDeletedEventArgs> handler = this.UxPayloadDeleted;
if (null != handler)
{
handler(this, args);
}
}

#region IBootstrapperApplication Members

int IBootstrapperApplication.BAProc(int message, IntPtr pvArgs, IntPtr pvResults)
Expand Down Expand Up @@ -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
}
}
28 changes: 28 additions & 0 deletions src/api/burn/WixToolset.BootstrapperApplicationApi/EventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2836,4 +2836,32 @@ public CachePackageNonVitalValidationFailureEventArgs(string packageId, int hrSt
/// </summary>
public string PackageId { get; private set; }
}

/// <summary>
/// Event arguments for <see cref="IDefaultBootstrapperApplication.UxPayloadDeleted"/>
/// </summary>
[Serializable]
public class UxPayloadDeletedEventArgs : ActionEventArgs<BOOTSTRAPPER_UXPAYLOADDELETED_ACTION>
{
/// <summary>
/// This class is for events raised by the engine.
/// It is not intended to be instantiated by user code.
/// </summary>
public UxPayloadDeletedEventArgs(string wzPayloadId, string wzPayloadPath, BOOTSTRAPPER_UXPAYLOADDELETED_ACTION recommendation, BOOTSTRAPPER_UXPAYLOADDELETED_ACTION action)
: base(0, recommendation, action)
{
this.PayloadId = wzPayloadId;
this.PayloadPath = wzPayloadPath;
}

/// <summary>
/// Gets the identity of the missing UX payload.
/// </summary>
public string PayloadId { get; private set; }

/// <summary>
/// Gets the relative path of the missing UX payload.
/// </summary>
public string PayloadPath { get; private set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,18 @@ int OnCachePackageNonVitalValidationFailure(
BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION recommendation,
ref BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION action
);

/// <summary>
/// See <see cref="IDefaultBootstrapperApplication.UxPayloadDeleted"/>.
/// </summary>
[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
);
}

/// <summary>
Expand Down Expand Up @@ -1759,6 +1771,22 @@ public enum BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION
Acquire,
}

/// <summary>
/// The available actions for <see cref="IDefaultBootstrapperApplication.CachePackageNonVitalValidationFailure"/>
/// </summary>
public enum BOOTSTRAPPER_UXPAYLOADDELETED_ACTION
{
/// <summary>
/// Instructs the engine to not take any special action.
/// </summary>
None,

/// <summary>
/// Instructs the engine to try to reacquire the UX payload.
/// </summary>
Reacquire,
}

/// <summary>
/// The available actions for <see cref="IDefaultBootstrapperApplication.CacheVerifyComplete"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ public interface IDefaultBootstrapperApplication : IBootstrapperApplication
/// </summary>
event EventHandler<CachePackageNonVitalValidationFailureEventArgs> CachePackageNonVitalValidationFailure;

/// <summary>
/// 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.
/// </summary>
event EventHandler<UxPayloadDeletedEventArgs> UxPayloadDeleted;

/// <summary>
/// Fired when the engine begins the extraction of the payload from the container.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/api/burn/balutil/inc/BootstrapperApplicationBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
9 changes: 9 additions & 0 deletions src/api/burn/balutil/inc/IBootstrapperApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
64 changes: 64 additions & 0 deletions src/api/burn/balutil/msg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<DWORD*>(&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<DWORD*>(&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,
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 22 additions & 0 deletions src/api/burn/inc/BootstrapperApplicationTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/burn/engine/ba.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down
75 changes: 75 additions & 0 deletions src/burn/engine/bacallback.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<DWORD*>(&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,
Expand Down
6 changes: 6 additions & 0 deletions src/burn/engine/bacallback.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit ccb6cb3

Please sign in to comment.