Ejemplo n.º 1
0
/******************************************************************
 CaSchedSecureObjects - entry point for CaReadSecureObjects Custom Action

 called as Type 1 CustomAction (binary DLL) from Windows Installer
 in InstallExecuteSequence before CaSecureObjects
******************************************************************/
extern "C" UINT __stdcall SchedSecureObjects(
	__in MSIHANDLE hInstall
	)
{
//	AssertSz(FALSE, "debug SchedSecureObjects");
	HRESULT hr = S_OK;
	UINT er = ERROR_SUCCESS;

	LPWSTR pwzData = NULL;
	LPWSTR pwzTable = NULL;
	LPWSTR pwzTargetPath = NULL;
	LPWSTR pwzFormattedString = NULL;

	int iRoot = 0;
	int iAllUsers = 0;
	LPWSTR pwzKey = NULL;

	PMSIHANDLE hView = NULL;
	PMSIHANDLE hRec = NULL;

	MSIHANDLE hViewObject = NULL; // Don't free this since it's always a copy of either hViewService or hViewCreateFolder
	PMSIHANDLE hViewService = NULL;
	PMSIHANDLE hViewCreateFolder = NULL;
	PMSIHANDLE hViewFile = NULL;
	PMSIHANDLE hViewRegistry = NULL;
	PMSIHANDLE hRecObject = NULL;

	INSTALLSTATE isInstalled;
	INSTALLSTATE isAction;

	LPWSTR pwzCustomActionData = NULL;
	DWORD cchCustomActionData = 0;

	DWORD cObjects = 0;
	eOBJECTTYPE eType = OT_UNKNOWN;

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

	//
	// loop through all the objects to be secured
	//
	hr = WcaOpenExecuteView(wzQUERY_SECUREOBJECTS, &hView);
	ExitOnFailure(hr, "failed to open view on SecureObjects table");
	while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
	{
		hViewObject = NULL;
		eType = OT_UNKNOWN;

		hr = WcaGetRecordString(hRec, QSO_TABLE, &pwzTable);
		ExitOnFailure(hr, "failed to get object table");

		// ensure we're looking at a known table
		if (0 == lstrcmpW(L"ServiceInstall", pwzTable))
		{
			eType = OT_SERVICE;
		}
		else if (0 == lstrcmpW(L"CreateFolder", pwzTable))
		{
			eType = OT_FOLDER;
		}
		else if (0 == lstrcmpW(L"File", pwzTable))
		{
			eType = OT_FILE;
		}
		else if (0 == lstrcmpW(L"Registry", pwzTable))
		{
			eType = OT_REGISTRY;
		}
		else
		{
			ExitOnFailure1(hr = E_INVALIDARG, "unknown SecureObject.Table: %S", pwzTable);
		}

		// if we haven't opened a view on the ServiceInstall/CreateFolder table, do that now
		if (OT_SERVICE == eType)
		{
			if (!hViewService)
			{
				hr = WcaTableExists(pwzTable);
				if (S_FALSE == hr)
					hr = E_UNEXPECTED;
				ExitOnFailure1(hr, "failed to open %s table to secure object", pwzTable);

				hr = WcaOpenView(wzQUERY_SERVICECOMPONENT, &hViewService);
				ExitOnFailure(hr, "failed to open view on ServiceInstall table");
			}

			hViewObject = hViewService;
		}
		else if (OT_FOLDER == eType)
		{
			if (!hViewCreateFolder)
			{
				hr = WcaTableExists(pwzTable);
				if (S_FALSE == hr)
					hr = E_UNEXPECTED;
				ExitOnFailure1(hr, "failed to open %s table to secure object", pwzTable);

				hr = WcaOpenView(wzQUERY_CREATEFOLDERCOMPONENT, &hViewCreateFolder);
				ExitOnFailure(hr, "failed to open view on CreateFolder table");
			}

			hViewObject = hViewCreateFolder;
		}
		else if (OT_FILE== eType)
		{
			if (!hViewFile)
			{
				hr = WcaTableExists(pwzTable);
				if (S_FALSE == hr)
					hr = E_UNEXPECTED;
				ExitOnFailure1(hr, "failed to open %s table to secure object", pwzTable);

				hr = WcaOpenView(wzQUERY_FILECOMPONENT, &hViewFile);
				ExitOnFailure(hr, "failed to open view on CreateFolder table");
			}

			hViewObject = hViewFile;
		}
		else if (OT_REGISTRY== eType)
		{
			if (!hViewRegistry)
			{
				hr = WcaTableExists(pwzTable);
				if (S_FALSE == hr)
					hr = E_UNEXPECTED;
				ExitOnFailure1(hr, "failed to open %s table to secure object", pwzTable);

				hr = WcaOpenView(wzQUERY_REGISTRYCOMPONENT, &hViewRegistry);
				ExitOnFailure(hr, "failed to open view on CreateFolder table");
			}

			hViewObject = hViewRegistry;
		}

		Assert(hViewObject);

		// execute a view looking for the object's Component_
		hr = WcaExecuteView(hViewObject, hRec);
		ExitOnFailure1(hr, "failed to execute view on %S table", pwzData);
		hr = WcaFetchSingleRecord(hViewObject, &hRecObject);
		ExitOnFailure(hr, "failed to fetch Component for secure object");

		hr = WcaGetRecordString(hRecObject, QSOC_COMPONENT, &pwzData);
		ExitOnFailure(hr, "failed to get Component name for secure object");

		//
		// if we are installing this Component
		//
		er = ::MsiGetComponentStateW(hInstall, pwzData, &isInstalled, &isAction);
		ExitOnFailure1(hr = HRESULT_FROM_WIN32(er), "failed to get install state for Component: %S", pwzData);

		if (WcaIsInstalling(isInstalled, isAction))
		{
			// add the data to the CustomActionData
			hr = WcaGetRecordString(hRecObject, QSOC_OBJECTNAME, &pwzData);
			ExitOnFailure(hr, "failed to get name of object");

			if (OT_SERVICE == eType)
			{
				hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData);
				ExitOnFailure(hr, "failed to add data to CustomActionData");
			}
			else if (OT_FOLDER == eType)
			{
				hr = WcaGetTargetPath(pwzData, &pwzTargetPath);
				ExitOnFailure1(hr, "failed to get target path for directory id: %S", pwzData);
				hr = WcaWriteStringToCaData(pwzTargetPath, &pwzCustomActionData);
				ExitOnFailure(hr, "failed to add data to CustomActionData");
			}
			else if (OT_FILE == eType)
			{
				hr = StrAllocFormatted(&pwzFormattedString, L"[#%s]", pwzData);
				ExitOnFailure1(hr, "failed to create formatted string for securing file object: %S", pwzData);

				hr = WcaGetFormattedString(pwzFormattedString, &pwzTargetPath);
				ExitOnFailure2(hr, "failed to get file path from formatted string: %S for secure object: %S", pwzFormattedString, pwzData);

				hr = WcaWriteStringToCaData(pwzTargetPath, &pwzCustomActionData);
				ExitOnFailure(hr, "failed to add data to CustomActionData");
			}
			else if (OT_REGISTRY == eType)
			{
				hr = WcaGetRecordInteger(hRecObject, QSOC_REGROOT, &iRoot);
				ExitOnFailure1(hr, "Failed to get reg key root for secure object: %S", pwzData);

				hr = WcaGetRecordFormattedString(hRecObject, QSOC_REGKEY, &pwzKey);
				ExitOnFailure1(hr, "Failed to get reg key for secure object: %S", pwzData);

				// Decode the root value
				if (-1 == iRoot)
				{
					// They didn't specify a root so that means it's either HKCU or HKLM depending on ALLUSERS property
					hr = WcaGetIntProperty(L"ALLUSERS", &iAllUsers);
					ExitOnFailure(hr, "failed to get value of ALLUSERS property");

					if (1 == iAllUsers)
					{
						hr = StrAllocString(&pwzTargetPath, L"MACHINE\\", 0);
						ExitOnFailure(hr, "failed to allocate target registry string with HKLM root");
					}
					else
					{
						hr = StrAllocString(&pwzTargetPath, L"CURRENT_USER\\", 0);
						ExitOnFailure(hr, "failed to allocate target registry string with HKCU root");
					}
				}
				else if (/*msidbRegistryRootClassesRoot*/ 0 == iRoot)
				{
					hr = StrAllocString(&pwzTargetPath, L"CLASSES_ROOT\\", 0);
					ExitOnFailure(hr, "failed to allocate target registry string with HKCR root");
				}
				else if (/*msidbRegistryRootCurrentUser*/ 1 == iRoot)
				{
					hr = StrAllocString(&pwzTargetPath, L"CURRENT_USER\\", 0);
					ExitOnFailure(hr, "failed to allocate target registry string with HKCU root");
				}
				else if (/*msidbRegistryRootLocalMachine*/ 2 == iRoot)
				{
					hr = StrAllocString(&pwzTargetPath, L"MACHINE\\", 0);
					ExitOnFailure(hr, "failed to allocate target registry string with HKLM root");
				}
				else if (/*msidbRegistryRootUsers*/ 3 == iRoot)
				{
					hr = StrAllocString(&pwzTargetPath, L"USERS\\", 0);
					ExitOnFailure(hr, "failed to allocate target registry string with HKU root");
				}
				else
				{
					ExitOnFailure2(hr = E_UNEXPECTED, "Unknown registry key root specified for secure object: '%S' root: %d", pwzData, iRoot);
				}

				hr = StrAllocConcat(&pwzTargetPath, pwzKey, 0);
				ExitOnFailure2(hr, "Failed to concat key: %S for secure object: %S", pwzKey, pwzData);

				hr = WcaWriteStringToCaData(pwzTargetPath, &pwzCustomActionData);
				ExitOnFailure(hr, "failed to add data to CustomActionData");
			}
			else
			{
				AssertSz(FALSE, "How did you get here?");
			}

			hr = WcaWriteStringToCaData(pwzTable, &pwzCustomActionData);
			ExitOnFailure(hr, "failed to add data to CustomActionData");

			hr = WcaGetRecordFormattedString(hRec, QSO_DOMAIN, &pwzData);
			ExitOnFailure(hr, "failed to get domain for user to configure object");
			hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData);
			ExitOnFailure(hr, "failed to add data to CustomActionData");

			hr = WcaGetRecordFormattedString(hRec, QSO_USER, &pwzData);
			ExitOnFailure(hr, "failed to get user to configure object");
			hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData);
			ExitOnFailure(hr, "failed to add data to CustomActionData");

			hr = WcaGetRecordString(hRec, QSO_PERMISSION, &pwzData);
			ExitOnFailure(hr, "failed to get domain for user to configure object");
			hr = WcaWriteStringToCaData(pwzData, &pwzCustomActionData);
			ExitOnFailure(hr, "failed to add data to CustomActionData");

			cObjects++;
		}
	}

	// if we looped through all records all is well
	if (E_NOMOREITEMS == hr)
		hr = S_OK;
	ExitOnFailure(hr, "failed while looping through all objects to secure");

	//
	// schedule the custom action and add to progress bar
	//
	if (pwzCustomActionData && *pwzCustomActionData)
	{
		Assert(0 < cObjects);

		hr = WcaDoDeferredAction(L"ExecSecureObjects", pwzCustomActionData, cObjects * COST_SECUREOBJECT);
		ExitOnFailure(hr, "failed to schedule ExecSecureObjects action");
	}

