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


// GAC related declarations

typedef struct _FUSION_INSTALL_REFERENCE_
{
    DWORD cbSize;
    DWORD dwFlags;
    GUID guidScheme;
    LPCWSTR szIdentifier;
    LPCWSTR szNonCannonicalData;
} FUSION_INSTALL_REFERENCE;

typedef struct _FUSION_INSTALL_REFERENCE_ *LPFUSION_INSTALL_REFERENCE;

typedef const FUSION_INSTALL_REFERENCE *LPCFUSION_INSTALL_REFERENCE;

typedef struct _ASSEMBLY_INFO
{
    ULONG cbAssemblyInfo;
    DWORD dwAssemblyFlags;
    ULARGE_INTEGER uliAssemblySizeInKB;
    LPWSTR pszCurrentAssemblyPathBuf;
    ULONG cchBuf;
} ASSEMBLY_INFO;

typedef interface IAssemblyCacheItem IAssemblyCacheItem;

MIDL_INTERFACE("e707dcde-d1cd-11d2-bab9-00c04f8eceae")
IAssemblyCache : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE UninstallAssembly( 
        /* [in] */ DWORD dwFlags,
        /* [in] */ LPCWSTR pszAssemblyName,
        /* [in] */ LPCFUSION_INSTALL_REFERENCE pRefData,
        /* [optional][out] */ ULONG *pulDisposition) = 0;

    virtual HRESULT STDMETHODCALLTYPE QueryAssemblyInfo( 
        /* [in] */ DWORD dwFlags,
        /* [in] */ LPCWSTR pszAssemblyName,
        /* [out][in] */ ASSEMBLY_INFO *pAsmInfo) = 0;

    virtual HRESULT STDMETHODCALLTYPE CreateAssemblyCacheItem( 
        /* [in] */ DWORD dwFlags,
        /* [in] */ PVOID pvReserved,
        /* [out] */ IAssemblyCacheItem **ppAsmItem,
        /* [optional][in] */ LPCWSTR pszAssemblyName) = 0;

    virtual HRESULT STDMETHODCALLTYPE CreateAssemblyScavenger( 
        /* [out] */ IUnknown **ppUnkReserved) = 0;

    virtual HRESULT STDMETHODCALLTYPE InstallAssembly( 
        /* [in] */ DWORD dwFlags,
        /* [in] */ LPCWSTR pszManifestFilePath,
        /* [in] */ LPCFUSION_INSTALL_REFERENCE pRefData) = 0;
};

typedef HRESULT (__stdcall *LoadLibraryShimFunc)(LPCWSTR szDllName, LPCWSTR szVersion, LPVOID pvReserved, HMODULE *phModDll);
typedef HRESULT (__stdcall *CreateAssemblyCacheFunc)(IAssemblyCache **ppAsmCache, DWORD dwReserved);


// RegistrationHelper related declarations

static const GUID CLSID_RegistrationHelper =
    { 0x89a86e7b, 0xc229, 0x4008, { 0x9b, 0xaa, 0x2f, 0x5c, 0x84, 0x11, 0xd7, 0xe0 } };

enum eInstallationFlags {
    ifConfigureComponentsOnly = 16,
    ifFindOrCreateTargetApplication = 4,
    ifExpectExistingTypeLib = 1
};


// private structs

struct CPIEXEC_ROLE_ASSIGNMENT
{
    WCHAR wzKey[MAX_DARWIN_KEY + 1];
    WCHAR wzRoleName[MAX_DARWIN_COLUMN + 1];

    CPIEXEC_ROLE_ASSIGNMENT* pNext;
};

struct CPIEXEC_METHOD
{
    WCHAR wzIndex[11 + 1];
    WCHAR wzName[MAX_DARWIN_COLUMN + 1];

    CPI_PROPERTY* pPropertyList;
    CPIEXEC_ROLE_ASSIGNMENT* pRoleAssignmentList;

    CPIEXEC_METHOD* pNext;
};

struct CPIEXEC_INTERFACE
{
    WCHAR wzIID[CPI_MAX_GUID + 1];

    CPI_PROPERTY* pPropertyList;
    CPIEXEC_ROLE_ASSIGNMENT* pRoleAssignmentList;
    CPIEXEC_METHOD* pMethodList;

    CPIEXEC_INTERFACE* pNext;
};

struct CPIEXEC_COMPONENT
{
    WCHAR wzCLSID[CPI_MAX_GUID + 1];

    CPI_PROPERTY* pPropertyList;
    CPIEXEC_ROLE_ASSIGNMENT* pRoleAssignmentList;
    CPIEXEC_INTERFACE* pInterfaceList;

    CPIEXEC_COMPONENT* pNext;
};

struct CPI_ASSEMBLY_ATTRIBUTES
{
    int iActionType;
    int iActionCost;
    LPWSTR pwzKey;
    LPWSTR pwzAssemblyName;
    LPWSTR pwzDllPath;
    LPWSTR pwzTlbPath;
    LPWSTR pwzPSDllPath;
    LPWSTR pwzAppID;
    LPWSTR pwzPartID;
    int iAttributes;
    CPIEXEC_COMPONENT* pCompList;
};

struct CPI_ROLE_ASSIGNMENTS_ATTRIBUTES
{
    int iActionType;
    int iActionCost;
    LPWSTR pwzKey;
    LPWSTR pwzAppID;
    LPWSTR pwzPartID;
    int iRoleCount;
    CPIEXEC_COMPONENT* pCompList;
};


// prototypes for private helper functions

static HRESULT RegisterAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    );
static HRESULT UnregisterAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    );
static void InitAssemblyExec();
static void UninitAssemblyExec();
static HRESULT GetRegistrationHelper(
    IDispatch** ppiRegHlp
    );
static HRESULT GetAssemblyCacheObject(
    IAssemblyCache** ppAssemblyCache
    );
static HRESULT GetAssemblyPathFromGAC(
    LPCWSTR pwzAssemblyName,
    LPWSTR* ppwzAssemblyPath
    );
static HRESULT RegisterDotNetAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    );
static HRESULT RegisterNativeAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    );
static HRESULT UnregisterDotNetAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    );
static HRESULT RemoveComponents(
    ICatalogCollection* piCompColl,
    CPIEXEC_COMPONENT* pCompList
    );
static HRESULT ReadAssemblyAttributes(
    LPWSTR* ppwzData,
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    );
static void FreeAssemblyAttributes(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    );
static HRESULT ReadRoleAssignmentsAttributes(
    LPWSTR* ppwzData,
    CPI_ROLE_ASSIGNMENTS_ATTRIBUTES* pAttrs
    );
static void FreeRoleAssignmentsAttributes(
    CPI_ROLE_ASSIGNMENTS_ATTRIBUTES* pAttrs
    );
static HRESULT ConfigureComponents(
    LPCWSTR pwzPartID,
    LPCWSTR pwzAppID,
    CPIEXEC_COMPONENT* pCompList,
    BOOL fCreate,
    BOOL fProgress
    );
static HRESULT ConfigureInterfaces(
    ICatalogCollection* piCompColl,
    ICatalogObject* piCompObj,
    CPIEXEC_INTERFACE* pIntfList,
    BOOL fCreate
    );
static HRESULT ConfigureMethods(
    ICatalogCollection* piIntfColl,
    ICatalogObject* piIntfObj,
    CPIEXEC_METHOD* pMethList,
    BOOL fCreate
    );
static HRESULT ConfigureRoleAssignments(
    LPCWSTR pwzCollName,
    ICatalogCollection* piCompColl,
    ICatalogObject* piCompObj,
    CPIEXEC_ROLE_ASSIGNMENT* pRoleList,
    BOOL fCreate
    );
