Custom Action Using WiX: Reading From The Binary Table

Over the years I have seen this question come up a few times. Although there are some good examples inside the source code of the Windows Installer XML Toolset (WiX) itself, it can be hard to find. The question is “how do I read data from a MSI Binary table?” The easiest way I have found is to use the Windows Installer XML Toolset (WiX) and some of the great libraries that ship with it. If you haven’t taken a look at them yet, start scanning the WcaUtil and DUtil libraries for some really great APIs that will make your life much less complex.

 const UINT COST_BINARY_READ_TRANSACTION = 10000;
//Extract the Binary Id to a memory stream as an immediate custom action.
//
__declspec(dllexport)
UINT __stdcall GetMyBinaryData(MSIHANDLE hInstall)
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    LPWSTR pwzStreamToString = NULL;
    BYTE* pbData = NULL;
    DWORD cbData = 0;

    hr = WcaInitialize(hInstall, "GetMyBinaryData");
    ExitOnFailure(hr, "failed to initialize GetMyBinaryData");

    //Extact the Id in the Binary table to a stream.
    hr = ExtractBinary(L"MyBinaryTableId", &pbData, &cbData);
    ExitOnFailure(hr, "failed to extract binary data");

    //Write the stream to a string to be passed as CustomActionData
    hr = WcaWriteStreamToCaData(pbData, cbData, &pwzStreamToString);
    ExitOnFailure(hr, "failed to write the stream to a string");

    hr = WcaDoDeferredAction(L"IAlwaysCodeForRollbackToHandleFailures", L"Rollback", 0);
    ExitOnFailure(hr, "Failed to schedule IAlwaysCodeForRollbackToHandleFailures");

    //Send it over to a deferred custom action if you need to do "special" things with it
    hr = WcaDoDeferredAction(L"IAmUsingThisAsAMemoryStream", pwzStreamToString, COST_BINARY_READ_TRANSACTION);
    ExitOnFailure(hr, "Failed to schedule IAmUsingThisAsAMemoryStream");

LExit:
    ReleaseStr(pwzStreamToString);
    ReleaseMem(pbData);

    if (FAILED(hr))
    {
        er = ERROR_INSTALL_FAILURE;
    }
    return WcaFinalize(er); 
}

Next, as the custom action reads, we will be opening this in a deferred action as a memory stream.

 //Read the stream from the CustomActionData property.
__declspec(dllexport)
UINT __stdcall IAmUsingThisAsAMemoryStream(MSIHANDLE hInstall)
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    LPWSTR pwzCustomActionData = NULL;
    LPBYTE pbData = NULL;
    DWORD_PTR cbData = 0;

    hr = WcaInitialize(hInstall, "IAmUsingThisAsAMemoryStream");
    ExitOnFailure(hr, "failed to initialize IAmUsingThisAsAMemoryStream");

    hr = WcaGetProperty(L"CustomActionData", &pwzCustomActionData);
    ExitOnFailure(hr, "failed to get CustomActionData");

    //Get the stream for the custom action data.
    hr = WcaReadStreamFromCaData(&pwzCustomActionData, &pbData, &cbData);
    ExitOnFailure(hr, "failed to read file contents from custom action data");


LExit:
    ReleaseStr(pwzCustomActionData);
    ReleaseMem(pbData);

    if (FAILED(hr))
    {
        er = ERROR_INSTALL_FAILURE;
    }

    return WcaFinalize(er);
}

There is a quick and very dirty example on how to read from a Binary table. There are easy ways of abusing this which you, the developer, should avoid at all cost. Writing the streams to disk and leaving them there is one of the most common I have seen.