LExit:
	ReleaseStr(pwzCustomActionData);
	ReleaseStr(pwzData);
	ReleaseStr(pwzTable);
	ReleaseStr(pwzTargetPath);
	ReleaseStr(pwzFormattedString);
	ReleaseStr(pwzKey);

	if (FAILED(hr))
		er = ERROR_INSTALL_FAILURE;
	return WcaFinalize(er);
}
Ejemplo n.º 2
0
static HRESULT GetTargetPath(
    __in eOBJECTTYPE eType,
    __in LPCWSTR pwzSecureObject,
    __out LPWSTR* ppwzTargetPath
    )
{
    HRESULT hr = S_OK;

    PMSIHANDLE hView = NULL;
    PMSIHANDLE hRecObject = NULL;
    PMSIHANDLE hRec = NULL;

    int iRoot = 0;
    int iAllUsers = 0;
    LPWSTR pwzKey = NULL;
    LPWSTR pwzFormattedString = NULL;

    if (OT_SERVICE == eType)
    {
        hr = WcaTableExists(L"ServiceInstall");
        if (S_FALSE == hr)
        {
            hr = E_UNEXPECTED;
        }
        ExitOnFailure(hr, "failed to open ServiceInstall table to secure object");

        hr = WcaOpenView(wzQUERY_SERVICEINSTALL, &hView);
        ExitOnFailure(hr, "failed to open view on ServiceInstall table");

        // create a record that stores the object to secure
        hRec = MsiCreateRecord(1);
        MsiRecordSetStringW(hRec, 1, pwzSecureObject);

        // execute a view looking for the object's ServiceInstall.ServiceInstall row.
        hr = WcaExecuteView(hView, hRec);
        ExitOnFailure(hr, "failed to execute view on ServiceInstall table");
        hr = WcaFetchSingleRecord(hView, &hRecObject);
        ExitOnFailure(hr, "failed to fetch ServiceInstall row for secure object");

        hr = WcaGetRecordFormattedString(hRecObject, QSSI_NAME, ppwzTargetPath);
        ExitOnFailure1(hr, "failed to get service name for secure object: %ls", pwzSecureObject);
    }
    else if (OT_FOLDER == eType)
    {
        hr = WcaGetTargetPath(pwzSecureObject, ppwzTargetPath);
        ExitOnFailure1(hr, "failed to get target path for directory id: %ls", pwzSecureObject);
    }
    else if (OT_FILE == eType)
    {
        hr = StrAllocFormatted(&pwzFormattedString, L"[#%s]", pwzSecureObject);
        ExitOnFailure1(hr, "failed to create formatted string for securing file object: %ls", pwzSecureObject);

        hr = WcaGetFormattedString(pwzFormattedString, ppwzTargetPath);
        ExitOnFailure2(hr, "failed to get file path from formatted string: %ls for secure object: %ls", pwzFormattedString, pwzSecureObject);
    }
    else if (OT_REGISTRY == eType)
    {
        hr = WcaTableExists(L"Registry");
        if (S_FALSE == hr)
        {
            hr = E_UNEXPECTED;
        }
        ExitOnFailure(hr, "failed to open Registry table to secure object");

        hr = WcaOpenView(wzQUERY_REGISTRY, &hView);
        ExitOnFailure(hr, "failed to open view on Registry table");

        // create a record that stores the object to secure
        hRec = MsiCreateRecord(1);
        MsiRecordSetStringW(hRec, 1, pwzSecureObject);

        // execute a view looking for the object's Registry row
        hr = WcaExecuteView(hView, hRec);
        ExitOnFailure(hr, "failed to execute view on Registry table");
        hr = WcaFetchSingleRecord(hView, &hRecObject);
        ExitOnFailure(hr, "failed to fetch Registry row for secure object");

        hr = WcaGetRecordInteger(hRecObject, QSOC_REGROOT, &iRoot);
        ExitOnFailure1(hr, "Failed to get reg key root for secure object: %ls", pwzSecureObject);

        hr = WcaGetRecordFormattedString(hRecObject, QSOC_REGKEY, &pwzKey);
        ExitOnFailure1(hr, "Failed to get reg key for secure object: %ls", pwzSecureObject);

        // Decode the root value
        if (-1 == iRoot)
        {
            // They didn't specify a root so that means it's either HKCU or HKLM depending on ALLUSERS property
            hr = WcaGetIntProperty(L"ALLUSERS", &iAllUsers);
            ExitOnFailure(hr, "failed to get value of ALLUSERS property");

            if (1 == iAllUsers)
            {
                hr = StrAllocString(ppwzTargetPath, L"MACHINE\\", 0);
                ExitOnFailure(hr, "failed to allocate target registry string with HKLM root");
            }
            else
            {
                hr = StrAllocString(ppwzTargetPath, L"CURRENT_USER\\", 0);
                ExitOnFailure(hr, "failed to allocate target registry string with HKCU root");
            }
        }
        else if (msidbRegistryRootClassesRoot == iRoot)
        {
            hr = StrAllocString(ppwzTargetPath, L"CLASSES_ROOT\\", 0);
            ExitOnFailure(hr, "failed to allocate target registry string with HKCR root");
        }
        else if (msidbRegistryRootCurrentUser == iRoot)
        {
            hr = StrAllocString(ppwzTargetPath, L"CURRENT_USER\\", 0);
            ExitOnFailure(hr, "failed to allocate target registry string with HKCU root");
        }
        else if (msidbRegistryRootLocalMachine == iRoot)
        {
            hr = StrAllocString(ppwzTargetPath, L"MACHINE\\", 0);
            ExitOnFailure(hr, "failed to allocate target registry string with HKLM root");
        }
        else if (msidbRegistryRootUsers == iRoot)
        {
            hr = StrAllocString(ppwzTargetPath, L"USERS\\", 0);
            ExitOnFailure(hr, "failed to allocate target registry string with HKU root");
        }
        else
        {
            ExitOnFailure2(hr = E_UNEXPECTED, "Unknown registry key root specified for secure object: '%ls' root: %d", pwzSecureObject, iRoot);
        }
        
        hr = StrAllocConcat(ppwzTargetPath, pwzKey, 0);
        ExitOnFailure2(hr, "Failed to concat key: %ls for secure object: %ls", pwzKey, pwzSecureObject);
    }
    else
    {
        AssertSz(FALSE, "How did you get here?");
        ExitOnFailure1(hr = E_UNEXPECTED, "Unknown secure object type: %d", eType);
    }

LExit:
    ReleaseStr(pwzFormattedString);
    ReleaseStr(pwzKey);

    return hr;
}
Ejemplo n.º 3
0
HRESULT CpiPropertiesRead(
    LPCWSTR pwzQuery,
    LPCWSTR pwzKey,
    CPI_PROPERTY_DEFINITION* pPropDefList,
    CPI_PROPERTY** ppPropList,
    int* piCount
    )
{
    HRESULT hr = S_OK;

    PMSIHANDLE hView, hRecKey, hRec;

    CPI_PROPERTY* pItm = NULL;
    LPWSTR pwzData = NULL;

    int iVersionNT = 0;

    CPI_PROPERTY_DEFINITION* pPropDef;

    *piCount = 0;

    // get NT version
    hr = WcaGetIntProperty(L"VersionNT", &iVersionNT);
    ExitOnFailure(hr, "Failed to set record string");

    // create parameter record
    hRecKey = ::MsiCreateRecord(1);
    ExitOnNull(hRecKey, hr, E_OUTOFMEMORY, "Failed to create record");
    hr = WcaSetRecordString(hRecKey, 1, pwzKey);
    ExitOnFailure(hr, "Failed to set record string");

    // open view
    hr = WcaOpenView(pwzQuery, &hView);
    ExitOnFailure(hr, "Failed to open view on property table");
    hr = WcaExecuteView(hView, hRecKey);
    ExitOnFailure(hr, "Failed to execute view on property table");

    while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
    {
        // create entry
        pItm = (CPI_PROPERTY*)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CPI_PROPERTY));
        if (!pItm)
            ExitFunction1(hr = E_OUTOFMEMORY);

        // get name
        hr = WcaGetRecordString(hRec, pqName, &pwzData);
        ExitOnFailure(hr, "Failed to get name");
        StringCchCopyW(pItm->wzName, countof(pItm->wzName), pwzData);

        // get value
        hr = WcaGetRecordFormattedString(hRec, pqValue, &pItm->pwzValue);
        ExitOnFailure(hr, "Failed to get value");

        // find property definition
        hr = FindPropertyDefinition(pPropDefList, pItm->wzName, &pPropDef);
        ExitOnFailure(hr, "Failed to find property definition");

        if (S_FALSE == hr)
            ExitOnFailure(hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND), "Unknown property, key: %S, property: %S", pwzKey, pItm->wzName);

        // check version, ignore if catalog version is too low
        if (iVersionNT < pPropDef->iMinVersionNT)
        {
            WcaLog(LOGMSG_VERBOSE, "Skipping property since NT version is too low, key: %S, property: %S", pwzKey, pItm->wzName);
            CpiPropertiesFreeList(pItm);
            pItm = NULL;
            continue;
        }

        // if the property is a user, replace the User table key with a user account name
        if (cpptUser == pPropDef->iType)
        {
            hr = GetUserAccountName(pItm->pwzValue, &pItm->pwzValue);
            ExitOnFailure(hr, "Failed to get user account name");
        }

        // add entry
        ++*piCount;
        if (*ppPropList)
            pItm->pNext = *ppPropList;
        *ppPropList = pItm;
        pItm = NULL;
    }

    if (E_NOMOREITEMS == hr)
        hr = S_OK;