static HRESULT ReadComponentList(
    LPWSTR* ppwzData,
    CPIEXEC_COMPONENT** ppCompList
    );
static HRESULT ReadInterfaceList(
    LPWSTR* ppwzData,
    CPIEXEC_INTERFACE** ppIntfList
    );
static HRESULT ReadMethodList(
    LPWSTR* ppwzData,
    CPIEXEC_METHOD** ppMethList
    );
static HRESULT ReadRoleAssignmentList(
    LPWSTR* ppwzData,
    CPIEXEC_ROLE_ASSIGNMENT** ppRoleList
    );
static void FreeComponentList(
    CPIEXEC_COMPONENT* pList
    );
static void FreeInterfaceList(
    CPIEXEC_INTERFACE* pList
    );
static void FreeMethodList(
    CPIEXEC_METHOD* pList
    );
static void FreeRoleAssignmentList(
    CPIEXEC_ROLE_ASSIGNMENT* pList
    );


// variables

static IDispatch* gpiRegHlp;
static IAssemblyCache* gpAssemblyCache;
static HMODULE ghMscoree;
static HMODULE ghFusion;


// function definitions

HRESULT CpiConfigureAssemblies(
    LPWSTR* ppwzData,
    HANDLE hRollbackFile
    )
{
    HRESULT hr = S_OK;

    CPI_ASSEMBLY_ATTRIBUTES attrs;
    ::ZeroMemory(&attrs, sizeof(attrs));

    // initialize
    InitAssemblyExec();

    // read action text
    hr = CpiActionStartMessage(ppwzData, FALSE);
    ExitOnFailure(hr, "Failed to send action start message");

    // get count
    int iCnt = 0;
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read count");

    // write count to rollback file
    hr = CpiWriteIntegerToRollbackFile(hRollbackFile, iCnt);
    ExitOnFailure(hr, "Failed to write count to rollback file");

    for (int i = 0; i < iCnt; i++)
    {
        // read attributes from CustomActionData
        hr = ReadAssemblyAttributes(ppwzData, &attrs);
        ExitOnFailure(hr, "Failed to read assembly attributes");

        // write key to rollback file
        hr = CpiWriteKeyToRollbackFile(hRollbackFile, attrs.pwzKey);
        ExitOnFailure(hr, "Failed to write key to rollback file");

        // action
        switch (attrs.iActionType)
        {
        case atCreate:
            hr = RegisterAssembly(&attrs);
            ExitOnFailure(hr, "Failed to register assembly, key: %S", attrs.pwzKey);
            break;
        case atRemove:
            hr = UnregisterAssembly(&attrs);
            ExitOnFailure(hr, "Failed to unregister assembly, key: %S", attrs.pwzKey);
            break;
        default:
            hr = S_OK;
            break;
        }

        if (S_FALSE == hr)
            ExitFunction(); // aborted by user

        // write completion status to rollback file
        hr = CpiWriteIntegerToRollbackFile(hRollbackFile, 1);
        ExitOnFailure(hr, "Failed to write completion status to rollback file");

        // progress
        hr = WcaProgressMessage(attrs.iActionCost, FALSE);
        ExitOnFailure(hr, "Failed to update progress");
    }

    hr = S_OK;

LExit:
    // clean up
    FreeAssemblyAttributes(&attrs);

    // uninitialize
    UninitAssemblyExec();

    return hr;
}

HRESULT CpiRollbackConfigureAssemblies(
    LPWSTR* ppwzData,
    CPI_ROLLBACK_DATA* pRollbackDataList
    )
{
    HRESULT hr = S_OK;

    int iRollbackStatus;

    CPI_ASSEMBLY_ATTRIBUTES attrs;
    ::ZeroMemory(&attrs, sizeof(attrs));

    // initialize
    InitAssemblyExec();

    // read action text
    hr = CpiActionStartMessage(ppwzData, NULL == pRollbackDataList);
    ExitOnFailure(hr, "Failed to send action start message");

    // get count
    int iCnt = 0;
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read count");

    for (int i = 0; i < iCnt; i++)
    {
        // read attributes from CustomActionData
        hr = ReadAssemblyAttributes(ppwzData, &attrs);
        ExitOnFailure(hr, "Failed to read assembly attributes");

        // rollback status
        hr = CpiFindRollbackStatus(pRollbackDataList, attrs.pwzKey, &iRollbackStatus);

        if (S_FALSE == hr)
            continue; // not found, nothing to rollback

        // action
        switch (attrs.iActionType)
        {
        case atCreate:
            hr = RegisterAssembly(&attrs);
            if (FAILED(hr))
                WcaLog(LOGMSG_STANDARD, "Failed to register assembly, hr: 0x%x, key: %S", hr, attrs.pwzKey);
            break;
        case atRemove:
            hr = UnregisterAssembly(&attrs);
            if (FAILED(hr))
                WcaLog(LOGMSG_STANDARD, "Failed to unregister assembly, hr: 0x%x, key: %S", hr, attrs.pwzKey);
            break;
        }

        // check rollback status
        if (0 == iRollbackStatus)
            continue; // operation did not complete, skip progress

        // progress
        hr = WcaProgressMessage(attrs.iActionCost, FALSE);
        ExitOnFailure(hr, "Failed to update progress");
    }

    hr = S_OK;

LExit:
    // clean up
    FreeAssemblyAttributes(&attrs);

    // uninitialize
    UninitAssemblyExec();

    return hr;
}

HRESULT CpiConfigureRoleAssignments(
    LPWSTR* ppwzData,
    HANDLE hRollbackFile
    )
{
    HRESULT hr = S_OK;

    CPI_ROLE_ASSIGNMENTS_ATTRIBUTES attrs;
    ::ZeroMemory(&attrs, sizeof(attrs));

    // read action text
    hr = CpiActionStartMessage(ppwzData, FALSE);
    ExitOnFailure(hr, "Failed to send action start message");

    // get count
    int iCnt = 0;
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read count");

    // write count to rollback file
    hr = CpiWriteIntegerToRollbackFile(hRollbackFile, iCnt);
    ExitOnFailure(hr, "Failed to write count to rollback file");

    for (int i = 0; i < iCnt; i++)
    {
        // read attributes from CustomActionData
        hr = ReadRoleAssignmentsAttributes(ppwzData, &attrs);
        ExitOnFailure(hr, "Failed to read role assignments attributes");

        // write key to rollback file
        hr = CpiWriteKeyToRollbackFile(hRollbackFile, attrs.pwzKey);
        ExitOnFailure(hr, "Failed to write key to rollback file");

        // action
        if (atNoOp != attrs.iActionType)
        {
            hr = ConfigureComponents(attrs.pwzPartID, attrs.pwzAppID, attrs.pCompList, atCreate == attrs.iActionType, TRUE);
            ExitOnFailure(hr, "Failed to configure components");

            if (S_FALSE == hr)
                ExitFunction(); // aborted by user
        }

        // write completion status to rollback file
        hr = CpiWriteIntegerToRollbackFile(hRollbackFile, 1);
        ExitOnFailure(hr, "Failed to write completion status to rollback file");

        // progress
        hr = WcaProgressMessage(attrs.iActionCost * attrs.iRoleCount, FALSE);
        ExitOnFailure(hr, "Failed to update progress");
    }

    hr = S_OK;

LExit:
    // clean up
    FreeRoleAssignmentsAttributes(&attrs);

    return hr;
}

