检测架构主机
Active Directory 域服务具有一个多主机更新系统;每个域控制器保留目录的可写副本。 架构更新传播到同一树或林中的所有域。 有冲突的架构更新很难协调,因此只能对单个服务器执行架构更新。 有权执行更新的服务器可能会发生更改,但在任何给定的时间,只有一台服务器具有该权限。 此服务器称为架构主机。 默认情况下,企业中安装的第一个 DC 便是架构主机。
架构更改只能在架构主机级别进行。 若要检测哪个 DC 是架构主机,请执行以下步骤。
检测 DC 架构主机
- 读取任何 DC 上架构容器的 fsmoRoleOwner 属性。 fsmoRoleOwner 属性返回架构主机的 nTDSDSA 对象的可分辨名称 (DN)。
- 绑定到刚刚检索到其 DN 的 nTDSDSA 对象。 此对象的父级是包含架构主机的 DC 的服务器对象。
- 获取 nTDSDSA 对象父级的 ADsPath。 父级是服务器对象。
- 绑定到服务器对象。
- 获取服务器对象的 dNSHostName 属性。 这是包含架构主机的 DC 的 DNS 名称。
- 将架构主机的 DNS 名称指定为服务器,将 DN 指定为要绑定到架构主机的架构容器的 DN。 例如,如果服务器为“dc1.fabrikam.com”,架构容器 DN 为“cn=schema,cn=configuration,dc=fabrikam,dc=com”,ADsPath 将如下所示。
LDAP://dc1.fabrikam.com/cn=schema,cn=configuration,dc=fabrikam,dc=com
建议找到架构主机并绑定到该主机以进行架构更改。 但是,可以将架构主机移动到另一台服务器。
若要使另一台服务器成为架构主机,适当的特权用户可以:
使用架构管理器 MMC 管理单元。
注意
必须手动注册 Active Directory 架构 MMC 管理单元。 若要注册架构管理单元,必须从 Windows System32 目录中的命令提示符运行以下命令。
regsvr32.exe schmmgmt.dll
使用 NTDSUTIL 命令行实用工具。
使用第三方应用程序(发出相应 LDAP 写入的应用程序)。
若要以编程方式成为架构主机,在适当特权用户的上下文中运行的应用程序可以向该 DC 上的 rootDSE 发出操作属性 becomeSchemaMaster 的 LDAP 写入。 这会启动架构主机从当前持有人到本地 DC 的原子传输。
下面的代码示例查找架构主机。 以下函数绑定到作为架构主机的计算机上的架构容器。
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_s(szPath,L"LDAP://");
wcscat_s(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_s(szPath,L"LDAP://");
wcscat_s(szPath,varRole.bstrVal);
hr = ADsOpenObject(szPath,
NULL,
NULL,
ADS_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_s(szPath,bstrParent);
hr = ADsOpenObject(szPath,
NULL,
NULL,
ADS_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_s(szPath,L"LDAP://");
wcscat_s(szPath,varComputer.bstrVal);
wcscat_s(szPath,L"/");
wcscat_s(szPath,var.bstrVal);
hr = ADsOpenObject(szPath,
NULL,
NULL,
ADS_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;
}
下面的代码示例显示作为架构主机的计算机的 DNS 名称。
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 the parent object.
''''''''''''''''''''
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