/******************************************************************* XmlLoadDocumentFromFile ********************************************************************/ extern "C" HRESULT DAPI XmlLoadDocumentFromFile( __in_z LPCWSTR wzPath, __out IXMLDOMDocument** ppixdDocument ) { return XmlLoadDocumentFromFileEx(wzPath, 0, ppixdDocument); }
/****************************************************************** ExecXmlConfig - entry point for XmlConfig Custom Action *******************************************************************/ extern "C" UINT __stdcall ExecXmlConfig( __in MSIHANDLE hInstall ) { //AssertSz(FALSE, "debug ExecXmlConfig"); HRESULT hr = S_OK; HRESULT hrOpenFailure = S_OK; UINT er = ERROR_SUCCESS; BOOL fIsWow64Process = FALSE; BOOL fIsFSRedirectDisabled = FALSE; BOOL fPreserveDate = FALSE; LPWSTR pwzCustomActionData = NULL; LPWSTR pwzData = NULL; LPWSTR pwzFile = NULL; LPWSTR pwzElementPath = NULL; LPWSTR pwzVerifyPath = NULL; LPWSTR pwzName = NULL; LPWSTR pwzValue = NULL; LPWSTR pwz = NULL; int cAdditionalChanges = 0; IXMLDOMDocument* pixd = NULL; IXMLDOMNode* pixn = NULL; IXMLDOMNode* pixnVerify = NULL; IXMLDOMNode* pixnNewNode = NULL; IXMLDOMNode* pixnRemovedChild = NULL; IXMLDOMDocument* pixdNew = NULL; IXMLDOMElement* pixeNew = NULL; FILETIME ft; int id = IDRETRY; eXmlAction xa; eXmlPreserveDate xd; // initialize hr = WcaInitialize(hInstall, "ExecXmlConfig"); 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"); // 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(); fIsWow64Process = WcaIsWow64Process(); if (xaOpenFile != xa && xaOpenFilex64 != xa) { ExitOnFailure(hr = E_INVALIDARG, "invalid custom action data"); } // loop through all the passed in data while (pwz && *pwz) { hr = WcaReadStringFromCaData(&pwz, &pwzFile); ExitOnFailure(hr, "failed to read file name from custom action data"); // Default to not preserve date, preserve it if any modifications require us to fPreserveDate = FALSE; // Open the file ReleaseNullObject(pixd); if (xaOpenFilex64 == xa) { 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; } 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); while (pwz && *pwz) { // If we skip past an element that has additional changes we need to strip them off the stream before // moving on to the next element. Do that now and then restart the outer loop. if (cAdditionalChanges > 0) { while (cAdditionalChanges > 0) { hr = WcaReadStringFromCaData(&pwz, &pwzName); ExitOnFailure(hr, "failed to process CustomActionData"); hr = WcaReadStringFromCaData(&pwz, &pwzValue); ExitOnFailure(hr, "failed to process CustomActionData"); cAdditionalChanges--; } continue; } 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, &pwzElementPath); ExitOnFailure(hr, "failed to process CustomActionData"); hr = WcaReadStringFromCaData(&pwz, &pwzVerifyPath); 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"); hr = WcaReadIntegerFromCaData(&pwz, &cAdditionalChanges); 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 || xaWriteDocument == xa) { MessageExitOnFailure1(hr = hrOpenFailure, msierrXmlConfigFailedOpen, "failed to load XML file: %ls", pwzFile); } else { continue; } } // Select the node we're about to modify ReleaseNullObject(pixn); hr = XmlSelectSingleNode(pixd, pwzElementPath, &pixn); // If we failed to find the node that we are going to add to, we've got a problem. Otherwise, just continue since the node's already gone. if (S_FALSE == hr) { if (xaCreateElement == xa || xaWriteValue == xa || xaWriteDocument == xa) { hr = HRESULT_FROM_WIN32(ERROR_OBJECT_NOT_FOUND); } else { hr = S_OK; continue; } } MessageExitOnFailure2(hr, msierrXmlConfigFailedSelect, "failed to find node: %ls in XML file: %ls", pwzElementPath, pwzFile); // Make the modification switch (xa) { case xaWriteValue: if (pwzName && *pwzName) { // We're setting an attribute hr = XmlSetAttribute(pixn, pwzName, pwzValue); ExitOnFailure2(hr, "failed to set attribute: %ls to value %ls", pwzName, pwzValue); } else { // We're setting the text of the node hr = XmlSetText(pixn, pwzValue); ExitOnFailure2(hr, "failed to set text to: %ls for element %ls. Make sure that XPath points to an element.", pwzValue, pwzElementPath); } break; case xaWriteDocument: if (NULL != pwzVerifyPath && 0 != pwzVerifyPath[0]) { hr = XmlSelectSingleNode(pixn, pwzVerifyPath, &pixnVerify); if (S_OK == hr) { // We found the verify path which means we have no further work to do continue; } ExitOnFailure1(hr, "failed to query verify path: %ls", pwzVerifyPath); } hr = XmlLoadDocumentEx(pwzValue, XML_LOAD_PRESERVE_WHITESPACE, &pixdNew); ExitOnFailure(hr, "Failed to load value as document."); hr = pixdNew->get_documentElement(&pixeNew); ExitOnFailure(hr, "Failed to get document element."); hr = pixn->appendChild(pixeNew, NULL); ExitOnFailure(hr, "Failed to append document element on to parent element."); ReleaseNullObject(pixeNew); ReleaseNullObject(pixdNew); break; case xaCreateElement: if (NULL != pwzVerifyPath && 0 != pwzVerifyPath[0]) { hr = XmlSelectSingleNode(pixn, pwzVerifyPath, &pixnVerify); if (S_OK == hr) { // We found the verify path which means we have no further work to do continue; } ExitOnFailure1(hr, "failed to query verify path: %ls", pwzVerifyPath); } hr = XmlCreateChild(pixn, pwzName, &pixnNewNode); ExitOnFailure1(hr, "failed to create child element: %ls", pwzName); if (pwzValue && *pwzValue) { hr = XmlSetText(pixnNewNode, pwzValue); ExitOnFailure2(hr, "failed to set text to: %ls for node: %ls", pwzValue, pwzName); } while (cAdditionalChanges > 0) { hr = WcaReadStringFromCaData(&pwz, &pwzName); ExitOnFailure(hr, "failed to process CustomActionData"); hr = WcaReadStringFromCaData(&pwz, &pwzValue); ExitOnFailure(hr, "failed to process CustomActionData"); // Set the additional attribute hr = XmlSetAttribute(pixnNewNode, pwzName, pwzValue); ExitOnFailure2(hr, "failed to set attribute: %ls to value %ls", pwzName, pwzValue); cAdditionalChanges--; } ReleaseNullObject(pixnNewNode); break; case xaDeleteValue: if (pwzName && *pwzName) { // Delete the attribute hr = XmlRemoveAttribute(pixn, pwzName); ExitOnFailure1(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"); } break; case xaDeleteElement: if (NULL != pwzVerifyPath && 0 != pwzVerifyPath[0]) { hr = XmlSelectSingleNode(pixn, pwzVerifyPath, &pixnVerify); if (S_OK == hr) { hr = pixn->removeChild(pixnVerify, &pixnRemovedChild); ExitOnFailure(hr, "failed to remove created child element"); ReleaseNullObject(pixnRemovedChild); } else { WcaLog(LOGMSG_VERBOSE, "Failed to select path %ls for deleting. Skipping...", pwzVerifyPath); hr = S_OK; } } else { // TODO: This requires a VerifyPath to delete an element. Should we support not having one? WcaLog(LOGMSG_VERBOSE, "No VerifyPath specified for delete element of ID: %ls", pwzElementPath); } break; default: ExitOnFailure(hr = E_UNEXPECTED, "Invalid modification specified in custom action data"); break; } } // 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); ExitOnFailure1(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: ExitOnFailure1(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 { ExitOnFailure1(hr, "Failed to save changes to XML file: %ls", pwzFile); } } break; default: // Unknown error ExitOnFailure1(hr, "Failed to save changes to XML file: %ls", pwzFile); } } } while (S_FALSE == hr); if (fPreserveDate) { hr = FileSetTime(pwzFile, NULL, NULL, &ft); ExitOnFailure1(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(); } WcaFinalizeWow64(); ReleaseStr(pwzCustomActionData); ReleaseStr(pwzData); ReleaseStr(pwzFile); ReleaseStr(pwzElementPath); ReleaseStr(pwzVerifyPath); ReleaseStr(pwzName); ReleaseStr(pwzValue); ReleaseObject(pixeNew); ReleaseObject(pixdNew); ReleaseObject(pixn); ReleaseObject(pixd); ReleaseObject(pixnNewNode); ReleaseObject(pixnRemovedChild); XmlUninitialize(); if (FAILED(hr)) { er = ERROR_INSTALL_FAILURE; } return WcaFinalize(er); }