// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"


// Exit macros
#define WiuExitTrace(x, s, ...) ExitTraceSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
#define WiuExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
#define WiuExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
#define WiuExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
#define WiuExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
#define WiuExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
#define WiuExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_WIUTIL, x, s, __VA_ARGS__)
#define WiuExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_WIUTIL, p, x, e, s, __VA_ARGS__)
#define WiuExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_WIUTIL, p, x, s, __VA_ARGS__)
#define WiuExitOnNullDebugTrace(p, x, e, s, ...)  ExitOnNullDebugTraceSource(DUTIL_SOURCE_WIUTIL, p, x, e, s, __VA_ARGS__)
#define WiuExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_WIUTIL, p, x, s, __VA_ARGS__)
#define WiuExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_WIUTIL, e, x, s, __VA_ARGS__)
#define WiuExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_WIUTIL, g, x, s, __VA_ARGS__)


// constants

const DWORD WIU_MSI_PROGRESS_INVALID = 0xFFFFFFFF;
const DWORD WIU_GOOD_ENOUGH_PROPERTY_LENGTH = 64;


// structs


static PFN_MSIENABLELOGW vpfnMsiEnableLogW = ::MsiEnableLogW;
static PFN_MSIGETPRODUCTINFOW vpfnMsiGetProductInfoW = ::MsiGetProductInfoW;
static PFN_MSIQUERYFEATURESTATEW vpfnMsiQueryFeatureStateW = ::MsiQueryFeatureStateW;
static PFN_MSIGETCOMPONENTPATHW vpfnMsiGetComponentPathW = ::MsiGetComponentPathW;
static PFN_MSILOCATECOMPONENTW vpfnMsiLocateComponentW = ::MsiLocateComponentW;
static PFN_MSIINSTALLPRODUCTW vpfnMsiInstallProductW = ::MsiInstallProductW;
static PFN_MSICONFIGUREPRODUCTEXW vpfnMsiConfigureProductExW = ::MsiConfigureProductExW;
static PFN_MSIREMOVEPATCHESW vpfnMsiRemovePatchesW = ::MsiRemovePatchesW;
static PFN_MSISETINTERNALUI vpfnMsiSetInternalUI = ::MsiSetInternalUI;
static PFN_MSISETEXTERNALUIW vpfnMsiSetExternalUIW = ::MsiSetExternalUIW;
static PFN_MSIENUMPRODUCTSW vpfnMsiEnumProductsW = ::MsiEnumProductsW;
static PFN_MSIENUMRELATEDPRODUCTSW vpfnMsiEnumRelatedProductsW = ::MsiEnumRelatedProductsW;

// MSI 3.0+
static PFN_MSIDETERMINEPATCHSEQUENCEW vpfnMsiDeterminePatchSequenceW = NULL;
static PFN_MSIDETERMINEAPPLICABLEPATCHESW vpfnMsiDetermineApplicablePatchesW = NULL;
static PFN_MSIENUMPRODUCTSEXW vpfnMsiEnumProductsExW = NULL;
static PFN_MSIGETPATCHINFOEXW vpfnMsiGetPatchInfoExW = NULL;
static PFN_MSIGETPRODUCTINFOEXW vpfnMsiGetProductInfoExW = NULL;
static PFN_MSISETEXTERNALUIRECORD vpfnMsiSetExternalUIRecord = NULL;
static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExW = NULL;

static HMODULE vhMsiDll = NULL;
static PFN_MSIDETERMINEPATCHSEQUENCEW vpfnMsiDeterminePatchSequenceWFromLibrary = NULL;
static PFN_MSIDETERMINEAPPLICABLEPATCHESW vpfnMsiDetermineApplicablePatchesWFromLibrary = NULL;
static PFN_MSIENUMPRODUCTSEXW vpfnMsiEnumProductsExWFromLibrary = NULL;
static PFN_MSIGETPATCHINFOEXW vpfnMsiGetPatchInfoExWFromLibrary = NULL;
static PFN_MSIGETPRODUCTINFOEXW vpfnMsiGetProductInfoExWFromLibrary = NULL;
static PFN_MSISETEXTERNALUIRECORD vpfnMsiSetExternalUIRecordFromLibrary = NULL;
static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExWFromLibrary = NULL;

// MSI Transactions v4.5+
static PFN_MSIBEGINTRANSACTIONW vpfnMsiBeginTransaction = NULL;
static PFN_MSIENDTRANSACTION vpfnMsiEndTransaction = NULL;

static BOOL vfWiuInitialized = FALSE;

// globals
static DWORD vdwMsiDllMajorMinor = 0;
static DWORD vdwMsiDllBuildRevision = 0;


// internal function declarations

static DWORD CheckForRestartErrorCode(
    __in DWORD dwErrorCode,
    __out WIU_RESTART* pRestart
    );
static INT CALLBACK InstallEngineCallback(
    __in LPVOID pvContext,
    __in UINT uiMessage,
    __in_z_opt LPCWSTR wzMessage
    );
static INT CALLBACK InstallEngineRecordCallback(
    __in LPVOID pvContext,
    __in UINT uiMessage,
    __in_opt MSIHANDLE hRecord
    );
static INT HandleInstallMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in INSTALLMESSAGE mt,
    __in UINT uiFlags,
    __in_z LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    );
static INT HandleInstallProgress(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in_z_opt LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    );
static INT SendMsiMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in INSTALLMESSAGE mt,
    __in UINT uiFlags,
    __in_z LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    );
static INT SendErrorMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in UINT uiFlags,
    __in_z LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    );
static INT SendFilesInUseMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in UINT uiFlags,
    __in_opt MSIHANDLE hRecord,
    __in BOOL fRestartManagerRequest
    );
static INT SendProgressUpdate(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext
    );
static void ResetProgress(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext
    );
static DWORD CalculatePhaseProgress(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in DWORD dwProgressIndex,
    __in DWORD dwWeightPercentage
    );
void InitializeMessageData(
    __in_opt MSIHANDLE hRecord,
    __deref_out_ecount(*pcData) LPWSTR** prgsczData,
    __out DWORD* pcData
    );
void UninitializeMessageData(
    __in LPWSTR* rgsczData,
    __in DWORD cData
    );


