Platform SDK: Active Directory, ADSI, and Directory Services

Detecting the Schema Master

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:

  1. Read the fsmoRoleOwner property of the schema container on any DC. The fsmoRoleOwner attribute returns the distinguished name (DN) of the nTDSDSA object for the schema master.
  2. Bind to the nTDSDSA object whose DN you just retrieved. The parent of this object is the server object for the DC containing the schema master.
  3. Get the ADsPath for the parent of the nTDSDSA object. The parent is a server object.
  4. Bind to the server object.
  5. Get the dNSHostName property of the server object. This is the DNS name of the DC containing the schema master.
  6. To bind to the schema master, specify the DNS name of the schema master as the server and the DN as the DN of the schema container. For example, if the server was dcnumberone.fabrikam.com and the schema container DN was cn=schema,cn=configuration,dc=fabrikam,dc=com, the ADsPath would be the following:
    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.

C/C++

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;
}
VBScript

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