HRESULT CpiRollbackConfigureRoleAssignments(
    LPWSTR* ppwzData,
    CPI_ROLLBACK_DATA* pRollbackDataList
    )
{
    HRESULT hr = S_OK;

    int iRollbackStatus;

    CPI_ROLE_ASSIGNMENTS_ATTRIBUTES attrs;
    ::ZeroMemory(&attrs, sizeof(attrs));

    // read action text
    hr = CpiActionStartMessage(ppwzData, NULL == pRollbackDataList);
    ExitOnFailure(hr, "Failed to send action start message");

    // get count
    int iCnt = 0;
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read count");

    for (int i = 0; i < iCnt; i++)
    {
        // read attributes from CustomActionData
        hr = ReadRoleAssignmentsAttributes(ppwzData, &attrs);
        ExitOnFailure(hr, "Failed to read role assignments attributes");

        // rollback status
        hr = CpiFindRollbackStatus(pRollbackDataList, attrs.pwzKey, &iRollbackStatus);

        if (S_FALSE == hr)
            continue; // not found, nothing to rollback

        // action
        if (atNoOp != attrs.iActionType)
        {
            hr = ConfigureComponents(attrs.pwzPartID, attrs.pwzAppID, attrs.pCompList, atCreate == attrs.iActionType, TRUE);
            ExitOnFailure(hr, "Failed to configure components");

            if (S_FALSE == hr)
                ExitFunction(); // aborted by user
        }

        // check rollback status
        if (0 == iRollbackStatus)
            continue; // operation did not complete, skip progress

        // progress
        hr = WcaProgressMessage(attrs.iActionCost * attrs.iRoleCount, FALSE);
        ExitOnFailure(hr, "Failed to update progress");
    }

    hr = S_OK;

LExit:
    // clean up
    FreeRoleAssignmentsAttributes(&attrs);

    return hr;
}


// helper function definitions

static HRESULT RegisterAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    )
{
    HRESULT hr = S_OK;

    // progress message
    hr = CpiActionDataMessage(1, (pAttrs->iAttributes & aaPathFromGAC) ? pAttrs->pwzAssemblyName : pAttrs->pwzDllPath);
    ExitOnFailure(hr, "Failed to send progress messages");

    if (S_FALSE == hr)
        ExitFunction(); // aborted by user

    // log
    WcaLog(LOGMSG_VERBOSE, "Registering assembly, key: %S", pAttrs->pwzKey);

    // extract path from GAC
    if (pAttrs->iAttributes & aaPathFromGAC)
    {
        hr = GetAssemblyPathFromGAC(pAttrs->pwzAssemblyName, &pAttrs->pwzDllPath);
        if (S_FALSE == hr)
            hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        ExitOnFailure(hr, "Failed to get path for assembly from GAC");

        // log
        WcaLog(LOGMSG_VERBOSE, "Assembly path extracted from GAC, key: %S, path: '%S'", pAttrs->pwzKey, pAttrs->pwzDllPath);
    }

    // .net assembly
    if (pAttrs->iAttributes & aaDotNetAssembly)
    {
        hr = RegisterDotNetAssembly(pAttrs);
        ExitOnFailure(hr, "Failed to register .NET assembly");
    }

    // native assembly
    else
    {
        hr = RegisterNativeAssembly(pAttrs);
        ExitOnFailure(hr, "Failed to register native assembly");
    }

    // configure components
    if (pAttrs->pCompList)
    {
        hr = ConfigureComponents(pAttrs->pwzPartID, pAttrs->pwzAppID, pAttrs->pCompList, TRUE, FALSE);
        ExitOnFailure(hr, "Failed to configure components");
    }

    hr = S_OK;

LExit:
    return hr;
}

static HRESULT UnregisterAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    )
{
    HRESULT hr = S_OK;

    long lChanges = 0;

    ICatalogCollection* piColl = NULL;
    ICatalogObject* piObj = NULL;

    // progress message
    hr = CpiActionDataMessage(1, (pAttrs->iAttributes & aaPathFromGAC) ? pAttrs->pwzAssemblyName : pAttrs->pwzDllPath);
    ExitOnFailure(hr, "Failed to send progress messages");

    if (S_FALSE == hr)
        ExitFunction(); // aborted by user

    // log
    WcaLog(LOGMSG_VERBOSE, "Unregistering assembly, key: %S", pAttrs->pwzKey);

    // extract path from GAC
    if (pAttrs->iAttributes & aaPathFromGAC)
    {
        hr = GetAssemblyPathFromGAC(pAttrs->pwzAssemblyName, &pAttrs->pwzDllPath);
        ExitOnFailure(hr, "Failed to get path for assembly from GAC");

        if (S_FALSE == hr)
        {
            WcaLog(LOGMSG_VERBOSE, "Unable to locate assembly in GAC, assembly will not be unregistered from COM+, key: %S", pAttrs->pwzKey);
            ExitFunction1(hr = S_OK);
        }

        // log
        WcaLog(LOGMSG_VERBOSE, "Assembly path extracted from GAC, key: %S, path: '%S'", pAttrs->pwzKey, pAttrs->pwzDllPath);
    }

    // .NET assembly
    if (pAttrs->iAttributes & aaDotNetAssembly)
    {
        if (pAttrs->pwzAppID && *pAttrs->pwzAppID)
        {
            // When unregistering a .net assembly using the RegistrationHelper class, and the application is
            // left empty after all components in the assembly are removed, the RegistrationHelper class also
            // attempts to remove the application for some reason. However, it does not handle the situation
            // when the application has its deleteable property set to false, and will simply fail if this is
            // the case. This is the reason we are clearing the deleatable property of the application here.
            //
            // TODO: handle rollbacks

            // get applications collection
            hr = CpiExecGetApplicationsCollection(pAttrs->pwzPartID, &piColl);
            ExitOnFailure(hr, "Failed to get applications collection");

            if (S_FALSE == hr)
            {
                // applications collection not found
                WcaLog(LOGMSG_VERBOSE, "Unable to retrieve applications collection, nothing to delete, key: %S", pAttrs->pwzKey);
                ExitFunction1(hr = S_OK);
            }

            // find application object
            hr = CpiFindCollectionObjectByStringKey(piColl, pAttrs->pwzAppID, &piObj);
            ExitOnFailure(hr, "Failed to find application object");

            if (S_FALSE == hr)
            {
                // application not found
                WcaLog(LOGMSG_VERBOSE, "Unable to find application object, nothing to delete, key: %S", pAttrs->pwzKey);
                ExitFunction1(hr = S_OK);
            }

            // reset deleteable property
            hr = CpiResetObjectProperty(piColl, piObj, L"Deleteable");
            ExitOnFailure(hr, "Failed to reset deleteable property");
        }

        // unregister assembly
        hr = UnregisterDotNetAssembly(pAttrs);
        ExitOnFailure(hr, "Failed to unregister .NET assembly");
    }

    // native assembly
    else
    {
        // get components collection
        hr = CpiGetComponentsCollection(pAttrs->pwzPartID, pAttrs->pwzAppID, &piColl);
        ExitOnFailure(hr, "Failed to get components collection");

        if (S_FALSE == hr)
        {
            // components collection not found
            WcaLog(LOGMSG_VERBOSE, "Unable to retrieve components collection, nothing to delete, key: %S", pAttrs->pwzKey);
            ExitFunction1(hr = S_OK);
        }

        // remove components
        hr = RemoveComponents(piColl, pAttrs->pCompList);
        ExitOnFailure(hr, "Failed to get remove components");

        // save changes
        hr = piColl->SaveChanges(&lChanges);
        if (COMADMIN_E_OBJECTERRORS == hr)
            CpiLogCatalogErrorInfo();
        ExitOnFailure(hr, "Failed to save changes");
    }

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piColl);
    ReleaseObject(piObj);

    return hr;
}

static void InitAssemblyExec()
{
    gpiRegHlp = NULL;
    gpAssemblyCache = NULL;
    ghMscoree = NULL;
    ghFusion = NULL;
}