LExit:
    // clean up
    if (pItm)
        CpiPropertiesFreeList(pItm);

    ReleaseStr(pwzData);

    return hr;
}
Ejemplo n.º 4
0
/********************************************************************
 ConfigureComPlusInstall - CUSTOM ACTION ENTRY POINT for installing COM+ components

********************************************************************/
extern "C" UINT __stdcall ConfigureComPlusInstall(MSIHANDLE hInstall)
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;

    BOOL fInitializedCom = FALSE;

    ICOMAdminCatalog* piCatalog = NULL;

    CPI_PARTITION_LIST partList;
    CPI_PARTITION_ROLE_LIST partRoleList;
    CPI_USER_IN_PARTITION_ROLE_LIST usrInPartRoleList;
    CPI_PARTITION_USER_LIST partUsrList;
    CPI_APPLICATION_LIST appList;
    CPI_APPLICATION_ROLE_LIST appRoleList;
    CPI_USER_IN_APPLICATION_ROLE_LIST usrInAppRoleList;
    CPI_ASSEMBLY_LIST asmList;
    CPI_SUBSCRIPTION_LIST subList;

    LPWSTR pwzRollbackFileName = NULL;
    LPWSTR pwzActionData = NULL;
    LPWSTR pwzRollbackActionData = NULL;
    LPWSTR pwzCommitActionData = NULL;

    int iVersionNT = 0;
    int iProgress = 0;
    int iCommitProgress = 0;

    ::ZeroMemory(&partList, sizeof(CPI_PARTITION_LIST));
    ::ZeroMemory(&partRoleList, sizeof(CPI_PARTITION_ROLE_LIST));
    ::ZeroMemory(&usrInPartRoleList, sizeof(CPI_USER_IN_PARTITION_ROLE_LIST));
    ::ZeroMemory(&partUsrList, sizeof(CPI_PARTITION_USER_LIST));
    ::ZeroMemory(&appList, sizeof(CPI_APPLICATION_LIST));
    ::ZeroMemory(&appRoleList, sizeof(CPI_APPLICATION_ROLE_LIST));
    ::ZeroMemory(&usrInAppRoleList, sizeof(CPI_USER_IN_APPLICATION_ROLE_LIST));
    ::ZeroMemory(&asmList, sizeof(CPI_ASSEMBLY_LIST));
    ::ZeroMemory(&subList, sizeof(CPI_SUBSCRIPTION_LIST));

    // initialize
    hr = WcaInitialize(hInstall, "ConfigureComPlusInstall");
    ExitOnFailure(hr, "Failed to initialize");

    hr = ::CoInitialize(NULL);
    ExitOnFailure(hr, "Failed to initialize COM");
    fInitializedCom = TRUE;

    CpiInitialize();

    // check for the prerequsite tables
    if (!CpiTableExists(cptComPlusPartition) && !CpiTableExists(cptComPlusApplication) && !CpiTableExists(cptComPlusAssembly))
    {
        WcaLog(LOGMSG_VERBOSE, "skipping install COM+ CustomAction, no ComPlusPartition, ComPlusApplication or ComPlusAssembly table present");
        ExitFunction1(hr = S_FALSE);
    }

    // make sure we can access the COM+ admin catalog
    do {
        hr = CpiGetAdminCatalog(&piCatalog);
        if (FAILED(hr))
        {
            WcaLog(LOGMSG_STANDARD, "Failed to get COM+ admin catalog");
            er = WcaErrorMessage(msierrComPlusCannotConnect, hr, INSTALLMESSAGE_ERROR | MB_ABORTRETRYIGNORE, 0);
            switch (er)
            {
            case IDABORT:
                ExitFunction(); // exit with hr from CpiGetAdminCatalog() to kick off a rollback
            case IDRETRY:
                hr = S_FALSE;
                break;
            case IDIGNORE:
            default:
                ExitFunction1(hr = S_OK); // pretend everything is okay and bail
            }
        }
    } while (S_FALSE == hr);

    // get NT version
    hr = WcaGetIntProperty(L"VersionNT", &iVersionNT);
    ExitOnFailure(hr, "Failed to get VersionNT property");

    // read elements
    if (502 <= iVersionNT && CpiTableExists(cptComPlusPartition))
    {
        hr = CpiPartitionsRead(&partList);
        MessageExitOnFailure(hr, msierrComPlusPartitionReadFailed, "Failed to read ComPlusPartitions table");
    }

    if (502 <= iVersionNT && CpiTableExists(cptComPlusPartitionRole))
    {
        hr = CpiPartitionRolesRead(&partList, &partRoleList);
        MessageExitOnFailure(hr, msierrComPlusPartitionRoleReadFailed, "Failed to read ComPlusPartitionRole table");
    }

    if (502 <= iVersionNT && (CpiTableExists(cptComPlusUserInPartitionRole) || CpiTableExists(cptComPlusGroupInPartitionRole)))
    {
        hr = CpiUsersInPartitionRolesRead(&partRoleList, &usrInPartRoleList);
        MessageExitOnFailure(hr, msierrComPlusUserInPartitionRoleReadFailed, "Failed to read ComPlusUserInPartitionRole table");
    }

    if (502 <= iVersionNT && CpiTableExists(cptComPlusPartitionUser))
    {
        hr = CpiPartitionUsersRead(&partList, &partUsrList);
        MessageExitOnFailure(hr, msierrComPlusPartitionUserReadFailed, "Failed to read ComPlusPartitionUser table");
    }

    if (CpiTableExists(cptComPlusApplication))
    {
        hr = CpiApplicationsRead(&partList, &appList);
        MessageExitOnFailure(hr, msierrComPlusApplicationReadFailed, "Failed to read ComPlusApplication table");
    }

    if (CpiTableExists(cptComPlusApplicationRole))
    {
        hr = CpiApplicationRolesRead(&appList, &appRoleList);
        MessageExitOnFailure(hr, msierrComPlusApplicationRoleReadFailed, "Failed to read ComPlusApplicationRole table");
    }

    if (CpiTableExists(cptComPlusUserInApplicationRole) || CpiTableExists(cptComPlusGroupInApplicationRole))
    {
        hr = CpiUsersInApplicationRolesRead(&appRoleList, &usrInAppRoleList);
        MessageExitOnFailure(hr, msierrComPlusUserInApplicationRoleReadFailed, "Failed to read ComPlusUserInApplicationRole table");
    }

    if (CpiTableExists(cptComPlusAssembly))
    {
        hr = CpiAssembliesRead(&appList, &appRoleList, &asmList);
        MessageExitOnFailure(hr, msierrComPlusAssembliesReadFailed, "Failed to read ComPlusAssembly table");
    }

    if (CpiTableExists(cptComPlusSubscription))
    {
        hr = CpiSubscriptionsRead(&asmList, &subList);
        MessageExitOnFailure(hr, msierrComPlusSubscriptionReadFailed, "Failed to read ComPlusSubscription table");
    }

    // verify elements
    hr = CpiPartitionsVerifyInstall(&partList);
    ExitOnFailure(hr, "Failed to verify partitions");

    hr = CpiApplicationsVerifyInstall(&appList);
    ExitOnFailure(hr, "Failed to verify applications");

    hr = CpiApplicationRolesVerifyInstall(&appRoleList);
    ExitOnFailure(hr, "Failed to verify application roles");

    hr = CpiAssembliesVerifyInstall(&asmList);
    ExitOnFailure(hr, "Failed to verify assemblies");

    if (subList.iInstallCount)
    {
        hr = CpiSubscriptionsVerifyInstall(&subList);
        ExitOnFailure(hr, "Failed to verify subscriptions");
    }

    // schedule
    if (partList.iInstallCount || appList.iInstallCount || usrInAppRoleList.iInstallCount ||
        appRoleList.iInstallCount || asmList.iInstallCount || asmList.iRoleInstallCount || subList.iInstallCount)
    {
        // create rollback file name
        hr = CpiGetTempFileName(&pwzRollbackFileName);
        ExitOnFailure(hr, "Failed to get rollback file name");

        // schedule rollback prepare custom action
        hr = WcaDoDeferredAction(CP_COMPLUSROLLBACKINSTALLPREPARE, pwzRollbackFileName, 0);
        ExitOnFailure(hr, "Failed to schedule ComPlusRollbackInstallPrepare");

        // schedule prepare custom action
        hr = WcaDoDeferredAction(CP_COMPLUSINSTALLPREPARE, pwzRollbackFileName, 0);
        ExitOnFailure(hr, "Failed to schedule ComPlusInstallPrepare");

        // schedule rollback custom action
        hr = WcaWriteStringToCaData(pwzRollbackFileName, &pwzRollbackActionData);
        ExitOnFailure(hr, "Failed to add rollback file name to rollback custom action data");

        hr = CpiSubscriptionsInstall(&subList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install subscriptions");
        hr = CpiRoleAssignmentsInstall(&asmList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install assemblies");
        hr = CpiAssembliesInstall(&asmList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install assemblies");
        hr = CpiUsersInApplicationRolesInstall(&usrInAppRoleList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install users in application roles");
        hr = CpiApplicationRolesInstall(&appRoleList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install application roles");
        hr = CpiApplicationsInstall(&appList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install applications");
        hr = CpiPartitionUsersInstall(&partUsrList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install partition users");
        hr = CpiUsersInPartitionRolesInstall(&usrInPartRoleList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install users in partition roles");
        hr = CpiPartitionsInstall(&partList, rmRollback, &pwzRollbackActionData, NULL);
        ExitOnFailure(hr, "Failed to install partitions");

        hr = WcaDoDeferredAction(CP_COMPLUSROLLBACKINSTALLEXECUTE, pwzRollbackActionData, 0);
        ExitOnFailure(hr, "Failed to schedule ComPlusRollbackInstallExecute");

        // schedule install custom action
        hr = WcaWriteStringToCaData(pwzRollbackFileName, &pwzActionData);
        ExitOnFailure(hr, "Failed to add rollback file name to custom action data");

        hr = CpiPartitionsInstall(&partList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install partitions");
        hr = CpiUsersInPartitionRolesInstall(&usrInPartRoleList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install users in partition roles");
        hr = CpiPartitionUsersInstall(&partUsrList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install partition users");
        hr = CpiApplicationsInstall(&appList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install applications");
        hr = CpiApplicationRolesInstall(&appRoleList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install application roles");
        hr = CpiUsersInApplicationRolesInstall(&usrInAppRoleList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install users in application roles");
        hr = CpiAssembliesInstall(&asmList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install assemblies");
        hr = CpiRoleAssignmentsInstall(&asmList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install assemblies");
        hr = CpiSubscriptionsInstall(&subList, rmDeferred, &pwzActionData, &iProgress);
        ExitOnFailure(hr, "Failed to install subscriptions");

        hr = WcaDoDeferredAction(CP_COMPLUSINSTALLEXECUTE, pwzActionData, iProgress);
        ExitOnFailure(hr, "Failed to schedule ComPlusInstallExecute");

        // schedule install commit custom action
        hr = WcaWriteStringToCaData(pwzRollbackFileName, &pwzCommitActionData);
        ExitOnFailure(hr, "Failed to add rollback file name to commit custom action data");

        hr = CpiAssembliesInstall(&asmList, rmCommit, &pwzCommitActionData, &iCommitProgress);
        ExitOnFailure(hr, "Failed to install assemblies");
        hr = CpiRoleAssignmentsInstall(&asmList, rmCommit, &pwzCommitActionData, &iCommitProgress);
        ExitOnFailure(hr, "Failed to install assemblies");
        hr = CpiSubscriptionsInstall(&subList, rmCommit, &pwzCommitActionData, &iCommitProgress);
        ExitOnFailure(hr, "Failed to install subscriptions");

        hr = WcaDoDeferredAction(CP_COMPLUSINSTALLEXECUTECOMMIT, pwzCommitActionData, iCommitProgress);
        ExitOnFailure(hr, "Failed to schedule ComPlusInstallExecuteCommit");

        // schedule commit custom action
        hr = WcaDoDeferredAction(CP_COMPLUSINSTALLCOMMIT, pwzRollbackFileName, 0);
        ExitOnFailure(hr, "Failed to schedule ComPlusInstallCommit");
    }

    hr = S_OK;

LExit:
    // clean up
    ReleaseObject(piCatalog);

    ReleaseStr(pwzRollbackFileName);
    ReleaseStr(pwzActionData);
    ReleaseStr(pwzRollbackActionData);
    ReleaseStr(pwzCommitActionData);

    CpiPartitionListFree(&partList);
    CpiPartitionRoleListFree(&partRoleList);
    CpiUserInPartitionRoleListFree(&usrInPartRoleList);
    CpiPartitionUserListFree(&partUsrList);
    CpiApplicationListFree(&appList);
    CpiApplicationRoleListFree(&appRoleList);
    CpiUserInApplicationRoleListFree(&usrInAppRoleList);
    CpiAssemblyListFree(&asmList);
    CpiSubscriptionListFree(&subList);

    // unitialize
    CpiFinalize();

    if (fInitializedCom)
        ::CoUninitialize();

    er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
    return WcaFinalize(er);
}