From 95a5a8f9efef02ddcec5b3f69be99a00d71a802a Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 16 Dec 2018 21:19:24 -0600 Subject: Import implementation of IisCA from old repo's scasched/scaexec. --- src/ca/scaexec.cpp | 1184 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1184 insertions(+) create mode 100644 src/ca/scaexec.cpp (limited to 'src/ca/scaexec.cpp') diff --git a/src/ca/scaexec.cpp b/src/ca/scaexec.cpp new file mode 100644 index 00000000..df25e8db --- /dev/null +++ b/src/ca/scaexec.cpp @@ -0,0 +1,1184 @@ +// 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" + + +/******************************************************************** + StartMetabaseTransaction - CUSTOM ACTION ENTRY POINT for backing up metabase + + Input: deferred CustomActionData - BackupName +********************************************************************/ +extern "C" UINT __stdcall StartMetabaseTransaction(MSIHANDLE hInstall) +{ +//AssertSz(FALSE, "debug StartMetabaseTransaction here"); + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + IMSAdminBase* piMetabase = NULL; + LPWSTR pwzData = NULL; + BOOL fInitializedCom = FALSE; + + // initialize + hr = WcaInitialize(hInstall, "StartMetabaseTransaction"); + ExitOnFailure(hr, "failed to initialize StartMetabaseTransaction"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + if (hr == REGDB_E_CLASSNOTREG) + { + WcaLog(LOGMSG_STANDARD, "Failed to get IIMSAdminBase to backup - continuing"); + hr = S_OK; + } + else + { + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IIMSAdminBase object"); + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + // back up the metabase + Assert(lstrlenW(pwzData) < MD_BACKUP_MAX_LEN); + + // MD_BACKUP_OVERWRITE = Overwrite if a backup of the same name and version exists in the backup location + hr = piMetabase->Backup(pwzData, MD_BACKUP_NEXT_VERSION, MD_BACKUP_OVERWRITE | MD_BACKUP_FORCE_BACKUP | MD_BACKUP_SAVE_FIRST); + if (MD_WARNING_SAVE_FAILED == hr) + { + WcaLog(LOGMSG_STANDARD, "Failed to save metabase before backing up - continuing"); + hr = S_OK; + } + MessageExitOnFailure(hr, msierrIISFailedStartTransaction, "failed to begin metabase transaction: '%ls'", pwzData); + } + hr = WcaProgressMessage(COST_IIS_TRANSACTIONS, FALSE); +LExit: + ReleaseStr(pwzData); + ReleaseObject(piMetabase); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + er = ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +/******************************************************************** + RollbackMetabaseTransaction - CUSTOM ACTION ENTRY POINT for unbacking up metabase + + Input: deferred CustomActionData - BackupName +********************************************************************/ +extern "C" UINT __stdcall RollbackMetabaseTransaction(MSIHANDLE hInstall) +{ +//AssertSz(FALSE, "debug RollbackMetabaseTransaction here"); + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + IMSAdminBase* piMetabase = NULL; + LPWSTR pwzData = NULL; + BOOL fInitializedCom = FALSE; + + hr = WcaInitialize(hInstall, "RollbackMetabaseTransaction"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + if (hr == REGDB_E_CLASSNOTREG) + { + WcaLog(LOGMSG_STANDARD, "Failed to get IIMSAdminBase to rollback - continuing"); + hr = S_OK; + ExitFunction(); + } + ExitOnFailure(hr, "failed to get IID_IIMSAdminBase object"); + + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + hr = piMetabase->Restore(pwzData, MD_BACKUP_HIGHEST_VERSION, 0); + ExitOnFailure(hr, "failed to rollback metabase transaction: '%ls'", pwzData); + + hr = piMetabase->DeleteBackup(pwzData, MD_BACKUP_HIGHEST_VERSION); + ExitOnFailure(hr, "failed to cleanup metabase transaction '%ls', continuing", pwzData); + +LExit: + ReleaseStr(pwzData); + ReleaseObject(piMetabase); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} + + +/******************************************************************** + CommitMetabaseTransaction - CUSTOM ACTION ENTRY POINT for unbacking up metabase + + Input: deferred CustomActionData - BackupName + * *****************************************************************/ +extern "C" UINT __stdcall CommitMetabaseTransaction(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + IMSAdminBase* piMetabase = NULL; + LPWSTR pwzData = NULL; + BOOL fInitializedCom = FALSE; + + hr = WcaInitialize(hInstall, "CommitMetabaseTransaction"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + if (hr == REGDB_E_CLASSNOTREG) + { + WcaLog(LOGMSG_STANDARD, "Failed to get IIMSAdminBase to commit - continuing"); + hr = S_OK; + ExitFunction(); + } + ExitOnFailure(hr, "failed to get IID_IIMSAdminBase object"); + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + hr = piMetabase->DeleteBackup(pwzData, MD_BACKUP_HIGHEST_VERSION); + ExitOnFailure(hr, "failed to cleanup metabase transaction: '%ls'", pwzData); + +LExit: + ReleaseStr(pwzData); + ReleaseObject(piMetabase); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} + + +/******************************************************************** + CreateMetabaseKey - Installs metabase keys + + Input: deferred CustomActionData - Key + * *****************************************************************/ +static HRESULT CreateMetabaseKey(__in LPWSTR* ppwzCustomActionData, __in IMSAdminBase* piMetabase) +{ +//AssertSz(FALSE, "debug CreateMetabaseKey here"); + HRESULT hr = S_OK; + METADATA_HANDLE mhRoot = 0; + LPWSTR pwzData = NULL; + LPCWSTR pwzKey; + + int i; + + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzData); + ExitOnFailure(hr, "failed to read key from custom action data"); + + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhRoot); + for (i = 30; i > 0 && HRESULT_FROM_WIN32(ERROR_PATH_BUSY) == hr; i--) + { + ::Sleep(1000); + WcaLog(LOGMSG_STANDARD, "failed to open root key, retrying %d time(s)...", i); + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhRoot); + } + MessageExitOnFailure(hr, msierrIISFailedOpenKey, "failed to open metabase key: %ls", L"/LM"); + + pwzKey = pwzData + 3; + + WcaLog(LOGMSG_VERBOSE, "Creating Metabase Key: %ls", pwzKey); + + hr = piMetabase->AddKey(mhRoot, pwzKey); + if (HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) == hr) + { + WcaLog(LOGMSG_VERBOSE, "Key `%ls` already existed, continuing.", pwzData); + hr = S_OK; + } + MessageExitOnFailure(hr, msierrIISFailedCreateKey, "failed to create metabase key: %ls", pwzKey); + + hr = WcaProgressMessage(COST_IIS_CREATEKEY, FALSE); + +LExit: + if (mhRoot) + { + piMetabase->CloseKey(mhRoot); + } + + return hr; +} + + +/******************************************************************** + WriteMetabaseValue -Installs metabase values + + Input: deferred CustomActionData - Key\tIdentifier\tAttributes\tUserType\tDataType\tData + * *****************************************************************/ +static HRESULT WriteMetabaseValue(__in LPWSTR* ppwzCustomActionData, __in IMSAdminBase* piMetabase) +{ + //AssertSz(FALSE, "debug WriteMetabaseValue here"); + HRESULT hr = S_OK; + + METADATA_HANDLE mhKey = 0; + + LPWSTR pwzKey = NULL; + LPWSTR pwzTemp = NULL; + DWORD dwData = 0; + DWORD dwTemp = 0; + BOOL fFreeData = FALSE; + METADATA_RECORD mr; + ::ZeroMemory((LPVOID)&mr, sizeof(mr)); + METADATA_RECORD mrGet; + ::ZeroMemory((LPVOID)&mrGet, sizeof(mrGet)); + + int i; + + // get the key first + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzKey); + ExitOnFailure(hr, "failed to read key"); + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&mr.dwMDIdentifier)); + ExitOnFailure(hr, "failed to read identifier"); + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&mr.dwMDAttributes)); + ExitOnFailure(hr, "failed to read attributes"); + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&mr.dwMDUserType)); + ExitOnFailure(hr, "failed to read user type"); + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&mr.dwMDDataType)); + ExitOnFailure(hr, "failed to read data type"); + + switch (mr.dwMDDataType) // data + { + case DWORD_METADATA: + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&dwData)); + mr.dwMDDataLen = sizeof(dwData); + mr.pbMDData = reinterpret_cast(&dwData); + break; + case STRING_METADATA: + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzTemp); + mr.dwMDDataLen = (lstrlenW(pwzTemp) + 1) * sizeof(WCHAR); + mr.pbMDData = reinterpret_cast(pwzTemp); + break; + case MULTISZ_METADATA: + { + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzTemp); + mr.dwMDDataLen = (lstrlenW(pwzTemp) + 1) * sizeof(WCHAR); + for (LPWSTR pwzT = pwzTemp; *pwzT; ++pwzT) + { + if (MAGIC_MULTISZ_CHAR == *pwzT) + { + *pwzT = L'\0'; + } + } + mr.pbMDData = reinterpret_cast(pwzTemp); + } + break; + case BINARY_METADATA: + hr = WcaReadStreamFromCaData(ppwzCustomActionData, &mr.pbMDData, reinterpret_cast(&mr.dwMDDataLen)); + fFreeData = TRUE; + break; + default: + hr = E_UNEXPECTED; + break; + } + ExitOnFailure(hr, "failed to parse CustomActionData into metabase record"); + + WcaLog(LOGMSG_VERBOSE, "Writing Metabase Value Under Key: %ls ID: %d", pwzKey, mr.dwMDIdentifier); + + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhKey); + for (i = 30; i > 0 && HRESULT_FROM_WIN32(ERROR_PATH_BUSY) == hr; i--) + { + ::Sleep(1000); + WcaLog(LOGMSG_STANDARD, "failed to open '%ls' key, retrying %d time(s)...", pwzKey, i); + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhKey); + } + MessageExitOnFailure(hr, msierrIISFailedOpenKey, "failed to open metabase key: %ls", pwzKey); + + if (lstrlenW(pwzKey) < 3) + { + ExitOnFailure(hr = E_INVALIDARG, "Key didn't begin with \"/LM\" as expected - key value: %ls", pwzKey); + } + + hr = piMetabase->SetData(mhKey, pwzKey + 3, &mr); // pwzKey + 3 to skip "/LM" that was used to open the key. + + // This means we're trying to write to a secure key without the secure flag set - let's try again with the secure flag set + if (MD_ERROR_CANNOT_REMOVE_SECURE_ATTRIBUTE == hr) + { + mr.dwMDAttributes |= METADATA_SECURE; + hr = piMetabase->SetData(mhKey, pwzKey + 3, &mr); + } + + // If IIS6 returned failure, let's check if the correct value exists in the metabase before actually failing the CA + if (FAILED(hr)) + { + // Backup the original failure error, so we can log it below if necessary + HRESULT hrOldValue = hr; + + mrGet.dwMDIdentifier = mr.dwMDIdentifier; + mrGet.dwMDAttributes = METADATA_NO_ATTRIBUTES; + mrGet.dwMDUserType = mr.dwMDUserType; + mrGet.dwMDDataType = mr.dwMDDataType; + mrGet.dwMDDataLen = mr.dwMDDataLen; + mrGet.pbMDData = static_cast(MemAlloc(mr.dwMDDataLen, TRUE)); + + hr = piMetabase->GetData(mhKey, pwzKey + 3, &mrGet, &dwTemp); + if (SUCCEEDED(hr)) + { + if (mrGet.dwMDDataType == mr.dwMDDataType && mrGet.dwMDDataLen == mr.dwMDDataLen && 0 == memcmp(mrGet.pbMDData, mr.pbMDData, mr.dwMDDataLen)) + { + WcaLog(LOGMSG_VERBOSE, "Received error while writing metabase value under key: %ls ID: %d with error 0x%x, but the correct value is in the metabase - continuing", pwzKey, mr.dwMDIdentifier, hrOldValue); + hr = S_OK; + } + else + { + WcaLog(LOGMSG_VERBOSE, "Succeeded in checking metabase value after write value, but the values didn't match"); + hr = hrOldValue; + } + } + else + { + WcaLog(LOGMSG_VERBOSE, "Failed to check value after metabase write failure (error code 0x%x)", hr); + hr = hrOldValue; + } + } + MessageExitOnFailure(hr, msierrIISFailedWriteData, "failed to write data to metabase key: %ls", pwzKey); + + hr = WcaProgressMessage(COST_IIS_WRITEVALUE, FALSE); + +LExit: + ReleaseStr(pwzTemp); + ReleaseStr(pwzKey); + + if (mhKey) + { + piMetabase->CloseKey(mhKey); + } + + if (fFreeData && mr.pbMDData) + { + MemFree(mr.pbMDData); + } + + return hr; +} + + +/******************************************************************** + DeleteMetabaseValue -Installs metabase values + + Input: deferred CustomActionData - Key\tIdentifier\tAttributes\tUserType\tDataType\tData + * *****************************************************************/ +static HRESULT DeleteMetabaseValue(__in LPWSTR* ppwzCustomActionData, __in IMSAdminBase* piMetabase) +{ + //AssertSz(FALSE, "debug DeleteMetabaseValue here"); + HRESULT hr = S_OK; + + METADATA_HANDLE mhKey = 0; + + LPWSTR pwzKey = NULL; + DWORD dwIdentifier = 0; + DWORD dwDataType = 0; + + int i; + + // get the key first + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzKey); + ExitOnFailure(hr, "failed to read key"); + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&dwIdentifier)); + ExitOnFailure(hr, "failed to read identifier"); + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&dwDataType)); + ExitOnFailure(hr, "failed to read data type"); + + WcaLog(LOGMSG_VERBOSE, "Deleting Metabase Value Under Key: %ls ID: %d", pwzKey, dwIdentifier); + + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhKey); + for (i = 30; i > 0 && HRESULT_FROM_WIN32(ERROR_PATH_BUSY) == hr; i--) + { + ::Sleep(1000); + WcaLog(LOGMSG_STANDARD, "failed to open '%ls' key, retrying %d time(s)...", pwzKey, i); + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhKey); + } + MessageExitOnFailure(hr, msierrIISFailedOpenKey, "failed to open metabase key: %ls", pwzKey); + + if (lstrlenW(pwzKey) < 3) + { + ExitOnFailure(hr = E_INVALIDARG, "Key didn't begin with \"/LM\" as expected - key value: %ls", pwzKey); + } + + hr = piMetabase->DeleteData(mhKey, pwzKey + 3, dwIdentifier, dwDataType); // pwzKey + 3 to skip "/LM" that was used to open the key. + if (MD_ERROR_DATA_NOT_FOUND == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) + { + hr = S_OK; + } + MessageExitOnFailure(hr, msierrIISFailedDeleteValue, "failed to delete value %d from metabase key: %ls", dwIdentifier, pwzKey); + + hr = WcaProgressMessage(COST_IIS_DELETEVALUE, FALSE); +LExit: + ReleaseStr(pwzKey); + + if (mhKey) + piMetabase->CloseKey(mhKey); + + return hr; +} + + +/******************************************************************** + DeleteAspApp - Deletes applications in IIS + + Input: deferred CustomActionData - MetabaseRoot\tRecursive + * *****************************************************************/ +static HRESULT DeleteAspApp(__in LPWSTR* ppwzCustomActionData, __in IMSAdminBase* piMetabase, __in ICatalogCollection* pApplicationCollection, __in IWamAdmin* piWam) +{ + const int BUFFER_BYTES = 512; + const BSTR bstrPropName = SysAllocString(L"Deleteable"); + + HRESULT hr = S_OK; + ICatalogObject* pApplication = NULL; + + LPWSTR pwzRoot = NULL; + DWORD dwActualBufferSize = 0; + long lSize = 0; + long lIndex = 0; + long lChanges = 0; + + VARIANT keyValue; + ::VariantInit(&keyValue); + VARIANT propValue; + propValue.vt = VT_BOOL; + propValue.boolVal = TRUE; + + METADATA_RECORD mr; + // Get current set of web service extensions. + ::ZeroMemory(&mr, sizeof(mr)); + mr.dwMDIdentifier = MD_APP_PACKAGE_ID; + mr.dwMDAttributes = 0; + mr.dwMDUserType = ASP_MD_UT_APP; + mr.dwMDDataType = STRING_METADATA; + mr.pbMDData = new unsigned char[BUFFER_BYTES]; + mr.dwMDDataLen = BUFFER_BYTES; + + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzRoot); // MetabaseRoot + ExitOnFailure(hr, "failed to get metabase root"); + + hr = piMetabase->GetData(METADATA_MASTER_ROOT_HANDLE, pwzRoot, &mr, &dwActualBufferSize); + if (HRESULT_FROM_WIN32(MD_ERROR_DATA_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + // This one doesn't have an independent app GUID associated with it - it may have been already partially deleted, or a low isolation app + WcaLog(LOGMSG_VERBOSE, "No independent COM+ application found associated with %ls. It either doesn't exist, or was already removed - continuing", pwzRoot); + ExitFunction1(hr = S_OK); + } + MessageExitOnFailure(hr, msierrIISFailedDeleteApp, "failed to get GUID for application at path: %ls", pwzRoot); + + WcaLog(LOGMSG_VERBOSE, "Deleting ASP App (used query: %ls) with GUID: %ls", pwzRoot, (LPWSTR)(mr.pbMDData)); + + // Delete the application association from IIS's point of view before it's obliterated from the application collection + hr = piWam->AppDelete(pwzRoot, FALSE); + if (FAILED(hr)) + { + // This isn't necessarily an error if we fail here, but let's log a failure if it happens + WcaLog(LOGMSG_VERBOSE, "error 0x%x: failed to call IWamAdmin::AppDelete() while removing web application - continuing"); + hr = S_OK; + } + + if (!pApplicationCollection) + { + WcaLog(LOGMSG_STANDARD, "Could not remove application with GUID %ls because the application collection could not be found", (LPWSTR)(mr.pbMDData)); + ExitFunction1(hr = S_OK); + } + + hr = pApplicationCollection->Populate(); + MessageExitOnFailure(hr, msierrIISFailedDeleteApp, "failed to populate Application collection"); + + hr = pApplicationCollection->get_Count(&lSize); + MessageExitOnFailure(hr, msierrIISFailedDeleteApp, "failed to get size of Application collection"); + WcaLog(LOGMSG_TRACEONLY, "Found %u items in application collection", lSize); + + // No need to check this too early, as we may not even need this to have successfully allocated + ExitOnNull(bstrPropName, hr, E_OUTOFMEMORY, "failed to allocate memory for \"Deleteable\" string"); + + for (lIndex = 0; lIndex < lSize; ++lIndex) + { + hr = pApplicationCollection->get_Item(lIndex, reinterpret_cast(&pApplication)); + MessageExitOnFailure(hr, msierrIISFailedDeleteApp, "failed to get COM+ application while enumerating through COM+ applications"); + + hr = pApplication->get_Key(&keyValue); + MessageExitOnFailure(hr, msierrIISFailedDeleteApp, "failed to get key of COM+ application while enumerating through COM+ applications"); + + WcaLog(LOGMSG_TRACEONLY, "While enumerating through COM+ applications, found an application with GUID: %ls", (LPWSTR)keyValue.bstrVal); + + if (VT_BSTR == keyValue.vt && 0 == lstrcmpW((LPWSTR)keyValue.bstrVal, (LPWSTR)(mr.pbMDData))) + { + hr = pApplication->put_Value(bstrPropName, propValue); + if (FAILED(hr)) + { + // This isn't necessarily a critical error unless we fail to actually delete it in the next step + WcaLog(LOGMSG_VERBOSE, "error 0x%x: failed to ensure COM+ application with guid %ls is deletable - continuing", hr, (LPWSTR)(mr.pbMDData)); + } + + hr = pApplicationCollection->SaveChanges(&lChanges); + if (FAILED(hr)) + { + // This isn't necessarily a critical error unless we fail to actually delete it in the next step + WcaLog(LOGMSG_VERBOSE, "error 0x%x: failed to save changes while ensuring COM+ application with guid %ls is deletable - continuing", hr, (LPWSTR)(mr.pbMDData)); + } + + hr = pApplicationCollection->Remove(lIndex); + if (FAILED(hr)) + { + WcaLog(LOGMSG_STANDARD, "error 0x%x: failed to remove COM+ application with guid %ls. The COM application will not be removed", hr, (LPWSTR)(mr.pbMDData)); + } + else + { + hr = pApplicationCollection->SaveChanges(&lChanges); + if (FAILED(hr)) + { + WcaLog(LOGMSG_STANDARD, "error 0x%x: failed to save changes when removing COM+ application with guid %ls. The COM application will not be removed - continuing", hr, (LPWSTR)(mr.pbMDData)); + } + else + { + WcaLog(LOGMSG_VERBOSE, "Found and removed application with GUID %ls", (LPWSTR)(mr.pbMDData)); + } + } + + // We've found the right key and successfully deleted the app - let's exit the loop now + lIndex = lSize; + } + } + // If we didn't find it, it isn't an error, because the application we want to delete doesn't seem to exist! + + hr = WcaProgressMessage(COST_IIS_DELETEAPP, FALSE); +LExit: + ReleaseBSTR(bstrPropName); + + ReleaseStr(pwzRoot); + // Don't release pApplication, because it points to an object within the collection + + delete [] mr.pbMDData; + + return hr; +} + + +/******************************************************************** + CreateAspApp - Creates applications in IIS + + Input: deferred CustomActionData - MetabaseRoot\tInProc + * ****************************************************************/ +static HRESULT CreateAspApp(__in LPWSTR* ppwzCustomActionData, __in IWamAdmin* piWam) +{ + HRESULT hr = S_OK; + + LPWSTR pwzRoot = NULL; + BOOL fInProc; + + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzRoot); // MetabaseRoot + ExitOnFailure(hr, "failed to get metabase root"); + hr = WcaReadIntegerFromCaData(ppwzCustomActionData, reinterpret_cast(&fInProc)); // InProc + ExitOnFailure(hr, "failed to get in proc flag"); + + WcaLog(LOGMSG_VERBOSE, "Creating ASP App: %ls", pwzRoot); + + hr = piWam->AppCreate(pwzRoot, fInProc); + MessageExitOnFailure(hr, msierrIISFailedCreateApp, "failed to create web application: %ls", pwzRoot); + + hr = WcaProgressMessage(COST_IIS_CREATEAPP, FALSE); +LExit: + return hr; +} + + +/******************************************************************** + DeleteMetabaseKey - Deletes metabase keys + + Input: deferred CustomActionData - Key + ******************************************************************/ +static HRESULT DeleteMetabaseKey(__in LPWSTR *ppwzCustomActionData, __in IMSAdminBase* piMetabase) +{ + HRESULT hr = S_OK; + + METADATA_HANDLE mhRoot = 0; + + LPWSTR pwzData = NULL; + + LPCWSTR pwzKey; + int i; + + hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzData); + ExitOnFailure(hr, "failed to read key to be deleted"); + + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhRoot); + for (i = 30; i > 0 && HRESULT_FROM_WIN32(ERROR_PATH_BUSY) == hr; i--) + { + ::Sleep(1000); + WcaLog(LOGMSG_STANDARD, "failed to open root key, retrying %d time(s)...", i); + hr = piMetabase->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", METADATA_PERMISSION_WRITE, 10, &mhRoot); + } + MessageExitOnFailure(hr, msierrIISFailedOpenKey, "failed to open metabase key: %ls", L"/LM"); + + pwzKey = pwzData + 3; + + WcaLog(LOGMSG_VERBOSE, "Deleting Metabase Key: %ls", pwzKey); + + hr = piMetabase->DeleteKey(mhRoot, pwzKey); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) + { + WcaLog(LOGMSG_STANDARD, "Key `%ls` did not exist, continuing.", pwzData); + hr = S_OK; + } + MessageExitOnFailure(hr, msierrIISFailedDeleteKey, "failed to delete metabase key: %ls", pwzData); + + hr = WcaProgressMessage(COST_IIS_DELETEKEY, FALSE); +LExit: + if (mhRoot) + { + piMetabase->CloseKey(mhRoot); + } + + return hr; +} + + +/******************************************************************** + WriteMetabaseChanges - CUSTOM ACTION ENTRY POINT for IIS Metabase changes + + *******************************************************************/ +extern "C" UINT __stdcall WriteMetabaseChanges(MSIHANDLE hInstall) +{ +//AssertSz(FALSE, "debug WriteMetabaseChanges here"); + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + IMSAdminBase* piMetabase = NULL; + IWamAdmin* piWam = NULL; + ICOMAdminCatalog* pCatalog = NULL; + ICatalogCollection* pApplicationCollection = NULL; + WCA_CASCRIPT_HANDLE hWriteMetabaseScript = NULL; + BSTR bstrApplications = SysAllocString(L"Applications"); + BOOL fInitializedCom = FALSE; + + LPWSTR pwzData = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzScriptKey = NULL; + METABASE_ACTION maAction = MBA_UNKNOWNACTION; + + hr = WcaInitialize(hInstall, "WriteMetabaseChanges"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + // Get the CaScript key + hr = WcaReadStringFromCaData(&pwzData, &pwzScriptKey); + ExitOnFailure(hr, "Failed to get CaScript key from custom action data"); + + hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_SCHEDULED, FALSE, pwzScriptKey, &hWriteMetabaseScript); + ExitOnFailure(hr, "Failed to open CaScript file"); + + // The rest of our existing custom action data string should be empty - go ahead and overwrite it + ReleaseNullStr(pwzData); + hr = WcaCaScriptReadAsCustomActionData(hWriteMetabaseScript, &pwzData); + ExitOnFailure(hr, "Failed to read script into CustomAction data."); + + pwz = pwzData; + + while (S_OK == (hr = WcaReadIntegerFromCaData(&pwz, (int *)&maAction))) + { + switch (maAction) + { + case MBA_CREATEAPP: + if (NULL == piWam) + { + hr = ::CoCreateInstance(CLSID_WamAdmin, NULL, CLSCTX_ALL, IID_IWamAdmin, reinterpret_cast(&piWam)); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IWamAdmin object"); + } + + hr = CreateAspApp(&pwz, piWam); + ExitOnFailure(hr, "failed to create ASP App"); + break; + case MBA_DELETEAPP: + if (NULL == piMetabase) + { + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IIMSAdminBase object"); + } + + if (NULL == pCatalog) + { + hr = CoCreateInstance(CLSID_COMAdminCatalog, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pCatalog); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_ICOMAdmin object"); + + hr = pCatalog->GetCollection(bstrApplications, reinterpret_cast(&pApplicationCollection)); + if (FAILED(hr)) + { + hr = S_OK; + WcaLog(LOGMSG_STANDARD, "error 0x%x: failed to get ApplicationCollection object for list of COM+ applications - COM+ applications will not be able to be uninstalled - continuing", hr); + } + } + + if (NULL == piWam) + { + hr = ::CoCreateInstance(CLSID_WamAdmin, NULL, CLSCTX_ALL, IID_IWamAdmin, reinterpret_cast(&piWam)); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IWamAdmin object"); + } + + hr = DeleteAspApp(&pwz, piMetabase, pApplicationCollection, piWam); + ExitOnFailure(hr, "failed to delete ASP App"); + break; + case MBA_CREATEKEY: + if (NULL == piMetabase) + { + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IIMSAdminBase object"); + } + + hr = CreateMetabaseKey(&pwz, piMetabase); + ExitOnFailure(hr, "failed to create metabase key"); + break; + case MBA_DELETEKEY: + if (NULL == piMetabase) + { + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IIMSAdminBase object"); + } + + hr = DeleteMetabaseKey(&pwz, piMetabase); + ExitOnFailure(hr, "failed to delete metabase key"); + break; + case MBA_WRITEVALUE: + if (NULL == piMetabase) + { + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IIMSAdminBase object"); + } + + hr = WriteMetabaseValue(&pwz, piMetabase); + ExitOnFailure(hr, "failed to write metabase value"); + break; + case MBA_DELETEVALUE: + if (NULL == piMetabase) + { + hr = ::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, reinterpret_cast(&piMetabase)); + MessageExitOnFailure(hr, msierrIISCannotConnect, "failed to get IID_IIMSAdminBase object"); + } + + hr = DeleteMetabaseValue(&pwz, piMetabase); + ExitOnFailure(hr, "failed to delete metabase value"); + break; + default: + ExitOnFailure(hr = E_UNEXPECTED, "Unexpected metabase action specified: %d", maAction); + break; + } + } + if (E_NOMOREITEMS == hr) // If there are no more items, all is well + { + if (NULL != piMetabase) + { + hr = piMetabase->SaveData(); + for (int i = 30; i > 0 && HRESULT_FROM_WIN32(ERROR_PATH_BUSY) == hr; i--) + { + ::Sleep(1000); + WcaLog(LOGMSG_VERBOSE, "Failed to force save of metabase data, retrying %d time(s)...", i); + hr = piMetabase->SaveData(); + } + if (FAILED(hr)) + { + WcaLog(LOGMSG_VERBOSE, "Failed to force save of metabase data: 0x%x - continuing", hr); + } + hr = S_OK; + } + else + { + hr = S_OK; + } + } + +LExit: + WcaCaScriptClose(hWriteMetabaseScript, WCA_CASCRIPT_CLOSE_DELETE); + + ReleaseBSTR(bstrApplications); + ReleaseStr(pwzScriptKey); + ReleaseStr(pwzData); + ReleaseObject(piMetabase); + ReleaseObject(piWam); + ReleaseObject(pCatalog); + ReleaseObject(pApplicationCollection); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} +/******************************************************************** + WriteIIS7ConfigChanges - CUSTOM ACTION ENTRY POINT for IIS7 config changes + + *******************************************************************/ +extern "C" UINT __stdcall WriteIIS7ConfigChanges(MSIHANDLE hInstall) +{ + //AssertSz(FALSE, "debug WriteIIS7ConfigChanges here"); + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR pwzData = NULL; + LPWSTR pwzScriptKey = NULL; + LPWSTR pwzHashString = NULL; + BYTE rgbActualHash[SHA1_HASH_LEN] = { }; + DWORD dwHashedBytes = SHA1_HASH_LEN; + + WCA_CASCRIPT_HANDLE hWriteIis7Script = NULL; + + hr = WcaInitialize(hInstall, "WriteIIS7ConfigChanges"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = WcaGetProperty( L"CustomActionData", &pwzScriptKey); + ExitOnFailure(hr, "Failed to get CustomActionData"); + WcaLog(LOGMSG_TRACEONLY, "Script WriteIIS7ConfigChanges: %ls", pwzScriptKey); + + hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_SCHEDULED, FALSE, pwzScriptKey, &hWriteIis7Script); + ExitOnFailure(hr, "Failed to open CaScript file"); + + hr = WcaCaScriptReadAsCustomActionData(hWriteIis7Script, &pwzData); + ExitOnFailure(hr, "Failed to read script into CustomAction data."); + + hr = CrypHashBuffer((BYTE*)pwzData, sizeof(pwzData) * sizeof(WCHAR), PROV_RSA_AES, CALG_SHA1, rgbActualHash, dwHashedBytes); + ExitOnFailure(hr, "Failed to calculate hash of CustomAction data."); + + hr = StrAlloc(&pwzHashString, ((dwHashedBytes * 2) + 1)); + ExitOnFailure(hr, "Failed to allocate string for script hash"); + + hr = StrHexEncode(rgbActualHash, dwHashedBytes, pwzHashString, ((dwHashedBytes * 2) + 1)); + ExitOnFailure(hr, "Failed to convert hash bytes to string."); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData WriteIIS7ConfigChanges: %ls", pwzData); + WcaLog(LOGMSG_VERBOSE, "Custom action data hash: %ls", pwzHashString); + WcaLog(LOGMSG_VERBOSE, "CustomActionData WriteIIS7ConfigChanges length: %d", wcslen(pwzData)); + + hr = IIS7ConfigChanges(hInstall, pwzData); + ExitOnFailure(hr, "WriteIIS7ConfigChanges Failed."); + +LExit: + WcaCaScriptClose(hWriteIis7Script, WCA_CASCRIPT_CLOSE_DELETE); + ReleaseStr(pwzScriptKey); + ReleaseStr(pwzData); + ReleaseStr(pwzHashString); + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + + return WcaFinalize(er); +} + + +/******************************************************************** + CommitIIS7ConfigTransaction - CUSTOM ACTION ENTRY POINT for unbacking up config + + Input: deferred CustomActionData - BackupName + * *****************************************************************/ +extern "C" UINT __stdcall CommitIIS7ConfigTransaction(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR pwzData = NULL; + WCHAR wzConfigCopy[MAX_PATH]; + DWORD dwSize = 0; + + BOOL fIsWow64Process = FALSE; + BOOL fIsFSRedirectDisabled = FALSE; + + hr = WcaInitialize(hInstall, "CommitIIS7ConfigTransaction"); + ExitOnFailure(hr, "failed to initialize IIS7 commit transaction"); + + WcaInitializeWow64(); + fIsWow64Process = WcaIsWow64Process(); + if (fIsWow64Process) + { + hr = WcaDisableWow64FSRedirection(); + if(FAILED(hr)) + { + //eat this error + hr = S_OK; + } + else + { + fIsFSRedirectDisabled = TRUE; + } + } + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + // Config AdminMgr changes already committed, just + // delete backup config file. + + dwSize = ExpandEnvironmentStringsW(L"%windir%\\system32\\inetsrv\\config\\applicationHost.config", + wzConfigCopy, + MAX_PATH + ); + if ( dwSize == 0 ) + { + ExitWithLastError(hr, "failed to get ExpandEnvironmentStrings"); + } + + hr = ::StringCchCatW(wzConfigCopy, MAX_PATH, L"."); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCatW of ."); + + hr = ::StringCchCatW(wzConfigCopy, MAX_PATH, pwzData); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCatW of extension"); + + if (!::DeleteFileW(wzConfigCopy)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + WcaLog(LOGMSG_STANDARD, "Failed to delete backup applicationHost, not found - continuing"); + hr = S_OK; + } + else + { + ExitOnFailure(hr, "failed to delete config backup"); + } + } + +LExit: + ReleaseStr(pwzData); + + // Make sure we revert FS Redirection if necessary before exiting + if (fIsFSRedirectDisabled) + { + fIsFSRedirectDisabled = FALSE; + WcaRevertWow64FSRedirection(); + } + WcaFinalizeWow64(); + + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} +/******************************************************************** + StartIIS7Config Transaction - CUSTOM ACTION ENTRY POINT for backing up config + + Input: deferred CustomActionData - BackupName +********************************************************************/ +extern "C" UINT __stdcall StartIIS7ConfigTransaction(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR pwzData = NULL; + WCHAR wzConfigSource[MAX_PATH]; + WCHAR wzConfigCopy[MAX_PATH]; + DWORD dwSize = 0; + + + BOOL fIsWow64Process = FALSE; + BOOL fIsFSRedirectDisabled = FALSE; + + // initialize + hr = WcaInitialize(hInstall, "StartIIS7ConfigTransaction"); + ExitOnFailure(hr, "failed to initialize StartIIS7ConfigTransaction"); + + WcaInitializeWow64(); + fIsWow64Process = WcaIsWow64Process(); + if (fIsWow64Process) + { + hr = WcaDisableWow64FSRedirection(); + if(FAILED(hr)) + { + //eat this error + hr = S_OK; + } + else + { + fIsFSRedirectDisabled = TRUE; + } + } + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + + dwSize = ExpandEnvironmentStringsW(L"%windir%\\system32\\inetsrv\\config\\applicationHost.config", + wzConfigSource, + MAX_PATH + ); + if ( dwSize == 0 ) + { + ExitWithLastError(hr, "failed to get ExpandEnvironmentStrings"); + } + hr = ::StringCchCopyW(wzConfigCopy, MAX_PATH, wzConfigSource); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCopyW"); + + //add ca action as extension + + hr = ::StringCchCatW(wzConfigCopy, MAX_PATH, L"."); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCatW of ."); + + hr = ::StringCchCatW(wzConfigCopy, MAX_PATH, pwzData); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCatW of extension"); + + if ( !::CopyFileW(wzConfigSource, wzConfigCopy, FALSE) ) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + // IIS may not be installed on the machine, we'll fail later if we try to install anything + WcaLog(LOGMSG_STANDARD, "Failed to back up applicationHost, not found - continuing"); + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to copy config backup %ls -> %ls", wzConfigSource, wzConfigCopy); + } + } + + + hr = WcaProgressMessage(COST_IIS_TRANSACTIONS, FALSE); + + +LExit: + + ReleaseStr(pwzData); + + // Make sure we revert FS Redirection if necessary before exiting + if (fIsFSRedirectDisabled) + { + fIsFSRedirectDisabled = FALSE; + WcaRevertWow64FSRedirection(); + } + WcaFinalizeWow64(); + + if (FAILED(hr)) + er = ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + + +/******************************************************************** + RollbackIIS7ConfigTransaction - CUSTOM ACTION ENTRY POINT for unbacking up config + + Input: deferred CustomActionData - BackupName +********************************************************************/ +extern "C" UINT __stdcall RollbackIIS7ConfigTransaction(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + LPWSTR pwzData = NULL; + WCHAR wzConfigSource[MAX_PATH]; + WCHAR wzConfigCopy[MAX_PATH]; + DWORD dwSize = 0; + + BOOL fIsWow64Process = FALSE; + BOOL fIsFSRedirectDisabled = FALSE; + + hr = WcaInitialize(hInstall, "RollbackIIS7ConfigTransaction"); + ExitOnFailure(hr, "failed to initialize"); + + WcaInitializeWow64(); + fIsWow64Process = WcaIsWow64Process(); + if (fIsWow64Process) + { + hr = WcaDisableWow64FSRedirection(); + if(FAILED(hr)) + { + //eat this error + hr = S_OK; + } + else + { + fIsFSRedirectDisabled = TRUE; + } + } + + hr = WcaGetProperty( L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + dwSize = ExpandEnvironmentStringsW(L"%windir%\\system32\\inetsrv\\config\\applicationHost.config", + wzConfigSource, + MAX_PATH + ); + if ( dwSize == 0 ) + { + ExitWithLastError(hr, "failed to get ExpandEnvironmentStrings"); + } + hr = ::StringCchCopyW(wzConfigCopy, MAX_PATH, wzConfigSource); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCopyW"); + + //add ca action as extension + + hr = ::StringCchCatW(wzConfigCopy, MAX_PATH, L"."); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCatW of ."); + + hr = ::StringCchCatW(wzConfigCopy, MAX_PATH, pwzData); + ExitOnFailure(hr, "Commit IIS7 failed StringCchCatW of extension"); + + //copy is reverse of start transaction + if (!::CopyFileW(wzConfigCopy, wzConfigSource, FALSE)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + WcaLog(LOGMSG_STANDARD, "Failed to restore applicationHost, not found - continuing"); + hr = S_OK; + } + else + { + ExitOnFailure(hr, "failed to restore config backup"); + } + } + + if (!::DeleteFileW(wzConfigCopy)) + { + ExitWithLastError(hr, "failed to delete config backup"); + } + + hr = WcaProgressMessage(COST_IIS_TRANSACTIONS, FALSE); + +LExit: + ReleaseStr(pwzData); + + // Make sure we revert FS Redirection if necessary before exiting + if (fIsFSRedirectDisabled) + { + fIsFSRedirectDisabled = FALSE; + WcaRevertWow64FSRedirection(); + } + WcaFinalizeWow64(); + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + return WcaFinalize(er); +} -- cgit v1.2.3-55-g6feb