static void UninitAssemblyExec()
{
    ReleaseObject(gpiRegHlp);
    ReleaseObject(gpAssemblyCache);
    if (ghFusion)
        ::FreeLibrary(ghFusion);
    if (ghMscoree)
        ::FreeLibrary(ghMscoree);
}

static HRESULT GetRegistrationHelper(
    IDispatch** ppiRegHlp
    )
{
    HRESULT hr = S_OK;

    if (!gpiRegHlp)
    {
        // create registration helper object
        hr = ::CoCreateInstance(CLSID_RegistrationHelper, NULL, CLSCTX_ALL, IID_IDispatch, (void**)&gpiRegHlp); 
        ExitOnFailure(hr, "Failed to create registration helper object");
    }

    gpiRegHlp->AddRef();
    *ppiRegHlp = gpiRegHlp;

    hr = S_OK;

LExit:
    return hr;
}

static HRESULT GetAssemblyCacheObject(
    IAssemblyCache** ppAssemblyCache
    )
{
    HRESULT hr = S_OK;

    if (!gpAssemblyCache)
    {
        // mscoree.dll
        if (!ghMscoree)
        {
            // load mscoree.dll
            ghMscoree = ::LoadLibraryW(L"mscoree.dll");
            ExitOnNull(ghMscoree, hr, E_FAIL, "Failed to load mscoree.dll");
        }

        // fusion.dll
        if (!ghFusion)
        {
            // get LoadLibraryShim function address
            LoadLibraryShimFunc pfnLoadLibraryShim = (LoadLibraryShimFunc)::GetProcAddress(ghMscoree, "LoadLibraryShim");
            ExitOnNull(pfnLoadLibraryShim, hr, HRESULT_FROM_WIN32(::GetLastError()), "Failed get address for LoadLibraryShim() function");

            // load fusion.dll
            hr = pfnLoadLibraryShim(L"fusion.dll", NULL, NULL, &ghFusion);
            ExitOnFailure(hr, "Failed to load fusion.dll");
        }

        // get CreateAssemblyCache function address
        CreateAssemblyCacheFunc pfnCreateAssemblyCache = (CreateAssemblyCacheFunc)::GetProcAddress(ghFusion, "CreateAssemblyCache");
        ExitOnNull(pfnCreateAssemblyCache, hr, HRESULT_FROM_WIN32(::GetLastError()), "Failed get address for CreateAssemblyCache() function");

        // create AssemblyCache object
        hr = pfnCreateAssemblyCache(&gpAssemblyCache, 0);
        ExitOnFailure(hr, "Failed to create AssemblyCache object");
    }

    gpAssemblyCache->AddRef();
    *ppAssemblyCache = gpAssemblyCache;

    hr = S_OK;

LExit:
    return hr;
}

static HRESULT GetAssemblyPathFromGAC(
    LPCWSTR pwzAssemblyName,
    LPWSTR* ppwzAssemblyPath
    )
{
    HRESULT hr = S_OK;

    IAssemblyCache* pAssemblyCache = NULL;

    ASSEMBLY_INFO assemblyInfo;
    WCHAR wzPathBuf[MAX_PATH];

    ::ZeroMemory(&assemblyInfo, sizeof(ASSEMBLY_INFO));
    ::ZeroMemory(wzPathBuf, countof(wzPathBuf));

    // get AssemblyCache object
    hr = GetAssemblyCacheObject(&pAssemblyCache);
    ExitOnFailure(hr, "Failed to get AssemblyCache object");

    // get assembly info
    assemblyInfo.cbAssemblyInfo = sizeof(ASSEMBLY_INFO);
    assemblyInfo.pszCurrentAssemblyPathBuf = wzPathBuf;
    assemblyInfo.cchBuf = countof(wzPathBuf);

    hr = pAssemblyCache->QueryAssemblyInfo(0, pwzAssemblyName, &assemblyInfo);
    if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
        ExitFunction1(hr = S_FALSE);
    ExitOnFailure(hr, "Failed to get assembly info");

    // copy assembly path
    hr = StrAllocString(ppwzAssemblyPath, wzPathBuf, 0);
    ExitOnFailure(hr, "Failed to copy assembly path");

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(pAssemblyCache);

    return hr;
}

