// 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" #define XMLFILE_CREATE_ELEMENT 0x00000001 #define XMLFILE_DELETE_VALUE 0x00000002 #define XMLFILE_BULKWRITE_VALUE 0x00000004 #define XMLFILE_DONT_UNINSTALL 0x00010000 #define XMLFILE_PRESERVE_MODIFIED 0x00001000 #define XMLFILE_USE_XPATH 0x00000100 extern BOOL vfMsxml30; enum eXmlAction { xaOpenFile = 1, xaOpenFilex64, xaWriteValue, xaDeleteValue, xaCreateElement, xaDeleteElement, xaBulkWriteValue, }; enum eXmlPreserveDate { xdDontPreserve = 0, xdPreserve }; enum eXmlSelectionLanguage { xsXSLPattern = 0, xsXPath = 1, }; LPCWSTR vcsXmlFileQuery = L"SELECT `Wix4XmlFile`.`XmlFile`, `Wix4XmlFile`.`File`, `Wix4XmlFile`.`ElementPath`, `Wix4XmlFile`.`Name`, `Wix4XmlFile`.`Value`, " L"`Wix4XmlFile`.`Flags`, `Wix4XmlFile`.`Component_`, `Component`.`Attributes` " L"FROM `Wix4XmlFile`,`Component` WHERE `Wix4XmlFile`.`Component_`=`Component`.`Component` ORDER BY `File`, `Sequence`"; enum eXmlFileQuery { xfqXmlFile = 1, xfqFile, xfqXPath, xfqName, xfqValue, xfqXmlFlags, xfqComponent, xfqCompAttributes }; struct XML_FILE_CHANGE { WCHAR wzId[MAX_DARWIN_KEY]; INSTALLSTATE isInstalled; INSTALLSTATE isAction; WCHAR wzFile[MAX_PATH]; LPWSTR pwzElementPath; WCHAR wzName[MAX_DARWIN_COLUMN]; LPWSTR pwzValue; int iXmlFlags; int iCompAttributes; XML_FILE_CHANGE* pxfcPrev; XML_FILE_CHANGE* pxfcNext; }; //static HRESULT FreeXmlFileChangeList( // __in XML_FILE_CHANGE* pxfcList // ) //{ // HRESULT hr = S_OK; // // XML_FILE_CHANGE* pxfcDelete; // while(pxfcList) // { // pxfcDelete = pxfcList; // pxfcList = pxfcList->pxfcNext; // // ReleaseStr(pxfcDelete->pwzElementPath); // ReleaseStr(pxfcDelete->pwzValue); // // hr = MemFree(pxfcDelete); // ExitOnFailure(hr, "failed to free xml file change list item"); // } // //LExit: // return hr; //} static HRESULT AddXmlFileChangeToList( __inout XML_FILE_CHANGE** ppxfcHead, __inout XML_FILE_CHANGE** ppxfcTail ) { Assert(ppxfcHead && ppxfcTail); HRESULT hr = S_OK; XML_FILE_CHANGE* pxfc = static_cast(MemAlloc(sizeof(XML_FILE_CHANGE), TRUE)); ExitOnNull(pxfc, hr, E_OUTOFMEMORY, "failed to allocate memory for new xml file change list element"); // Add it to the end of the list if (NULL == *ppxfcHead) { *ppxfcHead = pxfc; *ppxfcTail = pxfc; } else { Assert(*ppxfcTail && (*ppxfcTail)->pxfcNext == NULL); (*ppxfcTail)->pxfcNext = pxfc; pxfc->pxfcPrev = *ppxfcTail; *ppxfcTail = pxfc; } LExit: return hr; } static HRESULT ReadXmlFileTable( __inout XML_FILE_CHANGE** ppxfcHead, __inout XML_FILE_CHANGE** ppxfcTail ) { Assert(ppxfcHead && ppxfcTail); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; PMSIHANDLE hView = NULL; PMSIHANDLE hRec = NULL; LPWSTR pwzData = NULL; // check to see if necessary tables are specified if (S_FALSE == WcaTableExists(L"Wix4XmlFile")) { ExitFunction1(hr = S_FALSE); } // loop through all the xml configurations hr = WcaOpenExecuteView(vcsXmlFileQuery, &hView); ExitOnFailure(hr, "failed to open view on Wix4XmlFile table"); while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) { hr = AddXmlFileChangeToList(ppxfcHead, ppxfcTail); ExitOnFailure(hr, "failed to add xml file change to list"); // Get record Id hr = WcaGetRecordString(hRec, xfqXmlFile, &pwzData); ExitOnFailure(hr, "failed to get Wix4XmlFile record Id"); hr = StringCchCopyW((*ppxfcTail)->wzId, countof((*ppxfcTail)->wzId), pwzData); ExitOnFailure(hr, "failed to copy Wix4XmlFile record Id"); // Get component name hr = WcaGetRecordString(hRec, xfqComponent, &pwzData); ExitOnFailure(hr, "failed to get component name for Wix4XmlFile: %ls", (*ppxfcTail)->wzId); // Get the component's state er = ::MsiGetComponentStateW(WcaGetInstallHandle(), pwzData, &(*ppxfcTail)->isInstalled, &(*ppxfcTail)->isAction); ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "failed to get install state for Component: %ls", pwzData); // Get the xml file hr = WcaGetRecordFormattedString(hRec, xfqFile, &pwzData); ExitOnFailure(hr, "failed to get xml file for Wix4XmlFile: %ls", (*ppxfcTail)->wzId); hr = StringCchCopyW((*ppxfcTail)->wzFile, countof((*ppxfcTail)->wzFile), pwzData); ExitOnFailure(hr, "failed to copy xml file path"); // Get the Wix4XmlFile table flags hr = WcaGetRecordInteger(hRec, xfqXmlFlags, &(*ppxfcTail)->iXmlFlags); ExitOnFailure(hr, "failed to get Wix4XmlFile flags for Wix4XmlFile: %ls", (*ppxfcTail)->wzId); // Get the XPath hr = WcaGetRecordFormattedString(hRec, xfqXPath, &(*ppxfcTail)->pwzElementPath); ExitOnFailure(hr, "failed to get XPath for Wix4XmlFile: %ls", (*ppxfcTail)->wzId); // Get the name hr = WcaGetRecordFormattedString(hRec, xfqName, &pwzData); ExitOnFailure(hr, "failed to get Name for Wix4XmlFile: %ls", (*ppxfcTail)->wzId); hr = StringCchCopyW((*ppxfcTail)->wzName, countof((*ppxfcTail)->wzName), pwzData); ExitOnFailure(hr, "failed to copy name of element"); // Get the value hr = WcaGetRecordFormattedString(hRec, xfqValue, &pwzData); ExitOnFailure(hr, "failed to get Value for Wix4XmlFile: %ls", (*ppxfcTail)->wzId); hr = StrAllocString(&(*ppxfcTail)->pwzValue, pwzData, 0); ExitOnFailure(hr, "failed to allocate buffer for value"); // Get the component attributes hr = WcaGetRecordInteger(hRec, xfqCompAttributes, &(*ppxfcTail)->iCompAttributes); ExitOnFailure(hr, "failed to get component attributes for Wix4XmlFile: %ls", (*ppxfcTail)->wzId); } // 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"); LExit: ReleaseStr(pwzData); return hr; } static HRESULT BeginChangeFile( __in LPCWSTR pwzFile, __in XML_FILE_CHANGE* pxfc, __inout LPWSTR* ppwzCustomActionData ) { Assert(pwzFile && *pwzFile && ppwzCustomActionData); HRESULT hr = S_OK; BOOL fIs64Bit = pxfc->iCompAttributes & msidbComponentAttributes64bit; BOOL fUseXPath = pxfc->iXmlFlags & XMLFILE_USE_XPATH; LPBYTE pbData = NULL; SIZE_T cbData = 0; LPWSTR pwzRollbackCustomActionData = NULL; if (fIs64Bit) { hr = WcaWriteIntegerToCaData((int)xaOpenFilex64, ppwzCustomActionData); ExitOnFailure(hr, "failed to write 64-bit file indicator to custom action data"); } else { hr = WcaWriteIntegerToCaData((int)xaOpenFile, ppwzCustomActionData); ExitOnFailure(hr, "failed to write file indicator to custom action data"); } if (fUseXPath) { hr = WcaWriteIntegerToCaData((int)xsXPath, ppwzCustomActionData); ExitOnFailure(hr, "failed to write XPath selectionlanguage indicator to custom action data"); } else { hr = WcaWriteIntegerToCaData((int)xsXSLPattern, ppwzCustomActionData); ExitOnFailure(hr, "failed to write XSLPattern selectionlanguage indicator to custom action data"); } hr = WcaWriteStringToCaData(pwzFile, ppwzCustomActionData); ExitOnFailure(hr, "failed to write file to custom action data: %ls", pwzFile); // If the file already exits, then we have to put it back the way it was on failure if (FileExistsEx(pwzFile, NULL)) { hr = FileRead(&pbData, &cbData, pwzFile); ExitOnFailure(hr, "failed to read file: %ls", pwzFile); // Set up the rollback for this file hr = WcaWriteIntegerToCaData((int)fIs64Bit, &pwzRollbackCustomActionData); ExitOnFailure(hr, "failed to write component bitness to rollback custom action data"); hr = WcaWriteStringToCaData(pwzFile, &pwzRollbackCustomActionData); ExitOnFailure(hr, "failed to write file name to rollback custom action data: %ls", pwzFile); hr = WcaWriteStreamToCaData(pbData, cbData, &pwzRollbackCustomActionData); ExitOnFailure(hr, "failed to write file contents to rollback custom action data."); hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"ExecXmlFileRollback"), pwzRollbackCustomActionData, COST_XMLFILE); ExitOnFailure(hr, "failed to schedule ExecXmlFileRollback for file: %ls", pwzFile); ReleaseStr(pwzRollbackCustomActionData); } LExit: ReleaseMem(pbData); return hr; } static HRESULT WriteChangeData( __in XML_FILE_CHANGE* pxfc, __inout LPWSTR* ppwzCustomActionData ) { Assert(pxfc && ppwzCustomActionData); HRESULT hr = S_OK; hr = WcaWriteStringToCaData(pxfc->pwzElementPath, ppwzCustomActionData); ExitOnFailure(hr, "failed to write ElementPath to custom action data: %ls", pxfc->pwzElementPath); hr = WcaWriteStringToCaData(pxfc->wzName, ppwzCustomActionData); ExitOnFailure(hr, "failed to write Name to custom action data: %ls", pxfc->wzName); hr = WcaWriteStringToCaData(pxfc->pwzValue, ppwzCustomActionData); ExitOnFailure(hr, "failed to write Value to custom action data: %ls", pxfc->pwzValue); LExit: return hr; } /****************************************************************** SchedXmlFile - entry point for XmlFile Custom Action ********************************************************************/ extern "C" UINT __stdcall SchedXmlFile( __in MSIHANDLE hInstall ) { // AssertSz(FALSE, "debug SchedXmlFile"); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; LPWSTR pwzCurrentFile = NULL; BOOL fCurrentFileChanged = FALSE; BOOL fCurrentUseXPath = FALSE; PMSIHANDLE hView = NULL; PMSIHANDLE hRec = NULL; XML_FILE_CHANGE* pxfcHead = NULL; XML_FILE_CHANGE* pxfcTail = NULL; XML_FILE_CHANGE* pxfc = NULL; XML_FILE_CHANGE* pxfcUninstall = NULL; LPWSTR pwzCustomActionData = NULL; DWORD cFiles = 0; // initialize hr = WcaInitialize(hInstall, "SchedXmlFile"); ExitOnFailure(hr, "failed to initialize"); hr = ReadXmlFileTable(&pxfcHead, &pxfcTail); if (S_FALSE == hr) { WcaLog(LOGMSG_VERBOSE, "Skipping SchedXmlFile because Wix4XmlFile table not present"); ExitFunction1(hr = S_OK); } MessageExitOnFailure(hr, msierrXmlFileFailedRead, "failed to read Wix4XmlFile table"); // loop through all the xml configurations for (pxfc = pxfcHead; pxfc; pxfc = pxfc->pxfcNext) { // If this is the first file, a different file, the last file, or the SelectionLanguage property changes... if (NULL == pwzCurrentFile || 0 != lstrcmpW(pwzCurrentFile, pxfc->wzFile) || NULL == pxfc->pxfcNext || fCurrentUseXPath != ((XMLFILE_USE_XPATH & pxfc->iXmlFlags))) { // If this isn't the first file if (NULL != pwzCurrentFile) { // Do the uninstall work for the current file by walking backwards through the list (so the sequence is reversed) for (pxfcUninstall = ((NULL != pxfc->pxfcNext) ? pxfc->pxfcPrev : pxfc); pxfcUninstall && 0 == lstrcmpW(pwzCurrentFile, pxfcUninstall->wzFile) && fCurrentUseXPath == ((XMLFILE_USE_XPATH & pxfcUninstall->iXmlFlags)); pxfcUninstall = pxfcUninstall->pxfcPrev) { // If it's being uninstalled if (WcaIsUninstalling(pxfcUninstall->isInstalled, pxfcUninstall->isAction)) { // Uninstall the change if (!(XMLFILE_DONT_UNINSTALL & pxfcUninstall->iXmlFlags)) { if (!fCurrentFileChanged) { hr = BeginChangeFile(pwzCurrentFile, pxfcUninstall, &pwzCustomActionData); ExitOnFailure(hr, "failed to begin file change for file: %ls", pwzCurrentFile); fCurrentFileChanged = TRUE; ++cFiles; } if (XMLFILE_CREATE_ELEMENT & pxfcUninstall->iXmlFlags) { hr = WcaWriteIntegerToCaData((int)xaDeleteElement, &pwzCustomActionData); ExitOnFailure(hr, "failed to write delete element action indicator to custom action data"); } else { hr = WcaWriteIntegerToCaData((int)xaDeleteValue, &pwzCustomActionData); ExitOnFailure(hr, "failed to write delete value action indicator to custom action data"); } if (XMLFILE_PRESERVE_MODIFIED & pxfc->iXmlFlags) { hr = WcaWriteIntegerToCaData((int)xdPreserve, &pwzCustomActionData); ExitOnFailure(hr, "failed to write Preserve Date indicator to custom action data"); } else { hr = WcaWriteIntegerToCaData((int)xdDontPreserve, &pwzCustomActionData); ExitOnFailure(hr, "failed to write Don't Preserve Date indicator to custom action data"); } hr = WriteChangeData(pxfcUninstall, &pwzCustomActionData); ExitOnFailure(hr, "failed to write uninstall change data"); } } } } // Remember the file we're currently working on hr = StrAllocString(&pwzCurrentFile, pxfc->wzFile, 0); ExitOnFailure(hr, "failed to copy file name"); fCurrentUseXPath = (XMLFILE_USE_XPATH & pxfc->iXmlFlags); // We haven't changed the current file yet fCurrentFileChanged = FALSE; } // If it's being installed if (WcaIsInstalling(pxfc->isInstalled, pxfc->isAction)) { if (!fCurrentFileChanged) { hr = BeginChangeFile(pwzCurrentFile, pxfc, &pwzCustomActionData); ExitOnFailure(hr, "failed to begin file change for file: %ls", pwzCurrentFile); fCurrentFileChanged = TRUE; ++cFiles; } // Install the change if (XMLFILE_CREATE_ELEMENT & pxfc->iXmlFlags) { hr = WcaWriteIntegerToCaData((int)xaCreateElement, &pwzCustomActionData); ExitOnFailure(hr, "failed to write create element action indicator to custom action data"); } else if (XMLFILE_DELETE_VALUE & pxfc->iXmlFlags) { hr = WcaWriteIntegerToCaData((int)xaDeleteValue, &pwzCustomActionData); ExitOnFailure(hr, "failed to write delete value action indicator to custom action data"); } else if (XMLFILE_BULKWRITE_VALUE & pxfc->iXmlFlags) { hr = WcaWriteIntegerToCaData((int)xaBulkWriteValue, &pwzCustomActionData); ExitOnFailure(hr, "failed to write builkwrite value action indicator to custom action data"); } else { hr = WcaWriteIntegerToCaData((int)xaWriteValue, &pwzCustomActionData); ExitOnFailure(hr, "failed to write file indicator to custom action data"); } if (XMLFILE_PRESERVE_MODIFIED & pxfc->iXmlFlags) { hr = WcaWriteIntegerToCaData((int)xdPreserve, &pwzCustomActionData); ExitOnFailure(hr, "failed to write Preserve Date indicator to custom action data"); } else { hr = WcaWriteIntegerToCaData((int)xdDontPreserve, &pwzCustomActionData); ExitOnFailure(hr, "failed to write Don't Preserve Date indicator to custom action data"); } hr = WriteChangeData(pxfc, &pwzCustomActionData); ExitOnFailure(hr, "failed to write change data"); } } // 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 < cFiles); hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"ExecXmlFile"), pwzCustomActionData, cFiles * COST_XMLFILE); ExitOnFailure(hr, "failed to schedule ExecXmlFile action"); } LExit: ReleaseStr(pwzCurrentFile); ReleaseStr(pwzCustomActionData); return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : er); } /****************************************************************** ExecXmlFile - entry point for XmlFile Custom Action *******************************************************************/ extern "C" UINT __stdcall ExecXmlFile( __in MSIHANDLE hInstall ) { // AssertSz(FALSE, "debug ExecXmlFile"); HRESULT hr = S_OK; HRESULT hrOpenFailure = S_OK; UINT er = ERROR_SUCCESS; BOOL fIsFSRedirectDisabled = FALSE; BOOL fPreserveDate = FALSE; int id = IDRETRY; LPWSTR pwzCustomActionData = NULL; LPWSTR pwzData = NULL; LPWSTR pwzFile = NULL; LPWSTR pwzXPath = NULL; LPWSTR pwzName = NULL; LPWSTR pwzValue = NULL; LPWSTR pwz = NULL; IXMLDOMDocument* pixd = NULL; IXMLDOMNode* pixn = NULL; IXMLDOMNode* pixnNewNode = NULL; IXMLDOMNodeList* pixNodes = NULL; IXMLDOMDocument2 *pixdDocument2 = NULL; FILETIME ft; BSTR bstrProperty = ::SysAllocString(L"SelectionLanguage"); ExitOnNull(bstrProperty, hr, E_OUTOFMEMORY, "failed SysAllocString"); VARIANT varValue; ::VariantInit(&varValue); varValue.vt = VT_BSTR; varValue.bstrVal = ::SysAllocString(L"XPath"); ExitOnNull(varValue.bstrVal, hr, E_OUTOFMEMORY, "failed SysAllocString"); eXmlAction xa; eXmlPreserveDate xd; eXmlSelectionLanguage xl; // initialize hr = WcaInitialize(hInstall, "ExecXmlFile"); ExitOnFailure(hr, "failed to initialize"); hr = XmlInitialize(); ExitOnFailure(hr, "failed to initialize xml utilities"); hr = WcaGetProperty( L"CustomActionData", &pwzCustomActionData); ExitOnFailure(hr, "failed to get CustomActionData"); WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzCustomActionData); pwz = pwzCustomActionData; hr = WcaReadIntegerFromCaData(&pwz, (int*) &xa); ExitOnFailure(hr, "failed to process CustomActionData"); #ifndef _WIN64 // Initialize the Wow64 API - store the result in fWow64APIPresent // If it fails, this doesn't warrant an error yet, because we only need the Wow64 API in some cases WcaInitializeWow64(); BOOL fIsWow64Process = WcaIsWow64Process(); #endif if (xaOpenFile != xa && xaOpenFilex64 != xa) ExitOnFailure(hr = E_INVALIDARG, "invalid custom action data"); // loop through all the passed in data while (pwz && *pwz) { hr = WcaReadIntegerFromCaData(&pwz, (int*) &xl); ExitOnFailure(hr, "failed to process CustomActionData"); hr = WcaReadStringFromCaData(&pwz, &pwzFile); ExitOnFailure(hr, "failed to read file name from custom action data"); // Default to not preserve the modified date fPreserveDate = FALSE; // Open the file ReleaseNullObject(pixd); if (xaOpenFilex64 == xa) { #ifndef _WIN64 if (!fIsWow64Process) { hr = E_NOTIMPL; ExitOnFailure(hr, "Custom action was told to act on a 64-bit component, but the custom action process is not running in WOW."); } hr = WcaDisableWow64FSRedirection(); ExitOnFailure(hr, "Custom action was told to act on a 64-bit component, but was unable to disable filesystem redirection through the Wow64 API."); fIsFSRedirectDisabled = TRUE; #endif } hr = XmlLoadDocumentFromFileEx(pwzFile, XML_LOAD_PRESERVE_WHITESPACE, &pixd); if (FAILED(hr)) { // Ignore the return code for now. If they try to add something, we'll fail the install. If all they do is remove stuff then it doesn't matter. hrOpenFailure = hr; hr = S_OK; } else { hrOpenFailure = S_OK; } WcaLog(LOGMSG_VERBOSE, "Configuring Xml File: %ls", pwzFile); if (xsXPath == xl) { if (vfMsxml30) { // If we failed to open the file, don't fail immediately; just skip setting the selection language, and we'll fail later if appropriate if (SUCCEEDED(hrOpenFailure)) { hr = pixd->QueryInterface(XmlUtil_IID_IXMLDOMDocument2, (void**)&pixdDocument2); ExitOnFailure(hr, "failed in querying IXMLDOMDocument2 interface"); hr = pixdDocument2->setProperty(bstrProperty, varValue); ExitOnFailure(hr, "failed in setting SelectionLanguage"); } } else { ExitOnFailure(hr = E_NOTIMPL, "Error: current MSXML version does not support xpath query."); } } while (pwz && *pwz) { hr = WcaReadIntegerFromCaData(&pwz, (int*) &xa); ExitOnFailure(hr, "failed to process CustomActionData"); // Break if we need to move on to a different file if (xaOpenFile == xa || xaOpenFilex64 == xa) break; hr = WcaReadIntegerFromCaData(&pwz, (int*) &xd); ExitOnFailure(hr, "failed to process CustomActionData"); if (xdPreserve == xd) { fPreserveDate = TRUE; } // Get path, name, and value to be written hr = WcaReadStringFromCaData(&pwz, &pwzXPath); ExitOnFailure(hr, "failed to process CustomActionData"); hr = WcaReadStringFromCaData(&pwz, &pwzName); ExitOnFailure(hr, "failed to process CustomActionData"); hr = WcaReadStringFromCaData(&pwz, &pwzValue); ExitOnFailure(hr, "failed to process CustomActionData"); // If we failed to open the file and we're adding something to the file, we've got a problem. Otherwise, just continue on since the file's already gone. if (FAILED(hrOpenFailure)) { if (xaCreateElement == xa || xaWriteValue == xa || xaBulkWriteValue == xa) { MessageExitOnFailure(hr = hrOpenFailure, msierrXmlFileFailedOpen, "failed to load XML file: %ls", pwzFile); } else { continue; } } // Select the node we're about to modify ReleaseNullObject(pixn); if (xaBulkWriteValue == xa) { hr = XmlSelectNodes(pixd, pwzXPath, &pixNodes); if (S_FALSE == hr) { hr = HRESULT_FROM_WIN32(ERROR_OBJECT_NOT_FOUND); } MessageExitOnFailure(hr, msierrXmlFileFailedSelect, "failed to find any nodes: %ls in XML file: %ls", pwzXPath, pwzFile); for (;;) { pixNodes->nextNode(&pixn); if (NULL == pixn) break; if (pwzName && *pwzName) { // We're setting an attribute hr = XmlSetAttribute(pixn, pwzName, pwzValue); ExitOnFailure(hr, "failed to set attribute: %ls to value %ls", pwzName, pwzValue); } else { // We're setting the text of the node hr = XmlSetText(pixn, pwzValue); ExitOnFailure(hr, "failed to set text to: %ls for element %ls. Make sure that XPath points to an element.", pwzValue, pwzXPath); } ReleaseNullObject(pixn); } } else { hr = XmlSelectSingleNode(pixd, pwzXPath, &pixn); if (S_FALSE == hr) hr = HRESULT_FROM_WIN32(ERROR_OBJECT_NOT_FOUND); MessageExitOnFailure(hr, msierrXmlFileFailedSelect, "failed to find node: %ls in XML file: %ls", pwzXPath, pwzFile); // Make the modification if (xaWriteValue == xa) { if (pwzName && *pwzName) { // We're setting an attribute hr = XmlSetAttribute(pixn, pwzName, pwzValue); ExitOnFailure(hr, "failed to set attribute: %ls to value %ls", pwzName, pwzValue); } else { // We're setting the text of the node hr = XmlSetText(pixn, pwzValue); ExitOnFailure(hr, "failed to set text to: %ls for element %ls. Make sure that XPath points to an element.", pwzValue, pwzXPath); } } else if (xaCreateElement == xa) { hr = XmlCreateChild(pixn, pwzName, &pixnNewNode); ExitOnFailure(hr, "failed to create child element: %ls", pwzName); if (pwzValue && *pwzValue) { hr = XmlSetText(pixnNewNode, pwzValue); ExitOnFailure(hr, "failed to set text to: %ls for node: %ls", pwzValue, pwzName); } ReleaseNullObject(pixnNewNode); } else if (xaDeleteValue == xa) { if (pwzName && *pwzName) { // Delete the attribute hr = XmlRemoveAttribute(pixn, pwzName); ExitOnFailure(hr, "failed to remove attribute: %ls", pwzName); } else { // Clear the text value for the node hr = XmlSetText(pixn, L""); ExitOnFailure(hr, "failed to clear text value"); } } else if (xaDeleteElement == xa) { // TODO: This may be a little heavy handed hr = XmlRemoveChildren(pixn, pwzName); ExitOnFailure(hr, "failed to delete child node: %ls", pwzName); } else { ExitOnFailure(hr = E_UNEXPECTED, "Invalid modification specified in custom action data"); } } } // Now that we've made all of the changes to this file, save it and move on to the next if (S_OK == hrOpenFailure) { if (fPreserveDate) { hr = FileGetTime(pwzFile, NULL, NULL, &ft); ExitOnFailure(hr, "failed to get modified time of file : %ls", pwzFile); } int iSaveAttempt = 0; do { hr = XmlSaveDocument(pixd, pwzFile); if (FAILED(hr)) { id = WcaErrorMessage(msierrXmlConfigFailedSave, hr, INSTALLMESSAGE_ERROR | MB_ABORTRETRYIGNORE, 1, pwzFile); switch (id) { case IDABORT: ExitOnFailure(hr, "Failed to save changes to XML file: %ls", pwzFile); case IDRETRY: hr = S_FALSE; // hit me, baby, one more time break; case IDIGNORE: hr = S_OK; // pretend everything is okay and bail break; case 0: // No UI case, MsiProcessMessage returns 0 if (STIERR_SHARING_VIOLATION == hr) { // Only in case of sharing violation do we retry 30 times, once a second. if (iSaveAttempt < 30) { hr = S_FALSE; ++iSaveAttempt; WcaLog(LOGMSG_VERBOSE, "Unable to save changes to XML file: %ls, retry attempt: %x", pwzFile, iSaveAttempt); Sleep(1000); } else { ExitOnFailure(hr, "Failed to save changes to XML file: %ls", pwzFile); } } break; default: // Unknown error ExitOnFailure(hr, "Failed to save changes to XML file: %ls", pwzFile); } } } while (S_FALSE == hr); if (fPreserveDate) { hr = FileSetTime(pwzFile, NULL, NULL, &ft); ExitOnFailure(hr, "failed to set modified time of file : %ls", pwzFile); } if (fIsFSRedirectDisabled) { fIsFSRedirectDisabled = FALSE; WcaRevertWow64FSRedirection(); } } } LExit: // Make sure we revert FS Redirection if necessary before exiting if (fIsFSRedirectDisabled) { fIsFSRedirectDisabled = FALSE; WcaRevertWow64FSRedirection(); } #ifndef _WIN64 WcaFinalizeWow64(); #endif ReleaseStr(pwzCustomActionData); ReleaseStr(pwzData); ReleaseStr(pwzFile); ReleaseStr(pwzXPath); ReleaseStr(pwzName); ReleaseStr(pwzValue); ReleaseBSTR(bstrProperty); ReleaseVariant(varValue); ReleaseObject(pixdDocument2); ReleaseObject(pixn); ReleaseObject(pixd); ReleaseObject(pixnNewNode); ReleaseObject(pixNodes); XmlUninitialize(); return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : er); } /****************************************************************** ExecXmlFileRollback - entry point for XmlFile rollback Custom Action *******************************************************************/ extern "C" UINT __stdcall ExecXmlFileRollback( __in MSIHANDLE hInstall ) { // AssertSz(FALSE, "debug ExecXmlFileRollback"); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; int iIs64Bit; BOOL fIs64Bit = FALSE; LPWSTR pwzCustomActionData = NULL; LPWSTR pwz = NULL; LPWSTR pwzFileName = NULL; LPBYTE pbData = NULL; DWORD_PTR cbData = 0; FILETIME ft; HANDLE hFile = INVALID_HANDLE_VALUE; // initialize hr = WcaInitialize(hInstall, "ExecXmlFileRollback"); ExitOnFailure(hr, "failed to initialize"); hr = WcaGetProperty( L"CustomActionData", &pwzCustomActionData); ExitOnFailure(hr, "failed to get CustomActionData"); WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzCustomActionData); pwz = pwzCustomActionData; hr = WcaReadIntegerFromCaData(&pwz, &iIs64Bit); ExitOnFailure(hr, "failed to read component bitness from custom action data"); hr = WcaReadStringFromCaData(&pwz, &pwzFileName); ExitOnFailure(hr, "failed to read file name from custom action data"); hr = WcaReadStreamFromCaData(&pwz, &pbData, &cbData); ExitOnFailure(hr, "failed to read file contents from custom action data"); #ifndef _WIN64 fIs64Bit = (BOOL)iIs64Bit; if (fIs64Bit) { hr = WcaInitializeWow64(); if (S_FALSE == hr) { hr = TYPE_E_DLLFUNCTIONNOTFOUND; } ExitOnFailure(hr, "failed to initialize Wow64 API"); if (!WcaIsWow64Process()) { hr = E_NOTIMPL; ExitOnFailure(hr, "Custom action was told to rollback a 64-bit component, but the custom action process is not running in WOW."); } hr = WcaDisableWow64FSRedirection(); ExitOnFailure(hr, "Custom action was told to rollback a 64-bit component, but was unable to Disable Filesystem Redirection through the Wow64 API."); } #endif // Always preserve the modified date on rollback hr = FileGetTime(pwzFileName, NULL, NULL, &ft); ExitOnFailure(hr, "Failed to get modified date of file %ls.", pwzFileName); // Open the file hFile = ::CreateFileW(pwzFileName, GENERIC_WRITE, NULL, NULL, TRUNCATE_EXISTING, NULL, NULL); ExitOnInvalidHandleWithLastError(hFile, hr, "failed to open file: %ls", pwzFileName); // Write out the old data hr = FileWriteHandle(hFile, pbData, cbData); ExitOnFailure(hr, "failed to write to file: %ls", pwzFileName); ReleaseFile(hFile); // Always preserve the modified date on rollback hr = FileSetTime(pwzFileName, NULL, NULL, &ft); ExitOnFailure(hr, "Failed to set modified date of file %ls.", pwzFileName); LExit: ReleaseStr(pwzCustomActionData); ReleaseStr(pwzFileName); ReleaseFile(hFile); if (fIs64Bit) { WcaRevertWow64FSRedirection(); WcaFinalizeWow64(); } ReleaseMem(pbData); return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : er); }