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