/********************************************************************
 WiuInitialize - initializes wiutil

*********************************************************************/
extern "C" HRESULT DAPI WiuInitialize(
    )
{
    HRESULT hr = S_OK;
    LPWSTR sczMsiDllPath = NULL;

    hr = LoadSystemLibraryWithPath(L"Msi.dll", &vhMsiDll, &sczMsiDllPath);
    WiuExitOnFailure(hr, "Failed to load Msi.DLL");

    // Ignore failures
    FileVersion(sczMsiDllPath, &vdwMsiDllMajorMinor, &vdwMsiDllBuildRevision);

    vpfnMsiDeterminePatchSequenceWFromLibrary = reinterpret_cast<PFN_MSIDETERMINEPATCHSEQUENCEW>(::GetProcAddress(vhMsiDll, "MsiDeterminePatchSequenceW"));
    if (NULL == vpfnMsiDeterminePatchSequenceW)
    {
        vpfnMsiDeterminePatchSequenceW = vpfnMsiDeterminePatchSequenceWFromLibrary;
    }

    vpfnMsiDetermineApplicablePatchesWFromLibrary = reinterpret_cast<PFN_MSIDETERMINEAPPLICABLEPATCHESW>(::GetProcAddress(vhMsiDll, "MsiDetermineApplicablePatchesW"));
    if (NULL == vpfnMsiDetermineApplicablePatchesW)
    {
        vpfnMsiDetermineApplicablePatchesW = vpfnMsiDetermineApplicablePatchesWFromLibrary;
    }

    vpfnMsiEnumProductsExWFromLibrary = reinterpret_cast<PFN_MSIENUMPRODUCTSEXW>(::GetProcAddress(vhMsiDll, "MsiEnumProductsExW"));
    if (NULL == vpfnMsiEnumProductsExW)
    {
        vpfnMsiEnumProductsExW = vpfnMsiEnumProductsExWFromLibrary;
    }

    vpfnMsiGetPatchInfoExWFromLibrary = reinterpret_cast<PFN_MSIGETPATCHINFOEXW>(::GetProcAddress(vhMsiDll, "MsiGetPatchInfoExW"));
    if (NULL == vpfnMsiGetPatchInfoExW)
    {
        vpfnMsiGetPatchInfoExW = vpfnMsiGetPatchInfoExWFromLibrary;
    }

    vpfnMsiGetProductInfoExWFromLibrary = reinterpret_cast<PFN_MSIGETPRODUCTINFOEXW>(::GetProcAddress(vhMsiDll, "MsiGetProductInfoExW"));
    if (NULL == vpfnMsiGetProductInfoExW)
    {
        vpfnMsiGetProductInfoExW = vpfnMsiGetProductInfoExWFromLibrary;
    }

    vpfnMsiSetExternalUIRecordFromLibrary = reinterpret_cast<PFN_MSISETEXTERNALUIRECORD>(::GetProcAddress(vhMsiDll, "MsiSetExternalUIRecord"));
    if (NULL == vpfnMsiSetExternalUIRecord)
    {
        vpfnMsiSetExternalUIRecord = vpfnMsiSetExternalUIRecordFromLibrary;
    }

    //static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExW = NULL;
    vpfnMsiSourceListAddSourceExWFromLibrary = reinterpret_cast<PFN_MSISOURCELISTADDSOURCEEXW>(::GetProcAddress(vhMsiDll, "MsiSourceListAddSourceExW"));
    if (NULL == vpfnMsiSourceListAddSourceExW)
    {
        vpfnMsiSourceListAddSourceExW = vpfnMsiSourceListAddSourceExWFromLibrary;
    }

    // MSI Transaction functions
    if (NULL == vpfnMsiBeginTransaction)
    {
        vpfnMsiBeginTransaction = reinterpret_cast<PFN_MSIBEGINTRANSACTIONW>(::GetProcAddress(vhMsiDll, "MsiBeginTransactionW"));
    }

    if (NULL == vpfnMsiEndTransaction)
    {
        vpfnMsiEndTransaction = reinterpret_cast<PFN_MSIENDTRANSACTION>(::GetProcAddress(vhMsiDll, "MsiEndTransaction"));
    }

    vfWiuInitialized = TRUE;

LExit:
    ReleaseStr(sczMsiDllPath);
    return hr;
}


/********************************************************************
 WiuUninitialize - uninitializes wiutil

*********************************************************************/
extern "C" void DAPI WiuUninitialize(
    )
{
    if (vhMsiDll)
    {
        ::FreeLibrary(vhMsiDll);
        vhMsiDll = NULL;
        vpfnMsiSetExternalUIRecordFromLibrary = NULL;
        vpfnMsiGetProductInfoExWFromLibrary = NULL;
        vpfnMsiGetPatchInfoExWFromLibrary = NULL;
        vpfnMsiEnumProductsExWFromLibrary = NULL;
        vpfnMsiDetermineApplicablePatchesWFromLibrary = NULL;
        vpfnMsiDeterminePatchSequenceWFromLibrary = NULL;
        vpfnMsiSourceListAddSourceExWFromLibrary = NULL;
        vpfnMsiBeginTransaction = NULL;
        vpfnMsiEndTransaction = NULL;
    }

    vfWiuInitialized = FALSE;
}


/********************************************************************
 WiuFunctionOverride - overrides the Windows installer functions. Typically used
                       for unit testing.

*********************************************************************/
extern "C" void DAPI WiuFunctionOverride(
    __in_opt PFN_MSIENABLELOGW pfnMsiEnableLogW,
    __in_opt PFN_MSIGETCOMPONENTPATHW pfnMsiGetComponentPathW,
    __in_opt PFN_MSILOCATECOMPONENTW pfnMsiLocateComponentW,
    __in_opt PFN_MSIQUERYFEATURESTATEW pfnMsiQueryFeatureStateW,
    __in_opt PFN_MSIGETPRODUCTINFOW pfnMsiGetProductInfoW,
    __in_opt PFN_MSIGETPRODUCTINFOEXW pfnMsiGetProductInfoExW,
    __in_opt PFN_MSIINSTALLPRODUCTW pfnMsiInstallProductW,
    __in_opt PFN_MSICONFIGUREPRODUCTEXW pfnMsiConfigureProductExW,
    __in_opt PFN_MSISETINTERNALUI pfnMsiSetInternalUI,
    __in_opt PFN_MSISETEXTERNALUIW pfnMsiSetExternalUIW,
    __in_opt PFN_MSIENUMRELATEDPRODUCTSW pfnMsiEnumRelatedProductsW,
    __in_opt PFN_MSISETEXTERNALUIRECORD pfnMsiSetExternalUIRecord,
    __in_opt PFN_MSISOURCELISTADDSOURCEEXW pfnMsiSourceListAddSourceExW
    )
{
    vpfnMsiEnableLogW = pfnMsiEnableLogW ? pfnMsiEnableLogW : ::MsiEnableLogW;
    vpfnMsiGetComponentPathW = pfnMsiGetComponentPathW ? pfnMsiGetComponentPathW : ::MsiGetComponentPathW;
    vpfnMsiLocateComponentW = pfnMsiLocateComponentW ? pfnMsiLocateComponentW : ::MsiLocateComponentW;
    vpfnMsiQueryFeatureStateW = pfnMsiQueryFeatureStateW ? pfnMsiQueryFeatureStateW : ::MsiQueryFeatureStateW;
    vpfnMsiGetProductInfoW = pfnMsiGetProductInfoW ? pfnMsiGetProductInfoW : vpfnMsiGetProductInfoW;
    vpfnMsiInstallProductW = pfnMsiInstallProductW ? pfnMsiInstallProductW : ::MsiInstallProductW;
    vpfnMsiConfigureProductExW = pfnMsiConfigureProductExW ? pfnMsiConfigureProductExW : ::MsiConfigureProductExW;
    vpfnMsiSetInternalUI = pfnMsiSetInternalUI ? pfnMsiSetInternalUI : ::MsiSetInternalUI;
    vpfnMsiSetExternalUIW = pfnMsiSetExternalUIW ? pfnMsiSetExternalUIW : ::MsiSetExternalUIW;
    vpfnMsiEnumRelatedProductsW = pfnMsiEnumRelatedProductsW ? pfnMsiEnumRelatedProductsW : ::MsiEnumRelatedProductsW;
    vpfnMsiGetProductInfoExW = pfnMsiGetProductInfoExW ? pfnMsiGetProductInfoExW : vpfnMsiGetProductInfoExWFromLibrary;
    vpfnMsiSetExternalUIRecord = pfnMsiSetExternalUIRecord ? pfnMsiSetExternalUIRecord : vpfnMsiSetExternalUIRecordFromLibrary;
    vpfnMsiSourceListAddSourceExW = pfnMsiSourceListAddSourceExW ? pfnMsiSourceListAddSourceExW : vpfnMsiSourceListAddSourceExWFromLibrary;
}