static HRESULT RegisterDotNetAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    )
{
    HRESULT hr = S_OK;

    IDispatch* piRegHlp = NULL;

    DISPID dispid;
    BSTR bstrMember = NULL;

    long lInstallationFlags = 0;

    VARIANTARG rgvarg[5];
    DISPPARAMS dispparams;
    EXCEPINFO excepInfo;

    BSTR bstrPartName = NULL;
    BSTR bstrAppName = NULL;
    BSTR bstrDllPath = NULL;
    BSTR bstrTlbPath = NULL;

    ::ZeroMemory(rgvarg, sizeof(rgvarg));
    ::ZeroMemory(&dispparams, sizeof(dispparams));
    ::ZeroMemory(&excepInfo, sizeof(excepInfo));

    bstrMember = ::SysAllocString(L"InstallAssembly_2");
    ExitOnNull(bstrMember, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for method name");

    // create BSTRs for parameters
    if (pAttrs->pwzPartID && *pAttrs->pwzPartID)
    {
        bstrPartName = ::SysAllocString(pAttrs->pwzPartID);
        ExitOnNull(bstrPartName, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for partition id");
    }

    if (pAttrs->pwzAppID && *pAttrs->pwzAppID)
    {
        bstrAppName = ::SysAllocString(pAttrs->pwzAppID);
        ExitOnNull(bstrAppName, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for application id");
    }

    bstrDllPath = ::SysAllocString(pAttrs->pwzDllPath);
    ExitOnNull(bstrDllPath, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for dll path");

    if (pAttrs->pwzTlbPath && *pAttrs->pwzTlbPath)
    {
        bstrTlbPath = ::SysAllocString(pAttrs->pwzTlbPath);
        ExitOnNull(bstrTlbPath, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for tlb path");
    }

    // get registration helper object
    hr = GetRegistrationHelper(&piRegHlp);
    ExitOnFailure(hr, "Failed to get registration helper object");

    // get dispatch id of InstallAssembly() method
    hr = piRegHlp->GetIDsOfNames(IID_NULL, &bstrMember, 1, LOCALE_USER_DEFAULT, &dispid);
    ExitOnFailure(hr, "Failed to get dispatch id of InstallAssembly() method");

    // set installation flags
    lInstallationFlags = ifExpectExistingTypeLib;

    if (!bstrAppName)
        lInstallationFlags |= ifFindOrCreateTargetApplication;

    // invoke InstallAssembly() method
    rgvarg[0].vt = VT_I4;
    rgvarg[0].lVal = lInstallationFlags;
    rgvarg[1].vt = VT_BYREF|VT_BSTR;
    rgvarg[1].pbstrVal = &bstrTlbPath;
    rgvarg[2].vt = VT_BSTR;
    rgvarg[2].bstrVal = bstrPartName;
    rgvarg[3].vt = VT_BYREF|VT_BSTR;
    rgvarg[3].pbstrVal = &bstrAppName;
    rgvarg[4].vt = VT_BSTR;
    rgvarg[4].bstrVal = bstrDllPath;
    dispparams.rgvarg = rgvarg;
    dispparams.cArgs = 5;
    dispparams.cNamedArgs = 0;

    hr = piRegHlp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, &excepInfo, NULL);
    if (DISP_E_EXCEPTION == hr)
    {
        // log exception information
        if (!excepInfo.pfnDeferredFillIn || (excepInfo.pfnDeferredFillIn && SUCCEEDED(excepInfo.pfnDeferredFillIn(&excepInfo))))
        {
            WcaLog(LOGMSG_STANDARD, "ExceptionInfo: Code='%hu', Source='%S', Description='%S', HelpFile='%S', HelpContext='%u'",
                excepInfo.wCode, excepInfo.bstrSource,
                excepInfo.bstrDescription ? excepInfo.bstrDescription : L"",
                excepInfo.bstrHelpFile ? excepInfo.bstrHelpFile : L"",
                excepInfo.dwHelpContext);
        }
    }
    ExitOnFailure(hr, "Failed to invoke RegistrationHelper.InstallAssembly() method");

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piRegHlp);

    ReleaseBSTR(bstrMember);

    ReleaseBSTR(excepInfo.bstrSource);
    ReleaseBSTR(excepInfo.bstrDescription);
    ReleaseBSTR(excepInfo.bstrHelpFile);

    ReleaseBSTR(bstrPartName);
    ReleaseBSTR(bstrAppName);
    ReleaseBSTR(bstrDllPath);
    ReleaseBSTR(bstrTlbPath);

    return hr;
}

static HRESULT RegisterNativeAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    )
{
    HRESULT hr = S_OK;

    ICOMAdminCatalog* piCatalog = NULL;
    ICOMAdminCatalog2* piCatalog2 = NULL;
    BSTR bstrGlobPartID = NULL;

    BSTR bstrPartID = NULL;
    BSTR bstrAppID = NULL;
    BSTR bstrDllPath = NULL;
    BSTR bstrTlbPath = NULL;
    BSTR bstrPSDllPath = NULL;

    // create BSTRs for parameters
    if (pAttrs->pwzPartID && *pAttrs->pwzPartID)
    {
        bstrPartID = ::SysAllocString(pAttrs->pwzPartID);
        ExitOnNull(bstrPartID, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for partition id");
    }

    bstrAppID = ::SysAllocString(pAttrs->pwzAppID);
    ExitOnNull(bstrAppID, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for application id");

    bstrDllPath = ::SysAllocString(pAttrs->pwzDllPath);
    ExitOnNull(bstrDllPath, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for dll path");

    bstrTlbPath = ::SysAllocString(pAttrs->pwzTlbPath ? pAttrs->pwzTlbPath : L"");
    ExitOnNull(bstrTlbPath, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for tlb path");

    bstrPSDllPath = ::SysAllocString(pAttrs->pwzPSDllPath ? pAttrs->pwzPSDllPath : L"");
    ExitOnNull(bstrPSDllPath, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for tlb path");

    // get catalog
    hr = CpiExecGetAdminCatalog(&piCatalog);
    ExitOnFailure(hr, "Failed to get COM+ admin catalog");

    // get ICOMAdminCatalog2 interface
    hr = piCatalog->QueryInterface(IID_ICOMAdminCatalog2, (void**)&piCatalog2);

    // COM+ 1.5 or later
    if (E_NOINTERFACE != hr)
    {
        ExitOnFailure(hr, "Failed to get IID_ICOMAdminCatalog2 interface");

        // partition id
        if (!bstrPartID)
        {
            // get global partition id
            hr = piCatalog2->get_GlobalPartitionID(&bstrGlobPartID);
            ExitOnFailure(hr, "Failed to get global partition id");
        }

        // set current partition
        hr = piCatalog2->put_CurrentPartition(bstrPartID ? bstrPartID : bstrGlobPartID);
        ExitOnFailure(hr, "Failed to set current partition");
    }

    // COM+ pre 1.5
    else
    {
        // this version of COM+ does not support partitions, make sure a partition was not specified
        if (bstrPartID)
            ExitOnFailure(hr = E_FAIL, "Partitions are not supported by this version of COM+");
    }

    // install event classes
    if (pAttrs->iAttributes & aaEventClass)
    {
        hr = piCatalog->InstallEventClass(bstrAppID, bstrDllPath, bstrTlbPath, bstrPSDllPath);
        if (COMADMIN_E_OBJECTERRORS == hr)
            CpiLogCatalogErrorInfo();
        ExitOnFailure(hr, "Failed to install event classes");
    }

    // install components
    else
    {
        hr = piCatalog->InstallComponent(bstrAppID, bstrDllPath, bstrTlbPath, bstrPSDllPath);
        if (COMADMIN_E_OBJECTERRORS == hr)
            CpiLogCatalogErrorInfo();
        ExitOnFailure(hr, "Failed to install components");
    }

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piCatalog);
    ReleaseObject(piCatalog2);
    ReleaseBSTR(bstrGlobPartID);

    ReleaseBSTR(bstrPartID);
    ReleaseBSTR(bstrAppID);
    ReleaseBSTR(bstrDllPath);
    ReleaseBSTR(bstrTlbPath);
    ReleaseBSTR(bstrPSDllPath);

    return hr;
}

static HRESULT UnregisterDotNetAssembly(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    )
{
    HRESULT hr = S_OK;

    IDispatch* piRegHlp = NULL;

    DISPID dispid;
    BSTR bstrMember = NULL;

    VARIANTARG rgvarg[3];
    DISPPARAMS dispparams;
    EXCEPINFO excepInfo;

    BSTR bstrPartName = NULL;
    BSTR bstrAppName = NULL;
    BSTR bstrDllPath = NULL;

    ::ZeroMemory(rgvarg, sizeof(rgvarg));
    ::ZeroMemory(&dispparams, sizeof(dispparams));
    ::ZeroMemory(&excepInfo, sizeof(excepInfo));

    bstrMember = ::SysAllocString(L"UninstallAssembly_2");
    ExitOnNull(bstrMember, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for method name");

    // create BSTRs for parameters
    if (pAttrs->pwzPartID && *pAttrs->pwzPartID)
    {
        bstrPartName = ::SysAllocString(pAttrs->pwzPartID);
        ExitOnNull(bstrPartName, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for partition id");
    }

    bstrAppName = ::SysAllocString(pAttrs->pwzAppID);
    ExitOnNull(bstrAppName, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for application id");

    bstrDllPath = ::SysAllocString(pAttrs->pwzDllPath);
    ExitOnNull(bstrDllPath, hr, E_OUTOFMEMORY, "Failed to allocate BSTR for dll path");

    // get registration helper object
    hr = GetRegistrationHelper(&piRegHlp);
    ExitOnFailure(hr, "Failed to get registration helper object");

    // get dispatch id of UninstallAssembly() method
    hr = piRegHlp->GetIDsOfNames(IID_NULL, &bstrMember, 1, LOCALE_USER_DEFAULT, &dispid);
    ExitOnFailure(hr, "Failed to get dispatch id of UninstallAssembly() method");

    // invoke UninstallAssembly() method
    rgvarg[0].vt = VT_BSTR;
    rgvarg[0].bstrVal = bstrPartName;
    rgvarg[1].vt = VT_BSTR;
    rgvarg[1].bstrVal = bstrAppName;
    rgvarg[2].vt = VT_BSTR;
    rgvarg[2].bstrVal = bstrDllPath;
    dispparams.rgvarg = rgvarg;
    dispparams.cArgs = 3;
    dispparams.cNamedArgs = 0;

    hr = piRegHlp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, &excepInfo, NULL);
    if (DISP_E_EXCEPTION == hr)
    {
        // log exception information
        if (!excepInfo.pfnDeferredFillIn || (excepInfo.pfnDeferredFillIn && SUCCEEDED(excepInfo.pfnDeferredFillIn(&excepInfo))))
        {
            WcaLog(LOGMSG_STANDARD, "ExceptionInfo: Code='%hu', Source='%S', Description='%S', HelpFile='%S', HelpContext='%u'",
                excepInfo.wCode, excepInfo.bstrSource,
                excepInfo.bstrDescription ? excepInfo.bstrDescription : L"",
                excepInfo.bstrHelpFile ? excepInfo.bstrHelpFile : L"",
                excepInfo.dwHelpContext);
        }
    }
    ExitOnFailure(hr, "Failed to invoke RegistrationHelper.UninstallAssembly() method");

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piRegHlp);

    ReleaseBSTR(bstrMember);

    ReleaseBSTR(excepInfo.bstrSource);
    ReleaseBSTR(excepInfo.bstrDescription);
    ReleaseBSTR(excepInfo.bstrHelpFile);

    ReleaseBSTR(bstrPartName);
    ReleaseBSTR(bstrAppName);
    ReleaseBSTR(bstrDllPath);

    return hr;
}

static HRESULT RemoveComponents(
    ICatalogCollection* piCompColl,
    CPIEXEC_COMPONENT* pCompList
    )
{
    HRESULT hr = S_OK;

    for (CPIEXEC_COMPONENT* pItm = pCompList; pItm; pItm = pItm->pNext)
    {
        // remove
        hr = CpiRemoveCollectionObject(piCompColl, pItm->wzCLSID, NULL, FALSE);
        ExitOnFailure(hr, "Failed to remove component");

        if (S_FALSE == hr)
            WcaLog(LOGMSG_VERBOSE, "Component not found, nothing to delete, key: %S", pItm->wzCLSID);
    }

    hr = S_OK;

LExit:
    return hr;
}

static HRESULT ReadAssemblyAttributes(
    LPWSTR* ppwzData,
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    )
{
    HRESULT hr = S_OK;

    // read attributes
    hr = WcaReadIntegerFromCaData(ppwzData, &pAttrs->iActionType);
    ExitOnFailure(hr, "Failed to read action type");
    hr = WcaReadIntegerFromCaData(ppwzData, &pAttrs->iActionCost);
    ExitOnFailure(hr, "Failed to read action cost");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzKey);
    ExitOnFailure(hr, "Failed to read key");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzAssemblyName);
    ExitOnFailure(hr, "Failed to read assembly name");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzDllPath);
    ExitOnFailure(hr, "Failed to read dll path");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzTlbPath);
    ExitOnFailure(hr, "Failed to read tlb path");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzPSDllPath);
    ExitOnFailure(hr, "Failed to read proxy-stub dll path");
    hr = WcaReadIntegerFromCaData(ppwzData, &pAttrs->iAttributes);
    ExitOnFailure(hr, "Failed to read attributes");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzAppID);
    ExitOnFailure(hr, "Failed to read application id");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzPartID);
    ExitOnFailure(hr, "Failed to read partition id");

    // free existing component list
    if (pAttrs->pCompList)
    {
        FreeComponentList(pAttrs->pCompList);
        pAttrs->pCompList = NULL;
    }

    // read components
    hr = ReadComponentList(ppwzData, &pAttrs->pCompList);
    ExitOnFailure(hr, "Failed to read components");

    hr = S_OK;

LExit:
    return hr;
}

