Directory Services |
Active Directory is a multi-master update system; every domain controller holds a writable copy of the directory. Schema updates are propagated to 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, by default, the schema master.
Detecting the DC Schema Master
Schema changes can only be made at the schema master level. To detect which DC is the schema master.
LDAP://dcnumberone.fabrikam.com/cn=schema,cn=configuration,dc=fabrikam,dc=com
It is recommended that you find the schema master and bind to it 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, an application running in the context of a suitably privileged user can issue 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 code 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 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 (hr == S_OK) { hr = pObject->Get(CComBSTR("schemaNamingContext"), &var); if (hr == S_OK) { wcscpy(szPath,L"LDAP://"); wcscat(szPath,var.bstrVal); hr = ADsOpenObject(szPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, // Use Secure Authentication. IID_IADs, (void**)&pTempSchema); if (hr == S_OK) { // Read the fsmoRoleOwner attribute to identify which server is the schema master. hr = pTempSchema->Get(CComBSTR("fsmoRoleOwner"), &varRole); if (hr == S_OK) { // The 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 (hr == S_OK) { hr = pNTDS->get_Parent(&bstrParent); if (hr == S_OK) { // 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 (hr == S_OK) { // Get the dns name of the server. hr = pServer->Get(CComBSTR("dNSHostName"), &varComputer); if (hr == S_OK) { 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 code example 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 ''''''''''''''''''''''''''''''''''''''' ' The 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