extern "C" HRESULT DAPI WiuGetComponentPath(
    __in_z LPCWSTR wzProductCode,
    __in_z LPCWSTR wzComponentId,
    __out INSTALLSTATE* pInstallState,
    __out_z LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;
    DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
    DWORD cchCompare;

    hr = StrAlloc(psczValue, cch);
    WiuExitOnFailure(hr, "Failed to allocate string for component path.");

    cchCompare = cch;
    *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
    if (INSTALLSTATE_MOREDATA == *pInstallState)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for component path.");

        cchCompare = cch;
        *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
    }

    if (INSTALLSTATE_INVALIDARG == *pInstallState)
    {
        hr = E_INVALIDARG;
        WiuExitOnRootFailure(hr, "Invalid argument when getting component path.");
    }
    else if (INSTALLSTATE_UNKNOWN == *pInstallState)
    {
        ExitFunction();
    }

    // If the actual path length is greater than or equal to the original buffer
    // allocate a larger buffer and get the path again, just in case we are
    // missing any part of the path.
    if (cchCompare <= cch)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for component path.");

        *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
    }

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuLocateComponent(
    __in_z LPCWSTR wzComponentId,
    __out INSTALLSTATE* pInstallState,
    __out_z LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;
    DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
    DWORD cchCompare;

    hr = StrAlloc(psczValue, cch);
    WiuExitOnFailure(hr, "Failed to allocate string for component path.");

    cchCompare = cch;
    *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
    if (INSTALLSTATE_MOREDATA == *pInstallState)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for component path.");

        cchCompare = cch;
        *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
    }

    if (INSTALLSTATE_INVALIDARG == *pInstallState)
    {
        hr = E_INVALIDARG;
        WiuExitOnRootFailure(hr, "Invalid argument when locating component.");
    }
    else if (INSTALLSTATE_UNKNOWN == *pInstallState)
    {
        ExitFunction();
    }

    // If the actual path length is greater than or equal to the original buffer
    // allocate a larger buffer and get the path again, just in case we are
    // missing any part of the path.
    if (cchCompare <= cch)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for component path.");

        *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
    }

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuQueryFeatureState(
    __in_z LPCWSTR wzProduct,
    __in_z LPCWSTR wzFeature,
    __out INSTALLSTATE* pInstallState
    )
{
    HRESULT hr = S_OK;

    *pInstallState = vpfnMsiQueryFeatureStateW(wzProduct, wzFeature);
    if (INSTALLSTATE_INVALIDARG == *pInstallState)
    {
        hr = E_INVALIDARG;
        WiuExitOnRootFailure(hr, "Failed to query state of feature: %ls in product: %ls", wzFeature, wzProduct);
    }

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuGetProductInfo(
    __in_z LPCWSTR wzProductCode,
    __in_z LPCWSTR wzProperty,
    __out LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;

    hr = StrAlloc(psczValue, cch);
    WiuExitOnFailure(hr, "Failed to allocate string for product info.");

    er = vpfnMsiGetProductInfoW(wzProductCode, wzProperty, *psczValue, &cch);
    if (ERROR_MORE_DATA == er)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for product info.");

        er = vpfnMsiGetProductInfoW(wzProductCode, wzProperty, *psczValue, &cch);
    }

    if (ERROR_UNKNOWN_PRODUCT == er || ERROR_UNKNOWN_PROPERTY == er)
    {
        ExitFunction1(hr = HRESULT_FROM_WIN32(er));
    }
    WiuExitOnWin32Error(er, hr, "Failed to get product info.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuGetProductInfoEx(
    __in_z LPCWSTR wzProductCode,
    __in_z_opt LPCWSTR wzUserSid,
    __in MSIINSTALLCONTEXT dwContext,
    __in_z LPCWSTR wzProperty,
    __out LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;

    if (!vpfnMsiGetProductInfoExW)
    {
        hr = WiuGetProductInfo(wzProductCode, wzProperty, psczValue);
        if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) != hr && HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) != hr)
        {
            WiuExitOnFailure(hr, "Failed to get product info when extended info was not available.");
        }

        ExitFunction();
    }

    hr = StrAlloc(psczValue, cch);
    WiuExitOnFailure(hr, "Failed to allocate string for extended product info.");

    er = vpfnMsiGetProductInfoExW(wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
    if (ERROR_MORE_DATA == er)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for extended product info.");

        er = vpfnMsiGetProductInfoExW(wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
    }

    if (ERROR_UNKNOWN_PRODUCT == er || ERROR_UNKNOWN_PROPERTY == er)
    {
        ExitFunction1(hr = HRESULT_FROM_WIN32(er));
    }
    WiuExitOnWin32Error(er, hr, "Failed to get extended product info.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuGetProductProperty(
    __in MSIHANDLE hProduct,
    __in_z LPCWSTR wzProperty,
    __out LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;

    hr = StrAlloc(psczValue, cch);
    WiuExitOnFailure(hr, "Failed to allocate string for product property.");

    er = ::MsiGetProductPropertyW(hProduct, wzProperty, *psczValue, &cch);
    if (ERROR_MORE_DATA == er)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for product property.");

        er = ::MsiGetProductPropertyW(hProduct, wzProperty, *psczValue, &cch);
    }
    WiuExitOnWin32Error(er, hr, "Failed to get product property.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuGetPatchInfoEx(
    __in_z LPCWSTR wzPatchCode,
    __in_z LPCWSTR wzProductCode,
    __in_z_opt LPCWSTR wzUserSid,
    __in MSIINSTALLCONTEXT dwContext,
    __in_z LPCWSTR wzProperty,
    __out LPWSTR* psczValue
    )
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;

    if (!vpfnMsiGetPatchInfoExW)
    {
        ExitFunction1(hr = E_NOTIMPL);
    }

    hr = StrAlloc(psczValue, cch);
    WiuExitOnFailure(hr, "Failed to allocate string for extended patch info.");

    er = vpfnMsiGetPatchInfoExW(wzPatchCode, wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
    if (ERROR_MORE_DATA == er)
    {
        ++cch;
        hr = StrAlloc(psczValue, cch);
        WiuExitOnFailure(hr, "Failed to reallocate string for extended patch info.");

        er = vpfnMsiGetPatchInfoExW(wzPatchCode, wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
    }
    WiuExitOnWin32Error(er, hr, "Failed to get extended patch info.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuDeterminePatchSequence(
    __in_z LPCWSTR wzProductCode,
    __in_z_opt LPCWSTR wzUserSid,
    __in MSIINSTALLCONTEXT context,
    __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
    __in DWORD cPatchInfo
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    if (!vpfnMsiDeterminePatchSequenceW)
    {
        ExitFunction1(hr = E_NOTIMPL);
    }

    er = vpfnMsiDeterminePatchSequenceW(wzProductCode, wzUserSid, context, cPatchInfo, pPatchInfo);
    WiuExitOnWin32Error(er, hr, "Failed to determine patch sequence for product code.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuDetermineApplicablePatches(
    __in_z LPCWSTR wzProductPackagePath,
    __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
    __in DWORD cPatchInfo
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    if (!vpfnMsiDetermineApplicablePatchesW)
    {
        ExitFunction1(hr = E_NOTIMPL);
    }

    er = vpfnMsiDetermineApplicablePatchesW(wzProductPackagePath, cPatchInfo, pPatchInfo);
    WiuExitOnWin32Error(er, hr, "Failed to determine applicable patches for product package.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuEnumProducts(
    __in DWORD iProductIndex,
    __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    er = vpfnMsiEnumProductsW(iProductIndex, wzProductCode);
    if (ERROR_NO_MORE_ITEMS == er)
    {
        ExitFunction1(hr = HRESULT_FROM_WIN32(er));
    }
    WiuExitOnWin32Error(er, hr, "Failed to enumerate products.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuEnumProductsEx(
    __in_z_opt LPCWSTR wzProductCode,
    __in_z_opt LPCWSTR wzUserSid,
    __in DWORD dwContext,
    __in DWORD dwIndex,
    __out_opt WCHAR wzInstalledProductCode[39],
    __out_opt MSIINSTALLCONTEXT *pdwInstalledContext,
    __out_opt LPWSTR wzSid,
    __inout_opt LPDWORD pcchSid
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    if (!vpfnMsiEnumProductsExW)
    {
        ExitFunction1(hr = E_NOTIMPL);
    }

    er = vpfnMsiEnumProductsExW(wzProductCode, wzUserSid, dwContext, dwIndex, wzInstalledProductCode, pdwInstalledContext, wzSid, pcchSid);
    if (ERROR_NO_MORE_ITEMS == er)
    {
        ExitFunction1(hr = HRESULT_FROM_WIN32(er));
    }
    WiuExitOnWin32Error(er, hr, "Failed to enumerate products.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuEnumRelatedProducts(
    __in_z LPCWSTR wzUpgradeCode,
    __in DWORD iProductIndex,
    __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    er = vpfnMsiEnumRelatedProductsW(wzUpgradeCode, 0, iProductIndex, wzProductCode);
    if (ERROR_NO_MORE_ITEMS == er)
    {
        ExitFunction1(hr = HRESULT_FROM_WIN32(er));
    }
    WiuExitOnWin32Error(er, hr, "Failed to enumerate related products for updgrade code: %ls", wzUpgradeCode);

LExit:
    return hr;
}

/********************************************************************
 WiuEnumRelatedProductCodes - Returns an array of related products for a given upgrade code.

 Parameters:
 wzUpgradeCode - The upgrade code that will be used to find the related products.
 prgsczProductCodes - Pointer to the array that will contain the product codes.
 pcRelatedProducts - Returns the count of the number of related products found.
 fReturnHighestVersionOnly - When set to "TRUE", will only return the product code of the highest version found.
********************************************************************/
extern "C" HRESULT DAPI WiuEnumRelatedProductCodes(
    __in_z LPCWSTR wzUpgradeCode,
    __deref_out_ecount_opt(*pcRelatedProducts) LPWSTR** prgsczProductCodes,
    __out DWORD* pcRelatedProducts,
    __in BOOL fReturnHighestVersionOnly
    )
{
    HRESULT hr = S_OK;
    WCHAR wzCurrentProductCode[MAX_GUID_CHARS + 1] = { };
    LPWSTR sczInstalledVersion = NULL;
    VERUTIL_VERSION* pCurrentVersion = NULL;
    VERUTIL_VERSION* pHighestVersion = NULL;
    int nCompare = 0;

    // make sure we start at zero
    *pcRelatedProducts = 0;

    for (DWORD i = 0; ; ++i)
    {
        hr = WiuEnumRelatedProducts(wzUpgradeCode, i, wzCurrentProductCode);

        if (E_NOMOREITEMS == hr)
        {
            hr = S_OK;
            break;
        }
        WiuExitOnFailure(hr, "Failed to enumerate related products for upgrade code: %ls", wzUpgradeCode);

        if (fReturnHighestVersionOnly)
        {
            // try to get the version but if the product registration is broken
            // (for whatever reason), skip this product
            hr = WiuGetProductInfo(wzCurrentProductCode, L"VersionString", &sczInstalledVersion);
            if (FAILED(hr))
            {
                WiuExitTrace(hr, "Could not get product version for product code: %ls, skipping...", wzCurrentProductCode);
                continue;
            }

            hr = VerParseVersion(sczInstalledVersion, 0, FALSE, &pCurrentVersion);
            WiuExitOnFailure(hr, "Failed to parse version: %ls for product code: %ls", sczInstalledVersion, wzCurrentProductCode);

            if (pCurrentVersion->fInvalid)
            {
                WiuExitTrace(E_INVALIDDATA, "Enumerated msi package with invalid version, product code: '%1!ls!', version: '%2!ls!'");
            }

            // if this is the first product found then it is the highest version (for now)
            if (!pHighestVersion)
            {
                pHighestVersion = pCurrentVersion;
                pCurrentVersion = NULL;
            }
            else
            {
                hr = VerCompareParsedVersions(pCurrentVersion, pHighestVersion, &nCompare);
                WiuExitOnFailure(hr, "Failed to compare version '%ls' to highest version: '%ls'", pCurrentVersion->sczVersion, pHighestVersion->sczVersion);

                // if this is the highest version encountered so far then overwrite
                // the first item in the array (there will never be more than one item)
                if (nCompare > 0)
                {
                    ReleaseVerutilVersion(pHighestVersion);
                    pHighestVersion = pCurrentVersion;
                    pCurrentVersion = NULL;

                    hr = StrAllocString(prgsczProductCodes[0], wzCurrentProductCode, 0);
                    WiuExitOnFailure(hr, "Failed to update array with higher versioned product code.");
                }
                else
                {
                    ReleaseVerutilVersion(pCurrentVersion);
                }

                // continue here as we don't want anything else added to the list
                continue;
            }
        }

        hr = StrArrayAllocString(prgsczProductCodes, (LPUINT)(pcRelatedProducts), wzCurrentProductCode, 0);
        WiuExitOnFailure(hr, "Failed to add product code to array.");
    }

LExit:
    ReleaseVerutilVersion(pCurrentVersion);
    ReleaseVerutilVersion(pHighestVersion);
    ReleaseStr(sczInstalledVersion);
    return hr;
}


extern "C" HRESULT DAPI WiuEnableLog(
    __in DWORD dwLogMode,
    __in_z LPCWSTR wzLogFile,
    __in DWORD dwLogAttributes
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    er = vpfnMsiEnableLogW(dwLogMode, wzLogFile, dwLogAttributes);
    WiuExitOnWin32Error(er, hr, "Failed to enable MSI internal logging.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuInitializeInternalUI(
    __in INSTALLUILEVEL internalUILevel,
    __in_opt HWND hwndParent,
    __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
    )
{
    HRESULT hr = S_OK;

    memset(pExecuteContext, 0, sizeof(WIU_MSI_EXECUTE_CONTEXT));

    pExecuteContext->previousInstallUILevel = vpfnMsiSetInternalUI(internalUILevel, &hwndParent);
    pExecuteContext->hwndPreviousParentWindow = hwndParent;

    if (INSTALLUILEVEL_NOCHANGE == pExecuteContext->previousInstallUILevel)
    {
        hr = E_INVALIDARG;
    }

    return hr;
}


extern "C" HRESULT DAPI WiuInitializeExternalUI(
    __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler,
    __in INSTALLUILEVEL internalUILevel,
    __in_opt HWND hwndParent,
    __in LPVOID pvContext,
    __in BOOL fRollback,
    __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    DWORD dwMessageFilter = INSTALLLOGMODE_INITIALIZE | INSTALLLOGMODE_TERMINATE |
                            INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING |
                            INSTALLLOGMODE_RESOLVESOURCE | INSTALLLOGMODE_OUTOFDISKSPACE |
                            INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA | INSTALLLOGMODE_COMMONDATA |
                            INSTALLLOGMODE_PROGRESS | INSTALLLOGMODE_FILESINUSE;

    if (MAKEDWORD(0, 4) <= vdwMsiDllMajorMinor)
    {
        dwMessageFilter |= INSTALLLOGMODE_RMFILESINUSE;
    }

    // Wire the internal and external UI handler.
    hr = WiuInitializeInternalUI(internalUILevel, hwndParent, pExecuteContext);
    WiuExitOnFailure(hr, "Failed to set internal UI level and window.");

    pExecuteContext->fRollback = fRollback;
    pExecuteContext->pfnMessageHandler = pfnMessageHandler;
    pExecuteContext->pvContext = pvContext;

    // If the external UI record is available (MSI version >= 3.1) use it but fall back to the standard external
    // UI handler if necesary.
    if (vpfnMsiSetExternalUIRecord)
    {
        er = vpfnMsiSetExternalUIRecord(InstallEngineRecordCallback, dwMessageFilter, pExecuteContext, &pExecuteContext->pfnPreviousExternalUIRecord);
        WiuExitOnWin32Error(er, hr, "Failed to wire up external UI record handler.");
        pExecuteContext->fSetPreviousExternalUIRecord = TRUE;
    }
    else
    {
        pExecuteContext->pfnPreviousExternalUI = vpfnMsiSetExternalUIW(InstallEngineCallback, dwMessageFilter, pExecuteContext);
        pExecuteContext->fSetPreviousExternalUI = TRUE;
    }

LExit:
    return hr;
}


extern "C" void DAPI WiuUninitializeExternalUI(
    __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
    )
{
    if (INSTALLUILEVEL_NOCHANGE != pExecuteContext->previousInstallUILevel)
    {
        pExecuteContext->previousInstallUILevel = vpfnMsiSetInternalUI(pExecuteContext->previousInstallUILevel, &pExecuteContext->hwndPreviousParentWindow);
    }

    if (pExecuteContext->fSetPreviousExternalUI)  // unset the UI handler
    {
        vpfnMsiSetExternalUIW(pExecuteContext->pfnPreviousExternalUI, 0, NULL);
    }

    if (pExecuteContext->fSetPreviousExternalUIRecord)  // unset the UI record handler
    {
        vpfnMsiSetExternalUIRecord(pExecuteContext->pfnPreviousExternalUIRecord, 0, NULL, NULL);
    }

    memset(pExecuteContext, 0, sizeof(WIU_MSI_EXECUTE_CONTEXT));
}


extern "C" HRESULT DAPI WiuConfigureProductEx(
    __in_z LPCWSTR wzProduct,
    __in int iInstallLevel,
    __in INSTALLSTATE eInstallState,
    __in_z LPCWSTR wzCommandLine,
    __out WIU_RESTART* pRestart
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    er = vpfnMsiConfigureProductExW(wzProduct, iInstallLevel, eInstallState, wzCommandLine);
    er = CheckForRestartErrorCode(er, pRestart);
    WiuExitOnWin32Error(er, hr, "Failed to configure product: %ls", wzProduct);

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuInstallProduct(
    __in_z LPCWSTR wzPackagePath,
    __in_z LPCWSTR wzCommandLine,
    __out WIU_RESTART* pRestart
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    er = vpfnMsiInstallProductW(wzPackagePath, wzCommandLine);
    er = CheckForRestartErrorCode(er, pRestart);
    WiuExitOnWin32Error(er, hr, "Failed to install product: %ls", wzPackagePath);

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuRemovePatches(
    __in_z LPCWSTR wzPatchList,
    __in_z LPCWSTR wzProductCode,
    __in_z LPCWSTR wzPropertyList,
    __out WIU_RESTART* pRestart
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    er = vpfnMsiRemovePatchesW(wzPatchList, wzProductCode, INSTALLTYPE_SINGLE_INSTANCE, wzPropertyList);
    er = CheckForRestartErrorCode(er, pRestart);
    WiuExitOnWin32Error(er, hr, "Failed to remove patches.");

LExit:
    return hr;
}


extern "C" HRESULT DAPI WiuSourceListAddSourceEx(
    __in_z LPCWSTR wzProductCodeOrPatchCode,
    __in_z_opt LPCWSTR wzUserSid,
    __in MSIINSTALLCONTEXT dwContext,
    __in DWORD dwCode,
    __in_z LPCWSTR wzSource,
    __in_opt DWORD dwIndex
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    er = vpfnMsiSourceListAddSourceExW(wzProductCodeOrPatchCode, wzUserSid, dwContext, MSISOURCETYPE_NETWORK | dwCode, wzSource, dwIndex);
    WiuExitOnWin32Error(er, hr, "Failed to add source.");

LExit:
    return hr;
}

extern "C" BOOL DAPI WiuIsMsiTransactionSupported(
    )
{
    return vpfnMsiBeginTransaction && vpfnMsiEndTransaction;
}

extern "C" HRESULT DAPI WiuBeginTransaction(
    __in_z LPCWSTR szName,
    __in DWORD dwTransactionAttributes,
    __out MSIHANDLE * phTransactionHandle,
    __out HANDLE * phChangeOfOwnerEvent,
    __in DWORD dwLogMode,
    __in_z LPCWSTR szLogPath
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    if (!WiuIsMsiTransactionSupported())
    {
        WiuExitOnFailure(hr = E_NOTIMPL, "Msi transactions are not supported");
    }

    hr = WiuEnableLog(dwLogMode, szLogPath, INSTALLLOGATTRIBUTES_APPEND);
    WiuExitOnFailure(hr, "Failed to enable logging for MSI transaction");

    er = vpfnMsiBeginTransaction(szName, dwTransactionAttributes, phTransactionHandle, phChangeOfOwnerEvent);
    WiuExitOnWin32Error(er, hr, "Failed to begin transaction.");

LExit:
    return hr;
}

extern "C" HRESULT DAPI WiuEndTransaction(
    __in DWORD dwTransactionState,
    __in DWORD dwLogMode,
    __in_z LPCWSTR szLogPath,
    __out WIU_RESTART *pRestart
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    if (!WiuIsMsiTransactionSupported())
    {
        WiuExitOnFailure(hr = E_NOTIMPL, "Msi transactions are not supported");
    }

    hr = WiuEnableLog(dwLogMode, szLogPath, INSTALLLOGATTRIBUTES_APPEND);
    WiuExitOnFailure(hr, "Failed to enable logging for MSI transaction");

    er = vpfnMsiEndTransaction(dwTransactionState);
    er = CheckForRestartErrorCode(er, pRestart);
    WiuExitOnWin32Error(er, hr, "Failed to end transaction.");

LExit:
    return hr;
}



static DWORD CheckForRestartErrorCode(
    __in DWORD dwErrorCode,
    __out WIU_RESTART* pRestart
    )
{
    switch (dwErrorCode)
    {
    case ERROR_SUCCESS_REBOOT_REQUIRED:
    case ERROR_SUCCESS_RESTART_REQUIRED:
        *pRestart = WIU_RESTART_REQUIRED;
        dwErrorCode = ERROR_SUCCESS;
        break;

    case ERROR_SUCCESS_REBOOT_INITIATED:
    case ERROR_INSTALL_SUSPEND:
        *pRestart = WIU_RESTART_INITIATED;
        dwErrorCode = ERROR_SUCCESS;
        break;

    case ERROR_FAIL_REBOOT_REQUIRED:
        *pRestart = WIU_RESTART_REQUIRED;
        break;

    case ERROR_FAIL_REBOOT_INITIATED:
        *pRestart = WIU_RESTART_INITIATED;
        break;
    }

    return dwErrorCode;
}

static INT CALLBACK InstallEngineCallback(
    __in LPVOID pvContext,
    __in UINT uiMessage,
    __in_z_opt LPCWSTR wzMessage
    )
{
    INT nResult = IDNOACTION;
    WIU_MSI_EXECUTE_CONTEXT* pContext = (WIU_MSI_EXECUTE_CONTEXT*)pvContext;
    INSTALLMESSAGE mt = static_cast<INSTALLMESSAGE>(0xFF000000 & uiMessage);
    UINT uiFlags = 0x00FFFFFF & uiMessage;

    if (wzMessage)
    {
        if (INSTALLMESSAGE_PROGRESS == mt)
        {
            nResult = HandleInstallProgress(pContext, wzMessage, NULL);
        }
        else
        {
            nResult = HandleInstallMessage(pContext, mt, uiFlags, wzMessage, NULL);
        }
    }

    return nResult;
}

static INT CALLBACK InstallEngineRecordCallback(
    __in LPVOID pvContext,
    __in UINT uiMessage,
    __in_opt MSIHANDLE hRecord
    )
{
    INT nResult = IDNOACTION;
    HRESULT hr = S_OK;
    WIU_MSI_EXECUTE_CONTEXT* pContext = (WIU_MSI_EXECUTE_CONTEXT*)pvContext;

    INSTALLMESSAGE mt = static_cast<INSTALLMESSAGE>(0xFF000000 & uiMessage);
    UINT uiFlags = 0x00FFFFFF & uiMessage;
    LPWSTR sczMessage = NULL;
    DWORD cchMessage = 0;

    if (hRecord)
    {
        if (INSTALLMESSAGE_PROGRESS == mt)
        {
            nResult = HandleInstallProgress(pContext, NULL, hRecord);
        }
        else
        {
            // create formated message string
#pragma prefast(push)
#pragma prefast(disable:6298) // docs explicitly say this is a valid option for getting the buffer size
            DWORD er = ::MsiFormatRecordW(NULL, hRecord, L"", &cchMessage);
#pragma prefast(pop)
            if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er)
            {
                hr = StrAlloc(&sczMessage, ++cchMessage);
            }
            else
            {
                hr = HRESULT_FROM_WIN32(er);
            }
            WiuExitOnFailure(hr, "Failed to allocate string for formated message.");

            er = ::MsiFormatRecordW(NULL, hRecord, sczMessage, &cchMessage);
            WiuExitOnWin32Error(er, hr, "Failed to format message record.");

            // Pass to handler including both the formated message and the original record.
            nResult = HandleInstallMessage(pContext, mt, uiFlags, sczMessage, hRecord);
        }
    }

LExit:
    ReleaseStr(sczMessage);
    return nResult;
}

static INT HandleInstallMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in INSTALLMESSAGE mt,
    __in UINT uiFlags,
    __in_z LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    )
{
    INT nResult = IDNOACTION;

Trace(REPORT_STANDARD, "MSI install[%x]: %ls", pContext->dwCurrentProgressIndex, wzMessage);

    // Handle the message.
    switch (mt)
    {
    case INSTALLMESSAGE_INITIALIZE: // this message is received prior to internal UI initialization, no string data
        ResetProgress(pContext);
        break;

    case INSTALLMESSAGE_TERMINATE: // sent after UI termination, no string data
        break;

    case INSTALLMESSAGE_ACTIONSTART:
        if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex && pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData)
        {
            pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
        }

        nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
        break;

    case INSTALLMESSAGE_ACTIONDATA:
        if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex && pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData)
        {
            if (pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward)
            {
                pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted += pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep;
            }
            else // rollback.
            {
                pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted -= pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep;
            }

            nResult = SendProgressUpdate(pContext);
        }
        else
        {
            nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
        }
        break;

    case INSTALLMESSAGE_OUTOFDISKSPACE: __fallthrough;
    case INSTALLMESSAGE_FATALEXIT: __fallthrough;
    case INSTALLMESSAGE_ERROR:
        nResult = SendErrorMessage(pContext, uiFlags, wzMessage, hRecord);
        break;

    case INSTALLMESSAGE_FILESINUSE:
    case INSTALLMESSAGE_RMFILESINUSE:
        nResult = SendFilesInUseMessage(pContext, uiFlags, hRecord, INSTALLMESSAGE_RMFILESINUSE == mt);
        break;

/*
#if 0
    case INSTALLMESSAGE_COMMONDATA:
        if (L'1' == wzMessage[0] && L':' == wzMessage[1] && L' ' == wzMessage[2])
        {
            if (L'0' == wzMessage[3])
            {
                // TODO: handle the language common data message.
                lres = IDOK;
                return lres;
            }
            else if (L'1' == wzMessage[3])
            {
                // TODO: really handle sending the caption.
                lres = ::SendSuxMessage(pInstallContext->pSetupUXInformation, SRM_EXEC_SET_CAPTION, uiFlags, reinterpret_cast<LPARAM>(wzMessage + 3));
                return lres;
            }
            else if (L'2' == wzMessage[3])
            {
                // TODO: really handle sending the cancel button status.
                lres = ::SendSuxMessage(pInstallContext->pSetupUXInformation, SRM_EXEC_SET_CANCEL, uiFlags, reinterpret_cast<LPARAM>(wzMessage + 3));
                return lres;
            }
        }
        break;
#endif
*/

    //case INSTALLMESSAGE_WARNING:
    //case INSTALLMESSAGE_USER:
    //case INSTALLMESSAGE_INFO:
    //case INSTALLMESSAGE_SHOWDIALOG: // sent prior to display of authored dialog or wizard
    default:
        nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
        break;
    }

    // Always return "no action" (0) for resolve source messages.
    return (INSTALLMESSAGE_RESOLVESOURCE == mt) ? IDNOACTION : nResult;
}

static INT HandleInstallProgress(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in_z_opt LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    )
{
    HRESULT hr = S_OK;
    INT nResult = IDNOACTION;
    INT iFields[4] = { };
    INT cFields = 0;
    LPCWSTR pwz = NULL;
    DWORD cch = 0;

    // get field values
    if (hRecord)
    {
        cFields = ::MsiRecordGetFieldCount(hRecord);
        cFields = min(cFields, countof(iFields)); // avoid buffer overrun if there are more fields than our buffer can hold
        for (INT i = 0; i < cFields; ++i)
        {
            iFields[i] = ::MsiRecordGetInteger(hRecord, i + 1);
        }
    }
    else
    {
        Assert(wzMessage);

        // parse message string
        pwz = wzMessage;
        while (cFields < 4)
        {
            // check if we have the start of a valid part
            if ((L'1' + cFields) != pwz[0] || L':' != pwz[1] || L' ' != pwz[2])
            {
                break;
            }
            pwz += 3;

            // find character count of number
            cch = 0;
            while (pwz[cch] && L' ' != pwz[cch])
            {
                ++cch;
            }

            // parse number
            hr = StrStringToInt32(pwz, cch, &iFields[cFields]);
            WiuExitOnFailure(hr, "Failed to parse MSI message part.");

            // increment field count
            ++cFields;
        }
    }

#ifdef _DEBUG
    WCHAR wz[256];
    ::StringCchPrintfW(wz, countof(wz), L"1: %d 2: %d 3: %d 4: %d", iFields[0], iFields[1], iFields[2], iFields[3]);
    Trace(REPORT_STANDARD, "MSI progress[%x]: %ls", pContext->dwCurrentProgressIndex, wz);
#endif

    // Verify that we have the enough field values.
    if (1 > cFields)
    {
        ExitFunction(); // unknown message, bail
    }

    // Handle based on message type.
    switch (iFields[0])
    {
    case 0: // master progress reset
        if (4 > cFields)
        {
            Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
            ExitFunction();
        }
        //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - MASTER RESET - %d, %d, %d", iFields[1], iFields[2], iFields[3]);

        // Update the index into progress array.
        if (WIU_MSI_PROGRESS_INVALID == pContext->dwCurrentProgressIndex)
        {
            pContext->dwCurrentProgressIndex = 0;
        }
        else if (pContext->dwCurrentProgressIndex + 1 < countof(pContext->rgMsiProgress))
        {
            ++pContext->dwCurrentProgressIndex;
        }
        else
        {
            hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
            WiuExitOnRootFailure(hr, "Insufficient space to hold progress information.");
        }

        // we only care about the first stage after script execution has started
        //if (!pEngineInfo->fMsiProgressScriptInProgress && 1 != iFields[3])
        //{
        //    pEngineInfo->fMsiProgressFinished = TRUE;
        //}

        pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal = iFields[1];
        pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted = 0 == iFields[2] ? 0 : iFields[1]; // if forward start at 0, if backwards start at max
        pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward = (0 == iFields[2]);
        pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
        pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fScriptInProgress = (1 == iFields[3]);

        if (0 == pContext->dwCurrentProgressIndex)
        {
            // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase
            // is always off by "about 50".  So we'll toss an extra 50 ticks on so that the standard progress
            // doesn't go over 100%.  If there are any custom actions, they may blow the total so we'll call this
            // "close" and deal with the rest.
            pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal += 50;
        }
        break;

    case 1: // action info.
        if (3 > cFields)
        {
            Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
            ExitFunction();
        }
        //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - ACTION INFO - %d, %d, %d", iFields[1], iFields[2], iFields[3]);

        if (0 == iFields[2])
        {
            pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
        }
        else
        {
            pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = TRUE;
            pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep = iFields[1];
        }
        break;

    case 2: // progress report.
        if (2 > cFields)
        {
            Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
            break;
        }

        //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - PROGRESS REPORT - %d, %d, %d", iFields[1], iFields[2], iFields[3]);

        if (WIU_MSI_PROGRESS_INVALID == pContext->dwCurrentProgressIndex)
        {
            break;
        }
        else if (0 == pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal)
        {
            break;
        }

        // Update progress.
        if (pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward)
        {
            pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted += iFields[1];
        }
        else // rollback.
        {
            pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted -= iFields[1];
        }
        break;

    case 3: // extend the progress bar.
        pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal += iFields[1];
        break;

    default:
        ExitFunction(); // unknown message, bail
    }

    // If we have a valid progress index, send an update.
    if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex)
    {
        nResult = SendProgressUpdate(pContext);
    }

LExit:
    return nResult;
}

static INT SendMsiMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in INSTALLMESSAGE mt,
    __in UINT uiFlags,
    __in_z LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    )
{
    INT nResult = IDNOACTION;
    WIU_MSI_EXECUTE_MESSAGE message = { };
    LPWSTR* rgsczData = NULL;
    DWORD cData = 0;

    InitializeMessageData(hRecord, &rgsczData, &cData);

    message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE;
    message.dwUIHint = uiFlags;
    message.cData = cData;
    message.rgwzData = (LPCWSTR*)rgsczData;
    message.msiMessage.mt = mt;
    message.msiMessage.wzMessage = wzMessage;
    nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);

    UninitializeMessageData(rgsczData, cData);
    return nResult;
}

static INT SendErrorMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in UINT uiFlags,
    __in_z LPCWSTR wzMessage,
    __in_opt MSIHANDLE hRecord
    )
{
    INT nResult = IDNOACTION;
    WIU_MSI_EXECUTE_MESSAGE message = { };
    DWORD dwErrorCode = 0;
    LPWSTR* rgsczData = NULL;
    DWORD cData = 0;

    if (hRecord)
    {
        dwErrorCode = ::MsiRecordGetInteger(hRecord, 1);

        // Set the recommendation if it's a known error code.
        switch (dwErrorCode)
        {
        case 1605: // continue with install even if there isn't enough room for rollback.
            nResult = IDIGNORE;
            break;

        case 1704: // rollback suspended installs so our install can continue.
            nResult = IDOK;
            break;
        }
    }

    InitializeMessageData(hRecord, &rgsczData, &cData);

    message.type = WIU_MSI_EXECUTE_MESSAGE_ERROR;
    message.dwUIHint = uiFlags;
    message.nResultRecommendation = nResult;
    message.cData = cData;
    message.rgwzData = (LPCWSTR*)rgsczData;
    message.error.dwErrorCode = dwErrorCode;
    message.error.wzMessage = wzMessage;
    nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);

    UninitializeMessageData(rgsczData, cData);
    return nResult;
}

static INT SendFilesInUseMessage(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in UINT uiFlags,
    __in_opt MSIHANDLE hRecord,
    __in BOOL fRestartManagerRequest
    )
{
    INT nResult = IDNOACTION;
    WIU_MSI_EXECUTE_MESSAGE message = { };
    LPWSTR* rgsczData = NULL;
    DWORD cData = 0;

    InitializeMessageData(hRecord, &rgsczData, &cData);

    message.type = fRestartManagerRequest ? WIU_MSI_EXECUTE_MESSAGE_MSI_RM_FILES_IN_USE : WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE;
    message.dwUIHint = uiFlags;
    message.cData = cData;
    message.rgwzData = (LPCWSTR*)rgsczData;
    message.msiFilesInUse.cFiles = message.cData;       // point the files in use information to the message record information.
    message.msiFilesInUse.rgwzFiles = message.rgwzData;
    nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);

    UninitializeMessageData(rgsczData, cData);
    return nResult;
}