static void FreeAssemblyAttributes(
    CPI_ASSEMBLY_ATTRIBUTES* pAttrs
    )
{
    ReleaseStr(pAttrs->pwzKey);
    ReleaseStr(pAttrs->pwzAssemblyName);
    ReleaseStr(pAttrs->pwzDllPath);
    ReleaseStr(pAttrs->pwzTlbPath);
    ReleaseStr(pAttrs->pwzPSDllPath);
    ReleaseStr(pAttrs->pwzAppID);
    ReleaseStr(pAttrs->pwzPartID);

    if (pAttrs->pCompList)
        FreeComponentList(pAttrs->pCompList);
}

static HRESULT ReadRoleAssignmentsAttributes(
    LPWSTR* ppwzData,
    CPI_ROLE_ASSIGNMENTS_ATTRIBUTES* pAttrs
    )
{
    HRESULT hr = S_OK;

    // read attributes
    hr = WcaReadIntegerFromCaData(ppwzData, &pAttrs->iActionType);
    ExitOnFailure(hr, "Failed to read action type");
    hr = WcaReadIntegerFromCaData(ppwzData, &pAttrs->iActionCost);
    ExitOnFailure(hr, "Failed to read action cost");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzKey);
    ExitOnFailure(hr, "Failed to read key");
    hr = WcaReadIntegerFromCaData(ppwzData, &pAttrs->iRoleCount);
    ExitOnFailure(hr, "Failed to read role assignments count");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzAppID);
    ExitOnFailure(hr, "Failed to read application id");
    hr = WcaReadStringFromCaData(ppwzData, &pAttrs->pwzPartID);
    ExitOnFailure(hr, "Failed to read partition id");

    // free existing component list
    if (pAttrs->pCompList)
    {
        FreeComponentList(pAttrs->pCompList);
        pAttrs->pCompList = NULL;
    }

    // read components
    hr = ReadComponentList(ppwzData, &pAttrs->pCompList);
    ExitOnFailure(hr, "Failed to read components");

    hr = S_OK;

LExit:
    return hr;
}

static void FreeRoleAssignmentsAttributes(
    CPI_ROLE_ASSIGNMENTS_ATTRIBUTES* pAttrs
    )
{
    ReleaseStr(pAttrs->pwzKey);
    ReleaseStr(pAttrs->pwzAppID);
    ReleaseStr(pAttrs->pwzPartID);

    if (pAttrs->pCompList)
        FreeComponentList(pAttrs->pCompList);
}


static HRESULT ConfigureComponents(
    LPCWSTR pwzPartID,
    LPCWSTR pwzAppID,
    CPIEXEC_COMPONENT* pCompList,
    BOOL fCreate,
    BOOL fProgress
    )
{
    HRESULT hr = S_OK;

    ICatalogCollection* piCompColl = NULL;
    ICatalogObject* piCompObj = NULL;

    long lChanges = 0;

    // get components collection
    hr = CpiGetComponentsCollection(pwzPartID, pwzAppID, &piCompColl);
    if (S_FALSE == hr)
        if (fCreate)
            hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        else
            ExitFunction1(hr = S_OK);
    ExitOnFailure(hr, "Failed to get components collection");

    // read components
    for (CPIEXEC_COMPONENT* pItm = pCompList; pItm; pItm = pItm->pNext)
    {
        // progress message
        if (fProgress)
        {
            hr = CpiActionDataMessage(1, pItm->wzCLSID);
            ExitOnFailure(hr, "Failed to send progress messages");

            if (S_FALSE == hr)
                ExitFunction(); // aborted by user
        }

        // find component
        hr = CpiFindCollectionObjectByStringKey(piCompColl, pItm->wzCLSID, &piCompObj);
        if (S_FALSE == hr)
            if (fCreate)
                hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
            else
                continue;
        ExitOnFailure(hr, "Failed to find component object");

        // properties
        hr = CpiPutCollectionObjectValues(piCompObj, pItm->pPropertyList);
        ExitOnFailure(hr, "Failed to write properties");

        // read roles
        if (pItm->pRoleAssignmentList)
        {
            hr = ConfigureRoleAssignments(L"RolesForComponent", piCompColl, piCompObj, pItm->pRoleAssignmentList, fCreate);
            ExitOnFailure(hr, "Failed to read roles");
        }

        // read interfaces
        if (pItm->pInterfaceList)
        {
            hr = ConfigureInterfaces(piCompColl, piCompObj, pItm->pInterfaceList, fCreate);
            ExitOnFailure(hr, "Failed to read interfaces");
        }

        // clean up
        ReleaseNullObject(piCompObj);
    }

    // save changes
    hr = piCompColl->SaveChanges(&lChanges);
    if (COMADMIN_E_OBJECTERRORS == hr)
        CpiLogCatalogErrorInfo();
    ExitOnFailure(hr, "Failed to save changes");

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piCompColl);
    ReleaseObject(piCompObj);

    return hr;
}

