// Active Directory Display Specifier Upgrade Tool // // Copyright (c) 2001 Microsoft Corporation // // class Analyst: analyzes the display specifiers, logs the findings, and // compiles a set of corrective actions. // // 9 Mar 2001 sburns #include "headers.hxx" #include "resource.h" #include "AdsiHelpers.hpp" #include "Analyst.hpp" #include "Amanuensis.hpp" #include "Repairer.hpp" #include "ChangedObjectHandlerList.hpp" #include "ChangedObjectHandler.hpp" Analyst::Analyst( const String& targetDomainControllerName, Amanuensis& amanuensis_, Repairer& repairer_) : targetDcName(targetDomainControllerName), ldapPrefix(), rootDse(0), // alias the objects amanuensis(amanuensis_), repairer(repairer_) { LOG_CTOR(Analyst); ASSERT(!targetDcName.empty()); } // basic idea: if the error is critical and analysis should not continue, set // hr to a failure value, and break out, propagating the error backward. If // the error is non-critical and analysis should continue, log the error, skip // the current operation, and set hr to S_FALSE. HRESULT AssessErrorSeverity(HRESULT hrIn) { HRESULT hr = hrIn; if (SUCCEEDED(hr)) { return hr; } switch (hr) { case 0: { } // CODEWORK: we need to define what errors are critical... default: { // do nothing break; } } return hr; } HRESULT Analyst::AnalyzeDisplaySpecifiers() { LOG_FUNCTION(Analyst::AnalyzeDisplaySpecifiers); HRESULT hr = S_OK; do { Computer targetDc(targetDcName); hr = targetDc.Refresh(); if (FAILED(hr)) { amanuensis.AddErrorEntry( hr, String::format( IDS_CANT_TARGET_MACHINE, targetDcName.c_str())); break; } if (!targetDc.IsDomainController()) { amanuensis.AddEntry( String::format( IDS_TARGET_IS_NOT_DC, targetDcName.c_str())); break; } String dcName = targetDc.GetActivePhysicalFullDnsName(); ldapPrefix = L"LDAP://" + dcName + L"/"; // // Find the DN of the configuration container. // // Bind to the rootDSE object. We will keep this binding handle // open for the duration of the analysis and repair phases in order // to keep a server session open. If we decide to pass creds to the // AdsiOpenObject call in a later revision, then by keeping the // session open we will not need to pass the password to subsequent // AdsiOpenObject calls. hr = AdsiOpenObject(ldapPrefix + L"RootDSE", rootDse); if (FAILED(hr)) { amanuensis.AddErrorEntry( hr, String::format( IDS_UNABLE_TO_CONNECT_TO_DC, dcName.c_str())); break; } // read the configuration naming context. _variant_t variant; hr = rootDse->Get( AutoBstr(LDAP_OPATT_CONFIG_NAMING_CONTEXT_W), &variant); if (FAILED(hr)) { LOG(L"can't read config NC"); amanuensis.AddErrorEntry( hr, IDS_UNABLE_TO_READ_DIRECTORY_INFO); break; } String configNc = V_BSTR(&variant); LOG(configNc); ASSERT(!configNc.empty()); // // Here we go... // hr = AnalyzeDisplaySpecifierContainers(configNc); BREAK_ON_FAILED_HRESULT(hr); } while (0); LOG_HRESULT(hr); return hr; } HRESULT Analyst::AnalyzeDisplaySpecifierContainers(const String& configurationDn) { LOG_FUNCTION2(Analyst::AnalyzeDisplaySpecifierContainers, configurationDn); ASSERT(!configurationDn.empty()); HRESULT hr = S_OK; static const int LOCALEIDS[] = { // a list of all the non-english locale IDs that we support 0x401, 0x404, 0x405, 0x406, 0x407, 0x408, 0x40b, 0x40c, 0x40d, 0x40e, 0x410, 0x411, 0x412, 0x413, 0x414, 0x415, 0x416, 0x419, 0x41d, 0x41f, 0x804, 0x816, 0xc0a, 0 }; // compose the LDAP path of the display specifiers container String rootContainerDn = L"CN=DisplaySpecifiers," + configurationDn; for ( int i = 0; (i < sizeof(LOCALEIDS) / sizeof(int)) && LOCALEIDS[i]; ++i) { hr = AnalyzeDisplaySpecifierContainer(LOCALEIDS[i], rootContainerDn); BREAK_ON_FAILED_HRESULT(hr); } LOG_HRESULT(hr); return hr; } HRESULT Analyst::AnalyzeDisplaySpecifierContainer( int localeId, const String& rootContainerDn) { LOG_FUNCTION2( Analyst::AnalyzeDisplaySpecifierContainer, rootContainerDn); ASSERT(!rootContainerDn.empty()); ASSERT(localeId); HRESULT hr = S_OK; do { String childContainerDn = ldapPrefix + String::format(L"CN=%1!3x!,", localeId) + rootContainerDn; // Attempt to bind to the container. SmartInterface iads(0); hr = AdsiOpenObject(childContainerDn, iads); if (hr == E_ADS_UNKNOWN_OBJECT) { // The container object does not exist. This is possible because // the user has manually removed the container, or because it // was never created due to an aboted post-dcpromo import of the // display specifiers when the forest root dc was first promoted. repairer.AddCreateContainerWorkItem(localeId); hr = S_OK; break; } BREAK_ON_FAILED_HRESULT(hr); // At this point, the bind succeeded, so the child container exists. // So now we want to examine objects in that container. hr = AnalyzeDisplaySpecifierObjects( localeId, childContainerDn); } while (0); LOG_HRESULT(hr); hr = AssessErrorSeverity(hr); return hr; } HRESULT Analyst::AnalyzeDisplaySpecifierObjects( int localeId, const String& containerDn) { LOG_FUNCTION2(Analyst::AnalyzeDisplaySpecifierObjects, containerDn); ASSERT(localeId); ASSERT(!containerDn.empty()); HRESULT hr = S_OK; do { // Part 1: deal with new objects added in Whistler hr = AnalyzeAddedObjects(localeId, containerDn); hr = AssessErrorSeverity(hr); BREAK_ON_FAILED_HRESULT(hr); // Part 2: deal with objects that have changed from Win2k to Whistler hr = AnalyzeChangedObjects(localeId, containerDn); hr = AssessErrorSeverity(hr); BREAK_ON_FAILED_HRESULT(hr); // Part 3: deal with objects that have been deleted in whistler // This part is easy: there are no deletions. } while (0); LOG_HRESULT(hr); return hr; } bool RepairWasRunPreviously() { LOG_FUNCTION(RepairWasRunPreviously); bool result = false; // CODEWORK: need to complete LOG_BOOL(result); return result; } HRESULT Analyst::AnalyzeAddedObjects( int localeId, const String& containerDn) { LOG_FUNCTION2(Analyst::AnalyzeAddedObjects, containerDn); ASSERT(localeId); ASSERT(!containerDn.empty()); HRESULT hr = S_OK; do { static const String ADDED_OBJECTS[] = { L"msMQ-Custom-Recipient-Display", L"msMQ-Group-Display", L"msCOM-PartitionSet-Display", L"msCOM-Partition-Display", L"lostAndFound-Display", L"inetOrgPerson-Display", L"", }; for ( int i = 0; i < (sizeof(ADDED_OBJECTS) / sizeof(String)) && !ADDED_OBJECTS[i].empty(); ++i) { String objectName = ADDED_OBJECTS[i]; String objectPath = ldapPrefix + L"CN=" + objectName + L"," + containerDn; SmartInterface iads(0); hr = AdsiOpenObject(objectPath, iads); if (hr == E_ADS_UNKNOWN_OBJECT) { // The object does not exist. This is what we expect. We want // to add the object in the repair phase. repairer.AddCreateObjectWorkItem(localeId, objectName); hr = S_OK; continue; } else if (SUCCEEDED(hr)) { // The object already exists. Well, that's not expected, unless // we've already run the tool. if (!RepairWasRunPreviously()) { // we didn't create the object. If the user did, they did // it manually, and we don't support that. // cause the existing object to be deleted repairer.AddDeleteObjectWorkItem(localeId, objectName); // cause a new, replacement object to be created. repairer.AddCreateObjectWorkItem(localeId, objectName); hr = S_OK; continue; } } else { ASSERT(FAILED(hr)); LOG(L"Unexpected error attempting to bind to " + objectName); amanuensis.AddErrorEntry( hr, String::format( IDS_ERROR_BINDING_TO_OBJECT, objectName.c_str(), objectPath.c_str())); // move on to the next object hr = S_FALSE; continue; } } BREAK_ON_FAILED_HRESULT(hr); } while (0); LOG_HRESULT(hr); return hr; } HRESULT Analyst::AnalyzeChangedObjects( int localeId, const String& containerDn) { LOG_FUNCTION2(Analyst::AnalyzeChangedObjects, containerDn); ASSERT(localeId); ASSERT(!containerDn.empty()); HRESULT hr = S_OK; static const ChangedObjectHandlerList handlers; for ( ChangedObjectHandlerList::iterator i = handlers.begin(); i != handlers.end(); ++i) { hr = AnalyzeChangedObject(localeId, containerDn, **i); hr = AssessErrorSeverity(hr); BREAK_ON_FAILED_HRESULT(hr); } LOG_HRESULT(hr); return hr; } HRESULT Analyst::AnalyzeChangedObject( int localeId, const String& containerDn, const ChangedObjectHandler& changeHandler) { LOG_FUNCTION2(Analyst::AnalyzeChangedObject, changeHandler.GetObjectName()); ASSERT(localeId); ASSERT(!containerDn.empty()); HRESULT hr = S_OK; do { String objectName = changeHandler.GetObjectName(); String objectPath = ldapPrefix + L"CN=" + objectName + L"," + containerDn; SmartInterface iads(0); hr = AdsiOpenObject(objectPath, iads); if (hr == E_ADS_UNKNOWN_OBJECT) { // The object does not exist. This is possible because the user has // manually removed the container, or because it was never created // due to an aboted post-dcpromo import of the display specifiers // when the forest root dc was first promoted. // Add a work item to create the missing object repairer.AddCreateObjectWorkItem(localeId, objectName); hr = S_OK; break; } if (FAILED(hr)) { // any other error is quittin' time. break; } // At this point, the display specifier object exists. Determine if // if has been touched since its creation. // Compare usnCreated to usnChanged _variant_t variant; hr = iads->Get(AutoBstr(L"usnCreated"), &variant); if (FAILED(hr)) { LOG(L"Error reading usnCreated"); break; } // CODEWORK: need to complete this hr = changeHandler.HandleChange( localeId, containerDn, iads, amanuensis, repairer); } while (0); LOG_HRESULT(hr); return hr; }