548 lines
12 KiB
C++
548 lines
12 KiB
C++
// 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<IADs>(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> iads(0);
|
|
hr = AdsiOpenObject<IADs>(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> iads(0);
|
|
hr = AdsiOpenObject<IADs>(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> iads(0);
|
|
hr = AdsiOpenObject<IADs>(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;
|
|
}
|