static HRESULT ConfigureInterfaces(
    ICatalogCollection* piCompColl,
    ICatalogObject* piCompObj,
    CPIEXEC_INTERFACE* pIntfList,
    BOOL fCreate
    )
{
    HRESULT hr = S_OK;

    ICatalogCollection* piIntfColl = NULL;
    ICatalogObject* piIntfObj = NULL;

    long lChanges = 0;

    // get interfaces collection
    hr = CpiGetInterfacesCollection(piCompColl, piCompObj, &piIntfColl);
    if (S_FALSE == hr)
        if (fCreate)
            hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        else
            ExitFunction1(hr = S_OK);
    ExitOnFailure(hr, "Failed to get interfaces collection");

    // read interfaces
    for (CPIEXEC_INTERFACE* pItm = pIntfList; pItm; pItm = pItm->pNext)
    {
        // find interface
        hr = CpiFindCollectionObjectByStringKey(piIntfColl, pItm->wzIID, &piIntfObj);
        if (S_FALSE == hr)
            if (fCreate)
                hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
            else
                continue;
        ExitOnFailure(hr, "Failed to find interface object");

        // properties
        hr = CpiPutCollectionObjectValues(piIntfObj, pItm->pPropertyList);
        ExitOnFailure(hr, "Failed to write properties");

        // read roles
        if (pItm->pRoleAssignmentList)
        {
            hr = ConfigureRoleAssignments(L"RolesForInterface", piIntfColl, piIntfObj, pItm->pRoleAssignmentList, fCreate);
            ExitOnFailure(hr, "Failed to read roles");
        }

        // read methods
        if (pItm->pMethodList)
        {
            hr = ConfigureMethods(piIntfColl, piIntfObj, pItm->pMethodList, fCreate);
            ExitOnFailure(hr, "Failed to read methods");
        }

        // clean up
        ReleaseNullObject(piIntfObj);
    }

    // save changes
    hr = piIntfColl->SaveChanges(&lChanges);
    if (COMADMIN_E_OBJECTERRORS == hr)
        CpiLogCatalogErrorInfo();
    ExitOnFailure(hr, "Failed to save changes");

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piIntfColl);
    ReleaseObject(piIntfObj);

    return hr;
}

static HRESULT ConfigureMethods(
    ICatalogCollection* piIntfColl,
    ICatalogObject* piIntfObj,
    CPIEXEC_METHOD* pMethList,
    BOOL fCreate
    )
{
    HRESULT hr = S_OK;

    ICatalogCollection* piMethColl = NULL;
    ICatalogObject* piMethObj = NULL;

    long lChanges = 0;

    // get methods collection
    hr = CpiGetMethodsCollection(piIntfColl, piIntfObj, &piMethColl);
    if (S_FALSE == hr)
        if (fCreate)
            hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        else
            ExitFunction1(hr = S_OK);
    ExitOnFailure(hr, "Failed to get methods collection");

    // read methods
    for (CPIEXEC_METHOD* pItm = pMethList; pItm; pItm = pItm->pNext)
    {
        // find method
        if (*pItm->wzIndex)
            hr = CpiFindCollectionObjectByIntegerKey(piMethColl, _wtol(pItm->wzIndex), &piMethObj);
        else
            hr = CpiFindCollectionObjectByName(piMethColl, pItm->wzName, &piMethObj);

        if (S_FALSE == hr)
            if (fCreate)
                hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
            else
                continue;
        ExitOnFailure(hr, "Failed to find method object");

        // properties
        hr = CpiPutCollectionObjectValues(piMethObj, pItm->pPropertyList);
        ExitOnFailure(hr, "Failed to write properties");

        // read roles
        if (pItm->pRoleAssignmentList)
        {
            hr = ConfigureRoleAssignments(L"RolesForMethod", piMethColl, piMethObj, pItm->pRoleAssignmentList, fCreate);
            ExitOnFailure(hr, "Failed to read roles");
        }

        // clean up
        ReleaseNullObject(piMethObj);
    }

    // save changes
    hr = piMethColl->SaveChanges(&lChanges);
    if (COMADMIN_E_OBJECTERRORS == hr)
        CpiLogCatalogErrorInfo();
    ExitOnFailure(hr, "Failed to save changes");

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piMethColl);
    ReleaseObject(piMethObj);

    return hr;
}

static HRESULT ConfigureRoleAssignments(
    LPCWSTR pwzCollName,
    ICatalogCollection* piCompColl,
    ICatalogObject* piCompObj,
    CPIEXEC_ROLE_ASSIGNMENT* pRoleList,
    BOOL fCreate
    )
{
    HRESULT hr = S_OK;

    ICatalogCollection* piRoleColl = NULL;
    ICatalogObject* piRoleObj = NULL;

    long lChanges = 0;

    // get roles collection
    hr = CpiExecGetCatalogCollection(piCompColl, piCompObj, pwzCollName, &piRoleColl);
    if (S_FALSE == hr)
        if (fCreate)
            hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        else
            ExitFunction1(hr = S_OK);
    ExitOnFailure(hr, "Failed to get role assignments collection");

    // read roles
    for (CPIEXEC_ROLE_ASSIGNMENT* pItm = pRoleList; pItm; pItm = pItm->pNext)
    {
        if (fCreate)
        {
            // find existing role
            hr = CpiFindCollectionObjectByName(piRoleColl, pItm->wzRoleName, NULL);
            ExitOnFailure(hr, "Failed to find role, key: %S", pItm->wzKey);

            if (S_OK == hr)
                continue; // role already exists

            // add object
            hr = CpiAddCollectionObject(piRoleColl, &piRoleObj);
            ExitOnFailure(hr, "Failed to add role assignment to collection");

            // role name
            hr = CpiPutCollectionObjectValue(piRoleObj, L"Name", pItm->wzRoleName);
            ExitOnFailure(hr, "Failed to set role name property, key: %S", pItm->wzKey);

            // clean up
            ReleaseNullObject(piRoleObj);
        }
        else
        {
            // remove role
            hr = CpiRemoveCollectionObject(piRoleColl, NULL, pItm->wzRoleName, FALSE);
            ExitOnFailure(hr, "Failed to remove role, key: %S", pItm->wzKey);
        }
    }

    // save changes
    hr = piRoleColl->SaveChanges(&lChanges);
    if (COMADMIN_E_OBJECTERRORS == hr)
        CpiLogCatalogErrorInfo();
    ExitOnFailure(hr, "Failed to save changes");

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piRoleColl);
    ReleaseObject(piRoleObj);

    return hr;
}

static HRESULT ReadComponentList(
    LPWSTR* ppwzData,
    CPIEXEC_COMPONENT** ppCompList
    )
{
    HRESULT hr = S_OK;

    LPWSTR pwzData = NULL;

    CPIEXEC_COMPONENT* pItm = NULL;

    int iCnt = 0;

    // read count
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read count");

    // read components
    for (int i = 0; i < iCnt; i++)
    {
        pItm = (CPIEXEC_COMPONENT*)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CPIEXEC_COMPONENT));
        if (!pItm)
            ExitFunction1(hr = E_OUTOFMEMORY);

        // read clsid
        hr = WcaReadStringFromCaData(ppwzData, &pwzData);
        ExitOnFailure(hr, "Failed to read clsid");
        StringCchCopyW(pItm->wzCLSID, countof(pItm->wzCLSID), pwzData);

        // read properties
        hr = CpiReadPropertyList(ppwzData, &pItm->pPropertyList);
        ExitOnFailure(hr, "Failed to read properties");

        // read role assignments
        hr = ReadRoleAssignmentList(ppwzData, &pItm->pRoleAssignmentList);
        ExitOnFailure(hr, "Failed to read role assignments");

        // read interfaces
        hr = ReadInterfaceList(ppwzData, &pItm->pInterfaceList);
        ExitOnFailure(hr, "Failed to read interfaces");

        // add to list
        if (*ppCompList)
            pItm->pNext = *ppCompList;
        *ppCompList = pItm;
        pItm = NULL;
    }

    hr = S_OK;

