| Platform SDK: Active Directory, ADSI, and Directory Services |
Active Directory is a multi-master update system; every domain controller holds a writeable copy of the directory. Schema updates are propagated to the entire enterprise (that is, all domains that belong to the same tree or forest). Because it is difficult to reconcile conflicting updates to the schema, schema updates can only be performed at a single server. The server with the right to perform updates can change, but only one server will have that right at any given time. This server is called the Schema Master. The first DC installed in an enterprise is the Schema Master by default.
Schema changes can only be made at the schema master. You can detect which DC is the schema master by following these steps:
LDAP://dcnumberone.fabrikam.com/cn=schema,cn=configuration,dc=fabrikam,dc=com
It is highly recommended that you find the schema master and bind to it in order to make schema changes. However, you can move the schema master to another server.
To make another server the Schema Master, a suitably privileged user can:
To become the schema master programmatically, a program running in the context of a suitably privileged user issues an LDAP write of the operational attribute becomeSchemaMaster to the rootDSE on that DC. This initiates an atomic transfer of the schema master right from the current holder to the local DC.
The following example shows how to find the schema master.
The following function binds to the schema container on the computer that is the schema master:
HRESULT BindToSchemaMaster(IADsContainer **ppSchemaMaster)
{
HRESULT hr = E_FAIL;
//Get rootDSE and the schema container's DN.
IADs *pObject = NULL;
IADs *pTempSchema = NULL;
IADs *pNTDS = NULL;
IADs *pServer = NULL;
BSTR bstrParent;
LPOLESTR szPath = new OLECHAR[MAX_PATH];
VARIANT var, varRole,varComputer;
hr = ADsOpenObject(L"LDAP://rootDSE",
NULL,
NULL,
ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
IID_IADs,
(void**)&pObject);
if (SUCCEEDED(hr))
{
hr = pObject->Get(L"schemaNamingContext",&var);
if (SUCCEEDED(hr))
{
wcscpy(szPath,L"LDAP://");
wcscat(szPath,var.bstrVal);
hr = ADsOpenObject(szPath,
NULL,
NULL,
ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
IID_IADs,
(void**)&pTempSchema);
if (SUCCEEDED(hr))
{
//Read the fsmoRoleOwner attribute to see which server is the schema master.
hr = pTempSchema->Get(L"fsmoRoleOwner",&varRole);
if (SUCCEEDED(hr))
{
//fsmoRoleOwner attribute returns the nTDSDSA object.
//The parent is the server object.
//Bind to NTDSDSA object and get parent
wcscpy(szPath,L"LDAP://");
wcscat(szPath,varRole.bstrVal);
hr = ADsOpenObject(szPath,
NULL,
NULL,
ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
IID_IADs,
(void**)&pNTDS);
if (SUCCEEDED(hr))
{
hr = pNTDS->get_Parent(&bstrParent);
if (SUCCEEDED(hr))
{
//Bind to server object
//and get the DNS name of the server.
wcscpy(szPath,bstrParent);
hr = ADsOpenObject(szPath,
NULL,
NULL,
ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
IID_IADs,
(void**)&pServer);
if (SUCCEEDED(hr))
{
//Get the dns name of the server.
hr = pServer->Get(L"dNSHostName",&varComputer);
if (SUCCEEDED(hr))
{
wcscpy(szPath,L"LDAP://");
wcscat(szPath,varComputer.bstrVal);
wcscat(szPath,L"/");
wcscat(szPath,var.bstrVal);
hr = ADsOpenObject(szPath,
NULL,
NULL,
ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
IID_IADs,
(void**)ppSchemaMaster);
if (FAILED(hr))
{
if (*ppSchemaMaster)
{
(*ppSchemaMaster)->Release();
(*ppSchemaMaster) = NULL;
}
}
}
VariantClear(&varComputer);
}
if (pServer)
pServer->Release();
}
SysFreeString(bstrParent);
}
if (pNTDS)
pNTDS->Release();
}
VariantClear(&varRole);
}
if (pTempSchema)
pTempSchema->Release();
}
VariantClear(&var);
}
if (pObject)
pObject->Release();
return hr;
}
The following script displays the DNS name for the computer that is the schema master:
On Error Resume Next
''''''''''''''''''''''''''''''''''''''
'Bind to the rootDSE
'''''''''''''''''''''''''''''''''''''''
sPrefix = "LDAP://"
Set root= GetObject(sPrefix & "rootDSE")
If (Err.Number <> 0) Then
BailOnFailure Err.Number, "on GetObject method"
End If
''''''''''''''''''''''''''''''''''''''
'Get the DN for the Schema
'''''''''''''''''''''''''''''''''''''''
sSchema = root.Get("schemaNamingContext")
If (Err.Number <> 0) Then
BailOnFailure Err.Number, "on Get method"
End If
''''''''''''''''''''''''''''''''''''''
'Bind to the Schema container
'''''''''''''''''''''''''''''''''''''''
Set Schema= GetObject(sPrefix & sSchema )
If (Err.Number <> 0) Then
BailOnFailure Err.Number, "on GetObject method to bind to Schema"
End If
'''''''''''''''''''''''''''''''''''''''
'Read the fsmoRoleOwner attribute to see which server is the schema master.
'''''''''''''''''''''''''''''''''''''''
sMaster = Schema.Get("fsmoRoleOwner")
If (Err.Number <> 0) Then
BailOnFailure Err.Number, "on IADs::Get method for fsmoRoleOwner"
End If
'''''''''''''''''''''''''''''''''''''''
'fsmoRoleOwner attribute returns the nTDSDSA object.
'The parent is the server object.
'Bind to NTDSDSA object and get parent
'''''''''''''''''''''''''''''''''''''''
Set NTDS = GetObject(sPrefix & sMaster)
If (Err.Number <> 0) Then
BailOnFailure Err.Number, "on GetObject method for NTDS"
End If
sServer = NTDS.Parent
If (Err.Number <> 0) Then
BailOnFailure Err.Number, "on IADs::get_Parent method"
End If
'''''''''''''''''''''''''''''''''''''''
'Bind to server object
'and get the reference to the computer object.
'''''''''''''''''''''''''''''''''''''''
Set Server = GetObject(sServer)
If (Err.Number <> 0) Then
BailOnFailure Err.Number, "on GetObject method for " & sServer
End If
sComputer = Server.Get("dNSHostName")
'''''''''''''''''''''''''''''''''''''''
'Display the DNS name for the computer.
'''''''''''''''''''''''''''''''''''''''
strText = "Schema Master has the following DNS name: "& sComputer
WScript.echo strText
'''''''''''''''''''''''''''''''''''''''
'Display subroutines
'''''''''''''''''''''''''''''''''''''''
Sub BailOnFailure(ErrNum, ErrText) strText = "Error 0x" & Hex(ErrNum) & " " & ErrText
MsgBox strText, vbInformation, "ADSI Error"
WScript.Quit
End Sub