static INT SendProgressUpdate(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext
    )
{
    int nResult = IDNOACTION;
    DWORD dwPercentage = 0; // number representing 0 - 100%
    WIU_MSI_EXECUTE_MESSAGE message = { };

    //DWORD dwMsiProgressTotal = pEngineInfo->dwMsiProgressTotal;
    //DWORD dwMsiProgressComplete = pEngineInfo->dwMsiProgressComplete; //min(dwMsiProgressTotal, pEngineInfo->dwMsiProgressComplete);
    //double dProgressGauge = 0;
    //double dProgressStageTotal = (double)pEngineInfo->qwProgressStageTotal;

    // Calculate progress for the phases of Windows Installer.
    // TODO: handle upgrade progress which would add another phase.
    dwPercentage += CalculatePhaseProgress(pContext, 0, 15);
    dwPercentage += CalculatePhaseProgress(pContext, 1, 80);
    dwPercentage += CalculatePhaseProgress(pContext, 2, 5);
    dwPercentage = min(dwPercentage, 100); // ensure the percentage never goes over 100%.

    if (pContext->fRollback)
    {
        dwPercentage = 100 - dwPercentage;
    }

    //if (qwTotal) // avoid "divide by zero" if the MSI range is blank.
    //{
    //    // calculate gauge.
    //    double dProgressGauge = static_cast<double>(qwCompleted) / static_cast<double>(qwTotal);
    //    dProgressGauge = (1.0 / (1.0 + exp(3.7 - dProgressGauge * 7.5)) - 0.024127021417669196) / 0.975872978582330804;
    //    qwCompleted = (DWORD)(dProgressGauge * qwTotal);

    //    // calculate progress within range
    //    //qwProgressComplete = (DWORD64)(dwMsiProgressComplete * (dProgressStageTotal / dwMsiProgressTotal));
    //    //qwProgressComplete = min(qwProgressComplete, pEngineInfo->qwProgressStageTotal);
    //}

#ifdef _DEBUG
    DWORD64 qwCompleted = pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted;
    DWORD64 qwTotal = pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal;
    Trace(REPORT_STANDARD, "MSI progress: %I64u/%I64u (%u%%)", qwCompleted, qwTotal, dwPercentage);
    //AssertSz(qwCompleted <= qwTotal, "Completed progress is larger than total progress.");
#endif

    message.type = WIU_MSI_EXECUTE_MESSAGE_PROGRESS;
    message.dwUIHint = MB_OKCANCEL;
    message.progress.dwPercentage = dwPercentage;
    nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);

    return nResult;
}