LExit:
    // clean up
    ReleaseStr(pwzData);

    if (pItm)
        FreeComponentList(pItm);

    return hr;
}

static HRESULT ReadInterfaceList(
    LPWSTR* ppwzData,
    CPIEXEC_INTERFACE** ppIntfList
    )
{
    HRESULT hr = S_OK;

    LPWSTR pwzData = NULL;

    CPIEXEC_INTERFACE* pItm = NULL;

    int iCnt = 0;

    // read count
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read count");

    // read interfaces
    for (int i = 0; i < iCnt; i++)
    {
        pItm = (CPIEXEC_INTERFACE*)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CPIEXEC_INTERFACE));
        if (!pItm)
            ExitFunction1(hr = E_OUTOFMEMORY);

        // read iid
        hr = WcaReadStringFromCaData(ppwzData, &pwzData);
        ExitOnFailure(hr, "Failed to read iid");
        StringCchCopyW(pItm->wzIID, countof(pItm->wzIID), pwzData);

        // read properties
        hr = CpiReadPropertyList(ppwzData, &pItm->pPropertyList);
        ExitOnFailure(hr, "Failed to read properties");

        // read role assignments
        hr = ReadRoleAssignmentList(ppwzData, &pItm->pRoleAssignmentList);
        ExitOnFailure(hr, "Failed to read role assignments");

        // read methods
        hr = ReadMethodList(ppwzData, &pItm->pMethodList);
        ExitOnFailure(hr, "Failed to read methods");

        // add to list
        if (*ppIntfList)
            pItm->pNext = *ppIntfList;
        *ppIntfList = pItm;
        pItm = NULL;
    }

    hr = S_OK;

LExit:
    // clean up
    ReleaseStr(pwzData);

    if (pItm)
        FreeInterfaceList(pItm);

    return hr;
}

static HRESULT ReadMethodList(
    LPWSTR* ppwzData,
    CPIEXEC_METHOD** ppMethList
    )
{
    HRESULT hr = S_OK;

    LPWSTR pwzData = NULL;

    CPIEXEC_METHOD* pItm = NULL;

    int iCnt = 0;

    // read count
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read count");

    // read methods
    for (int i = 0; i < iCnt; i++)
    {
        pItm = (CPIEXEC_METHOD*)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CPIEXEC_METHOD));
        if (!pItm)
            ExitFunction1(hr = E_OUTOFMEMORY);

        // read index
        hr = WcaReadStringFromCaData(ppwzData, &pwzData);
        ExitOnFailure(hr, "Failed to read index");
        StringCchCopyW(pItm->wzIndex, countof(pItm->wzIndex), pwzData);

        // read name
        hr = WcaReadStringFromCaData(ppwzData, &pwzData);
        ExitOnFailure(hr, "Failed to read name");
        StringCchCopyW(pItm->wzName, countof(pItm->wzName), pwzData);

        // read properties
        hr = CpiReadPropertyList(ppwzData, &pItm->pPropertyList);
        ExitOnFailure(hr, "Failed to read properties");

        // read role assignments
        hr = ReadRoleAssignmentList(ppwzData, &pItm->pRoleAssignmentList);
        ExitOnFailure(hr, "Failed to read role assignments");

        // add to list
        if (*ppMethList)
            pItm->pNext = *ppMethList;
        *ppMethList = pItm;
        pItm = NULL;
    }

    hr = S_OK;

LExit:
    // clean up
    ReleaseStr(pwzData);

    if (pItm)
        FreeMethodList(pItm);

    return hr;
}

static HRESULT ReadRoleAssignmentList(
    LPWSTR* ppwzData,
    CPIEXEC_ROLE_ASSIGNMENT** ppRoleList
    )
{
    HRESULT hr = S_OK;

    LPWSTR pwzData = NULL;

    CPIEXEC_ROLE_ASSIGNMENT* pItm = NULL;

    int iCnt = 0;

    // read role count
    hr = WcaReadIntegerFromCaData(ppwzData, &iCnt);
    ExitOnFailure(hr, "Failed to read role assignments count");

    // read roles
    for (int i = 0; i < iCnt; i++)
    {
        pItm = (CPIEXEC_ROLE_ASSIGNMENT*)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CPIEXEC_ROLE_ASSIGNMENT));
        if (!pItm)
            ExitFunction1(hr = E_OUTOFMEMORY);

        // read key
        hr = WcaReadStringFromCaData(ppwzData, &pwzData);
        ExitOnFailure(hr, "Failed to read key");
        StringCchCopyW(pItm->wzKey, countof(pItm->wzKey), pwzData);

        // read role name
        hr = WcaReadStringFromCaData(ppwzData, &pwzData);
        ExitOnFailure(hr, "Failed to read role name");
        StringCchCopyW(pItm->wzRoleName, countof(pItm->wzRoleName), pwzData);

        // add to list
        if (*ppRoleList)
            pItm->pNext = *ppRoleList;
        *ppRoleList = pItm;
        pItm = NULL;
    }

    hr = S_OK;

LExit:
    // clean up
    ReleaseStr(pwzData);

    if (pItm)
        FreeRoleAssignmentList(pItm);

    return hr;
}

static void FreeComponentList(
    CPIEXEC_COMPONENT* pList
    )
{
    while (pList)
    {
        if (pList->pPropertyList)
            CpiFreePropertyList(pList->pPropertyList);
        if (pList->pRoleAssignmentList)
            FreeRoleAssignmentList(pList->pRoleAssignmentList);
        if (pList->pInterfaceList)
            FreeInterfaceList(pList->pInterfaceList);

        CPIEXEC_COMPONENT* pDelete = pList;
        pList = pList->pNext;
        ::HeapFree(::GetProcessHeap(), 0, pDelete);
    }
}

static void FreeInterfaceList(
    CPIEXEC_INTERFACE* pList
    )
{
    while (pList)
    {
        if (pList->pPropertyList)
            CpiFreePropertyList(pList->pPropertyList);
        if (pList->pRoleAssignmentList)
            FreeRoleAssignmentList(pList->pRoleAssignmentList);
        if (pList->pMethodList)
            FreeMethodList(pList->pMethodList);

        CPIEXEC_INTERFACE* pDelete = pList;
        pList = pList->pNext;
        ::HeapFree(::GetProcessHeap(), 0, pDelete);
    }
}

static void FreeMethodList(
    CPIEXEC_METHOD* pList
    )
{
    while (pList)
    {
        if (pList->pPropertyList)
            CpiFreePropertyList(pList->pPropertyList);
        if (pList->pRoleAssignmentList)
            FreeRoleAssignmentList(pList->pRoleAssignmentList);

        CPIEXEC_METHOD* pDelete = pList;
        pList = pList->pNext;
        ::HeapFree(::GetProcessHeap(), 0, pDelete);
    }
}

static void FreeRoleAssignmentList(
    CPIEXEC_ROLE_ASSIGNMENT* pList
    )
{
    while (pList)
    {
        CPIEXEC_ROLE_ASSIGNMENT* pDelete = pList;
        pList = pList->pNext;
        ::HeapFree(::GetProcessHeap(), 0, pDelete);
    }
}