static void ResetProgress(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext
    )
{
    memset(pContext->rgMsiProgress, 0, sizeof(pContext->rgMsiProgress));
    pContext->dwCurrentProgressIndex = WIU_MSI_PROGRESS_INVALID;
}

static DWORD CalculatePhaseProgress(
    __in WIU_MSI_EXECUTE_CONTEXT* pContext,
    __in DWORD dwProgressIndex,
    __in DWORD dwWeightPercentage
    )
{
    DWORD dwPhasePercentage = 0;

    // If we've already passed this progress index, return the maximum percentage possible (the weight)
    if (dwProgressIndex < pContext->dwCurrentProgressIndex)
    {
        dwPhasePercentage = dwWeightPercentage;
    }
    else if (dwProgressIndex == pContext->dwCurrentProgressIndex) // have to do the math for the current progress.
    {
        WIU_MSI_PROGRESS* pProgress = pContext->rgMsiProgress + dwProgressIndex;
        if (pProgress->dwTotal)
        {
            DWORD64 dw64Completed = pProgress->dwCompleted;
            dwPhasePercentage = static_cast<DWORD>(dw64Completed * dwWeightPercentage / pProgress->dwTotal);
        }
    }
    // else we're not there yet so it has to be zero.

    return dwPhasePercentage;
}

void InitializeMessageData(
    __in_opt MSIHANDLE hRecord,
    __deref_out_ecount(*pcData) LPWSTR** prgsczData,
    __out DWORD* pcData
    )
{
    DWORD cData = 0;
    LPWSTR* rgsczData = NULL;

    // If we have a record based message, try to get the extra data.
    if (hRecord)
    {
        cData = ::MsiRecordGetFieldCount(hRecord);
        if (cData)
        {
            rgsczData = (LPWSTR*)MemAlloc(sizeof(LPWSTR*) * cData, TRUE);
        }

        for (DWORD i = 0; rgsczData && i < cData; ++i)
        {
            DWORD cch = 0;

            // get string from record
#pragma prefast(push)
#pragma prefast(disable:6298)
            DWORD er = ::MsiRecordGetStringW(hRecord, i + 1, L"", &cch);
#pragma prefast(pop)
            if (ERROR_MORE_DATA == er)
            {
                HRESULT hr = StrAlloc(&rgsczData[i], ++cch);
                if (SUCCEEDED(hr))
                {
                    er = ::MsiRecordGetStringW(hRecord, i + 1, rgsczData[i], &cch);
                }
            }
        }
    }

    *prgsczData = rgsczData;
    *pcData = cData;
}

void UninitializeMessageData(
    __in LPWSTR* rgsczData,
    __in DWORD cData
    )
{
    // Clean up if there was any data allocated.
    if (rgsczData)
    {
        for (DWORD i = 0; i < cData; ++i)
        {
            ReleaseStr(rgsczData[i]);
        }

        MemFree(rgsczData);
    }
}