Active Directory编程 active directory域
本节将介绍:
● System.DirectoryServices命名空间中的类
● 连接Active Directory的处理方式:绑定
● 获取目录项,创建新对象,并更新已有的项目
● 搜索Active Directory
24.4.1 System.DirectoryServices命名空间中的类
表24-1列出了System.DirectoryServices命名空间中的主要类。
表 24-1
类
说 明
DirectoryEntry
这个类是System.DirectoryServices命名空间中的主类。这个类的对象表示Active Directory库中的一个对象。使用这个类可以绑定对象,查看和更新属性。对象的属性都放在PropertyCollection中。PropertyCollection中的每个元素都有一个PropertyValueCollection
DirectoryEntries
DirectoryEntries是DirectoryEntry对象的一个集合。DirectoryEntry对象的Children属性返回DirectoryEntries集合中的一个对象列表
DirectorySearcher
这个类主要用于用指定的属性搜索对象。要定义该搜索,可以使用SortOption类和枚举SearchScope、SortDirection 和 ReferalChasingOption。搜索的结果是一个SearchResult或SearchResultCollection。也可以得到ResultPropertyCollection 和 ResultPropertyValueCollection对象
24.4.2 绑定
要获得Active Directory中一个对象的值,必须连接Active Directory服务。这个连接过程称为绑定,绑定路径如下所示。
LDAP://dc01.athenaproject.com/OU=Development, DC= AthenaProject, DC=Com
在绑定过程中,可以指定下述内容:
● 指定提供程序使用的协议(protocol)。
● 域控制器的服务器名(server name)。
● 服务器过程的端口号(port number)。
● 对象的显名(distingunshed name),以标识要访问的对象。
● 如果需要访问Active Directory的用户不是运行当前进程的账户,则提供用户名和密码。
● 如果需要加密,应指定authentication类型。
下面详细介绍这些内容。
1. 协议
绑定路径的第一部分指定ADSI提供程序。该提供程序是一个COM服务器;prog-id的标识信息在注册表的HKEY_CLASSES_ROOT下。Windows XP附带的提供程序如表24-2所示。
2. 服务器名
在绑定路径中,服务器名在协议的后面。如果用户登录到Active Directory域上,服务器名就是可选的。如果不提供服务器名,就会发生无服务器绑定操作,此时Windows Server 2003会在域中查找与用户绑定过程相关的、“最好的”域控制器。如果站点中没有服务器,就使用查找到的第一个域控制器。
无服务器的绑定如下所示:
LDAP://OU=Sales,DC=AthenaProject,DC=Local
表 24-2
协 议
说 明
LDAP
LDAP服务器,例如Exchange目录和Windows 2000 Server或Windows Server 2003 Active Directory服务器
GC
GC用于访问Active Directory中的全局目录。它也可以用于快速查询
IIS
使用IIS的ADSI提供程序,可以在IIS目录中创建和管理新网站
WinNT
要访问旧Windows NT 4域的用户数据库,可以使用WinNT 的ADSI提供程序。NT4用户只有几个属性没有改变。也可以使用这个协议绑定Windows 2000域,但这里也限制了可用于NT4的属性
NDS
这个progID用于和Novell Directory Services通信
NWCOMPAT
使用NWCOMPAT可以访问旧的Novell目录,例如Novell Netware 3.x
3. 端口号
在服务器名的后面,可以指定服务器过程的端口号,其语法是:xxx。LDAP服务器的默认端口号是端口389: LDAP://dc01.globalknowledge.net:389。Exchange服务器使用的端口号与LDAP服务器一样。如果在同一个系统上安装了Exchange服务器,例如用作Active Directory的域控制器,就可以配置另一个端口。
4. 显名
在路径中指定的第四部分是显名(distinguished name,DN)。显名是一个惟一的名称,标识要访问的对象。在Active Directory中,可以使用基于X.500的LDAP语法,指定对象的名称。
例如有一个显名:
CN=Christian Nagel, OU=Consultants, DC= AthenaProject, DC=local
这个显名指定域AthenaProject.local中域组件(Domain Component,DC) AthenaProject的组织单元(Organizational Unit,OU) Consultants的公共名称(Common Name,CN) Christian Nagel。最右边的部分是域的根对象。该名称必须符合对象树中的分层方式。
显名的字符串表示的LDAP规范在 RFC 2253( http://www.ietf.org/rfc/rfc2253.txt)上。
(1) 相对显名
相对显名(RDN)用于引用容器对象中的对象。使用RDN时,不需要指定OU和DC,有一个公共名称就足够了。CN=Christian Nagel就是组织单元中的一个相对显名。如果已经引用了一个容器对象,要访问其子对象,就可以使用相对显名。
(2) 默认的命名环境
如果在路径中没有指定显名,绑定过程就会使用默认的命名环境(default naming context)。使用rootDSE可以读取默认命名环境。LDAP 3.0把rootDSE定义为目录服务器中目录树的根。例如
LDAP://rootDSE
或:
LDAP://servername/rootDSE
通过列举rootDSE的所有属性,将获得没有指定名称时可以使用的defaultNamingContext信息。schemaNamingContext 和 configurationNamingContext指定了用于访问模式所需要的名称和Active Directory库中的配置。
通过下面的代码可获得rootDSE的所有属性:
using (DirectoryEntry de = new DirectoryEntry())
{
de.Path = "LDAP://platinum/rootDSE";
de.Username = @" platinumchristian";
de.Password = "password";
PropertyCollection props = de.Properties;
foreach (string prop in props.PropertyNames)
{
PropertyValueCollection values = props[prop];
foreach (string val in values)
{
Console.Write(prop + ": ");
Console.WriteLine(val);
}
}
}
这个程序显示了默认的命名环境(defaultNamingContext DC=eichkogelstrasse、 DC=local),用于访问模式的环境(CN=Schema、 CN=Configuration、 DC=eichkogelstrasse、 DC=local)和配置的命名环境(CN=Configuration、 DC=eichkogelstrasse、 DC=local),如图24-9所示。
图 24-9
(3) 对象标识符
每个对象都有一个全局惟一的标识符GUID。GUID是一个惟一的128位数字,您可能已经在COM开发中了解了它。可以使用GUID绑定一个对象。这样,即使对象移动到另一个容器中,也可以得到该对象。GUID在创建对象时生成,且总是保持不变。
使用DirectoryEntry.NativeGuid可以得到GUID的字符串表示。这个字符串表示就可以用于绑定对象。
下面的示例显示了一个无服务器绑定的路径名称,它绑定到GUID代表的一个特定对象上:
LDAP://<GUID=14abbd652aae1a47abc60782dcfc78ea>
(4) Windows NT域中的对象名
WinNT提供程序不允许在绑定字符串的名称部分使用LDAP语法。在这个提供程序中,对象用ObjectName、ClassName指定。Windows NT域的有效绑定字符串如下:
WinNT:
WinNT://DomainName
WinNT://DomainName/UserName, user
WinNT://DomainName/ServerName/MyGroup, group
user和group后缀指定可以访问类型为user或group的对象。
5. 用户名
有时,在访问目录时,必须使用一个非当前进程的用户名(也许这个用户没有访问Active Directory所必须的许可),此时必须为绑定过程显式指定用户证书(user credential)。Active Directory提供了许多方式来设置用户名。
(1) Downlevel登录
使用downlevel登录,用户名可以用Windows 2000以前的域名来指定:
domainusername
(2) 显名
也可以用user对象的显名来指定用户,例如:
CN=Administrator, CN=Users,DC=athenaproject,DC=local
(3) User Principal Name (UPN)
对象的UPN用userPrincipalName属性来定义。系统管理员可以在Active Directory Users and Computers工具中User属性的Account选项卡上,用登录信息来指定UPN,注意这不是用户的电子邮件地址。
这些信息也惟一地标识了用户,可以用于登录:
Nagel@ athenaproject.local
6. 身份验证
为了给身份验证进行安全的加密,也可以指定身份验证(authentication)类型。身份验证可以用DirectoryEntry类的AuthenticationType属性设置。可以指定其值为AuthenticationTypes枚举中的一个值。因为枚举是用属性[Flags]标识的,所以可以指定多个值。其可能的值有:对发送的数据进行加密的ReadonlyServer,它指定只需要读取访问,Secure表示安全的身份验证。
7. 用DirectoryEntry类绑定
System.DirectoryServices.DirectoryEntry类可以用于指定所有的绑定信息。可以使用默认的构造函数,用Path、Username、Password和 AuthenticationType属性定义绑定信息,或者把这些信息传递给构造函数:
DirectoryEntry de = new DirectoryEntry();
de.Path = "LDAP://platinum/DC=athenaproject, DC=local";
de.Username = "nagel@ athenaproject.local";
de.Password = "password";
// use the current user credentials
DirectoryEntry de2 = new DirectoryEntry(
"LDAP://DC= athenaproject, DC=local");
即使成功地构造了DirectoryEntry对象,也并不意味着绑定成功了。在第一次读取属性时进行绑定,可以避免不必要的网络流通量。对象是否存在,或者指定的用户证书是否正确,都可以在第一次访问该对象时确定。
24.4.3 获取目录项
前面介绍了如何指定Active Directory中对象的绑定属性,下面要读取对象的属性。在下面的示例中要读取用户对象的属性。
DirectoryEntry类的一些属性可以提供对象的信息,即Name、Guid和 SchemaClassName属性。第一次访问DirectoryEntry对象的属性时,会执行绑定操作,并填充底层ADSI对象的缓存。后面将详细讨论这些。其他属性可以从缓存中读取,同一对象的数据不需要通过与服务器的通信来获得。
在本例中,用组织单元Wrox Press中的公共名称Christian Nagel访问一个用户对象:
using (DirectoryEntry de = new DirectoryEntry())
{
de.Path = "LDAP://platinum/CN=Christian Nagel, " +
"OU=Wrox Press, DC=athenaproject, DC=local";
Console.WriteLine("Name: " + de.Name);
Console.WriteLine("GUID: " + de.Guid);
Console.WriteLine("Type: " + de.SchemaClassName);
Console.WriteLine();
//...
}
Active Directory对象包含许多信息,这些信息取决于对象的类型。属性Properties将返回一个PropertyCollection。每个属性本身就是一个集合,因为一个属性可以有多个值,例如,user对象可以有多个电话号码。在本例中,用一个内部foreach循环查看这些值。从properties[name]返回的集合是一个object数组。属性值可以是字符串、数字或其他类型。使用ToString()方法就可以显示这些值:
Console.WriteLine("Properties: ");
PropertyCollection properties = de.Properties;
foreach (string name in properties.PropertyNames)
{
foreach (object o in properties[name])
{
Console.WriteLine(name + ": " + o.ToString());
}
}
在得到的结果中,包含了user对象Christian Nagel的所有属性,如图24-10所示。OtherTelephone是一个多值属性,它包含许多电话号码。一些属性值只显示System._ComObject对象的类型,例如,lastLogoff、lastLogon和nTSecurityDescriptor。要得到这些属性的值,必须直接使用System.DirectoryServices命名空间的类中的ADSI COM接口。
图 24-10
注意:
第28章介绍了如何使用COM对象和接口。
2. 直接通过名称访问属性
使用 DirectoryEntry.Properties可以访问所有属性。如果已知属性名,就可以直接访问其值:
foreach (string homePage in de.Properties["wWWHomePage"])
Console.WriteLine("Home page: " + homePage);
24.4.4 对象集合
对象在Active Directory中是分层存储的。容器对象包含子对象。使用DirectoryEntry类的Children属性可以枚举容器中的子对象。另一方面,使用Parent属性可以得到对象的容器。
用户对象没有子对象,所以下面的示例使用一个组织单元,如图24-11所示。非容器对象用Children属性返回一个空集合。下面在域athenaproject.local的组织单元Wrox Press中获得其所有的用户对象。Children属性返回一个DirectoryEntries集合,其中包含DirectoryEntry对象。迭代所有的DirectoryEntry对象,显示子对象的名称。
using (DirectoryEntry de = new DirectoryEntry())
{
de.Path = "LDAP://platinum/OU=Wrox Press, " +
"DC=athenaproject, DC=local";
Console.WriteLine("Children of " + de.Name);
foreach (DirectoryEntry obj in de.Children)
{
Console.WriteLine(obj.Name);
}
}
图 24-11
本例显示了组织单元中的所有对象:users、contacts、printers、shares和其他组织单元。如果只需要查看某些对象类型,可以使用DirectoryEntries类的SchemaFilter属性。SchemaFilter属性返回一个SchemaNameCollection。有了这个SchemaNameCollection,就可以使用Add()方法定义要查看的对象类型。在本例中,因为只需要查看user对象,所以把user添加到这个集合中:
using (DirectoryEntry de = new DirectoryEntry())
{
de.Path = "LDAP:// platinum /OU=Wrox Press, " +
"DC= athenaproject, DC=local";
Console.WriteLine("Children of " + de.Name);
de.Children.SchemaFilter.Add("user");
foreach (DirectoryEntry obj in de.Children)
{
Console.WriteLine(obj.Name);
}
}
结果,只显示组织单元中的user对象,如图24-12所示。
24.4.5 缓存
要减少网络流通量,ADSI使用缓存来存储对象属性。如前所述,在创建DirectoryEntry对象时,是不访问服务器的。只要从目录库中读取第一个属性,所有的属性都会写到缓存中,这样,在读取下一个属性时,就不需要往返于服务器了。
写入对象的改变,只会改变已缓存的对象。设置属性不会产生网络流通量。必须使用DirectoryEntry.CommitChanges()才能刷新缓存,把所有已改变的数据传送回服务器。要再次从目录库中获取新写入的数据,可以使用DirectoryEntry.RefreshCache()读取属性。当然,如果没有调用CommitChanges() 和RefreshCache()就修改了一些属性,所有的改变都会丢失,因为我们将再次使用RefreshCache()读取目录服务中的值。
把属性DirectoryEntry.UsePropertyCache设置为false,就可以关闭这个属性cache。但除非在进行调试,否则最好不要关闭它,因为这会与服务器间产生许多不必要的往返通信。
24.4.6 创建新对象
创建新Active Directory对象时,例如users、computers、printers和contacts等,可以使用DirectoryEntries类以编程的方式来完成创建工作。
要给目录添加新对象,首先必须绑定一个容器对象,例如组织单元,可以在其中插入一个新对象。不包含其他对象的对象是不能使用的。下面使用一个容器对象,其显名为CN=Users,DC= athenaproject,DC=local:
DirectoryEntry de = new DirectoryEntry();
de.Path = "LDAP://platinum/CN=Users, DC= athenaproject, DC=local";
使用DirectoryEntry的Children属性,可以得到一个DirectoryEntries对象:
DirectoryEntries users = de.Children;
使用DirectoryEntries,可以添加、删除和查找集合中的对象。下面创建一个新的user对象。使用Add()方法时,需要一个对象名和一个类型名。使用ADSI Edit很容易获得类型名:
DirectoryEntry user = users.Add("CN=John Doe", "user");
对象现在有了默认属性值。要指定特定的属性值,可以使用Properties属性的Add()方法添加属性。当然,所有的属性都必须存在于user对象的模式中。如果指定的属性不存在,就得到一个COMException“指定的目录服务属性或值不存在”:
user.Properties["company"].Add("Some Company");
user.Properties["department"].Add("Sales");
user.Properties["employeeID"].Add("4711");
user.Properties["samAccountName"].Add("JDoe");
user.Properties["userPrincipalName"].Add("JDoe@ athenaproject.local");
user.Properties["givenName"].Add("John");
user.Properties["sn"].Add("Doe");
user.Properties["userPassword"].Add("someSecret");
此时,为了把数据写入Active Directory,必须刷新缓存中的数据:
user.CommitChanges();
24.4.7 更新目录项
Active Directory服务中对象的更新和读取一样简单。可以在读取对象后修改它们的值。要删除一个属性的所有值,可以调用PropertyValueCollection.Clear()方法。使用Add(),可以把新值添加到属性中。Remove()和RemoveAt()可以从属性集合中删除指定的值。
要修改一个值,可以把这个值设置为指定的值。下面的代码将通过PropertyValueCollection的一个索引符把移动电话号码设置为一个新值。使用该索引符,只能改变已存在的值。因此,应使用DirectoryEntry.Properties.Contains()来确定属性是否可用。
using (DirectoryEntry de = new DirectoryEntry())
{
de.Path = "LDAP://platinum /CN=Christian Nagel, " +
"OU=Wrox Press, DC= athenaproject, DC=local";
if (de.Properties.Contains("mobile"))
{
de.Properties["mobile"][0] = "+43(664)3434343434";
}
else
{
de.Properties["mobile"].Add("+43(664)3434343434");
}
de.CommitChanges();
}
在本例的else部分,如果移动电话号码不存在新属性,就用方法PropertyValue Collection.Add()添加它。如果使用Add()方法和已有的属性,得到的结果将取决于属性的类型:单值属性或多值属性。使用Add()方法和已有的单值属性,会得到一个COMException:A constraint violation occurred。使用Add()方法和已有的多值属性则会成功地把另一个值添加到属性中。
User对象的属性mobile定义为单值属性,所以不能添加其他移动电话的号码。但是用户可以有多个移动电话的号码。对于多个移动电话的号码,可以使用属性otherMobile,它是一个多值属性,允许设置多个移动电话号码,所以可以调用Add()多次。多值属性有一个重要的检查:即检查其唯一性。如果把第二个电话号码添加到同一个user对象上,也会得到一个COMException:指定的目录服务属性或值已经存在。
提示:
在创建或更新新的目录对象后,应调用DirectoryEntry.CommitChanges()。否则只能更新缓存中的信息,改变的信息不会发送到目录服务上。
24.4.8 访问内部的ADSI对象
调用预定义的ADSI接口方法常常比搜索对象的属性名容易得多。一些ADSI对象还支持不能在DirectoryEntry类中直接使用的方法。例如IADsServiceOperations接口有启动和停止Windows服务的方法。Windows服务将在第32章讨论。
如本章前面所述,System.DirectoryServices命名空间的类使用底层的ADSI COM对象。DirectoryEntry支持直接使用Invoke()方法调用底层对象的方法。
Invoke()的第一个参数是应在ADSI对象中调用的方法名,第二个参数的params关键字允许把数量可变的其他参数传送给ADSI方法:
public object Invoke(string methodName, params object[] args);
在ADSI文档中介绍了可以用Invoke()方法调用的方法。域中的每个对象都支持IADS接口的方法。前面创建的User对象也支持IADsUser接口的方法。
在下面的代码示例中,使用方法IADsUser.SetPassword()改变前面创建的user对象的密码:
using (DirectoryEntry de = new DirectoryEntry())
{
de.Path = "LDAP://platinum /CN=John Doe, " +
"CN=Users, DC= athenaproject, DC=local";
de.Invoke("SetPassword", "anotherSecret");
de.CommitChanges();
}
如果不使用Invoke(),还可以直接使用底层的ADSI对象。要使用这些对象,必须使用Project | Add Reference添加对Active DS Type Library的引用,如图24-13所示。这会创建一个包装器类,在该类中可以访问ActiveDS命名空间中的这些对象。
图 24-13
内部对象可以使用DirectoryEntry类的NativeObject属性来访问。在下面的示例中,对象de是一个user对象,所以可以把它强制转换为ActiveDs.IADsUser。SetPassword()是在IADsUser接口中说明的方法,所以可以直接调用它,而不是使用Invoke()方法来调用。把IADsUser的AccountDisabled属性设置为false,就可以激活账户。与前面的示例一样,调用CommitChanges() 和DirectoryEntry对象,把改变的内容写到目录服务中:
ActiveDs.IADsUser user = (ActiveDs.IADsUser)de.NativeObject;
user.SetPassword("someSecret");
user.AccountDisabled = false;
de.CommitChanges();
24.4.9 在Active Directory中搜索
Active Directory是一个数据库,它为大多数读取访问进行了优化,所以一般应使用搜索一些值。要在Active Directory中搜索,可以使用.NET Framework中的类DirectorySearcher。
注意:
DirectorySearcher只能和LDAP提供程序一起使用。它不能和NDS或IIS提供程序一起使用。
在类DirectorySearcher的构造函数中,可以定义搜索的4个重要部分。也可以使用默认构造函数,用属性定义搜索选项。
1. SearchRoot
SearchRoot指定搜索应从什么地方开始。SearchRoot的默认值是当前使用的域的根。SearchRoot用DirectoryEntry对象的Path指定。
2. 过滤器
过滤器定义了希望有的值。过滤器是一个必须放在括号中的字符串。
表达式中可以使用关系运算符,如<=、 =、 >=。(objectClass=contact)会搜索所有类型为contact的对象,(lastName>=Nagel) 会按字母表的顺序搜索lastName属性等于或大于Nagel的所有对象。
表达式可以和前缀运算符 & 和 |组合使用。例如,(&(objectClass=user)(description=Auth*))搜索类型为User,其属性description以字符串Auth开头的所有对象。因为 & 和 | 运算符在表达式的开头,所以可以把多个表达式用一个前缀运算符组合在一起。
默认过滤器是(objectClass=*),所以将选择所有的对象。
注意:
过滤器语法在RFC 2254中定义为“LDAP搜索过滤器的字符串表示”。这个RFC在http://www.ietf.org/rfc/rfc2254.txt中。
3. PropertiesToLoad
使用PropertiesToLoad,可以定义所有属性的StringCollection。对象可以有许多属性,其中的大多数对于搜索请求都不太重要。我们定义了应加载到缓存中的属性。如果没有指定属性,默认属性就应是对象的Path 和Name属性。
4. SearchScope
SearchScope 是一个枚举,定义了搜索应延伸的深度:
● SearchScope.Base只搜索对象中的属性,至多可以得到一个对象。
● SearchScope.OneLevel表示在基对象的子集合中继续搜索。基对象本身是不搜索的。
● SearchScope.Subtree定义了在整个树中搜索。
SearchScope属性的默认值是SearchScope.Subtree。
5. 搜索的限制
在目录服务中搜索特定的对象可以在几个域中进行。对搜索要限制对象的个数或搜索时间,可以定义其他几个属性(如表24-3所示)。
表 24-3
属 性
说 明
ClientTimeout
客户机等待服务器返回结果的最长时间。如果服务器没有响应,就不返回记录
PageSize
使用 paged search,服务器会返回用PageSize 定义的许多对象,而不是所有的对象。这会减少客户获得第一个答案的时间和需要的内存,服务器把一个cookie送给客户机,客户机再把下一个搜索请求发送回服务器,这样搜索就可以在上一次结束的地方继续进行
ServerPageTimeLimit
对于paged search,这个值定义了一个搜索时间,在该时间内返回用PageSize 值定义的许多对象。如果这段时间在PageSize值之前到达,就会把此时查找到的对象返回给客户机。默认值为-1,表示时间为无限
ServerTimeLimit
定义服务器搜索对象的最长时间。过了这段时间后,就会把此时查找到的所有对象返回给客户机。默认值是120秒,不能把搜索时间设置为较高的值
ReferalChasing
搜索可以在几个域中进行。如果用SearchRoot 指定的根是一个父域或没有指定根,就会继续在子域中搜索。使用这个属性可以指定是否应在不同的服务器上继续搜索
ReferalChasingOption.None表示不能在不同的服务器上继续搜索
ReferalChasingOption.Subordinate指定继续在子域中搜索,搜索从DC=Wrox、DC=COM开始时,服务器可以返回一个结果集,例如DC=France、DC=Wrox、DC=COM引用。客户机可以继续在子域中搜索
ReferalChasingOption.External表示服务器可以让客户机搜索不在子域中的另一个服务器,这是默认选项
ReferalChasingOption.All表示返回外部和从属的引用
在搜索示例中,要搜索组织单元Wrox Press中description属性值为Author的所有user 对象。
首先,绑定组织单元Wrox Press,在该组织单元中开始搜索。创建一个DirectorySearcher对象,在其中设置SearchRoot。过滤器定义为(&(objectClass=user) (description=Auth*)),这样就可以搜索所有类型为User、description属性以Auth开头的对象。搜索的范围应在一个子树中,即在Wrox Press的子组织单元中搜索:
using (DirectoryEntry de =
new DirectoryEntry("LDAP://OU=Wrox Press, DC= athenaproject, DC=local"))
using (DirectorySearcher searcher = new DirectorySearcher())
{
searcher.SearchRoot = de;
searcher.Filter = "(&(objectClass=user)(description=Auth*))";
searcher.SearchScope = SearchScope.Subtree;
要在搜索结果中包含的属性有name、description、givenName和WWWHomePage。
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("description");
searcher.PropertiesToLoad.Add("givenName");
searcher.PropertiesToLoad.Add("wWWHomePage");
下面准备开始搜索。还应对结果进行排序。DirectorySearcher有一个属性Sort,它可以设置SortOption。SortOption构造函数的第一个参数定义了要排序的属性,第二个参数定义了排序的方向。SortDirection枚举的值是Ascending 和 Descending。
要开始搜索,可以使用FindOne()方法查找第一个对象,或者使用FindAll()。FindOne()返回一个简单的SearchResult,FindAll()返回一个SearchResultCollection。要得到所有的作者对象,就应使用FindAll():
searcher.Sort = new SortOption("givenName", SortDirection.Ascending);
SearchResultCollection results = searcher.FindAll();
使用一个foreach循环,可以访问SearchResultCollection中的每个SearchResult。一个SearchResult表示搜索缓存中的一个对象。Properties属性返回一个ResultPropertyCollection,使用属性名和索引符可以访问该集合中所有的属性:
SearchResultCollection results = searcher.FindAll();
foreach (SearchResult result in results)
{
ResultPropertyCollection props = result.Properties;
foreach (string propName in props.PropertyNames)
{
Console.Write(propName + ": ");
Console.WriteLine(props[propName][0]);
}
Console.WriteLine();
}
}
可以在搜索之后获得完整的对象:SearchResult有一个方法GetDirectoryEntry(),它返回查找到的对象的相应DirectoryEntry。
得到的结果应显示《Professional C#》(即《C#高级编程》)的作者列表,这些作者都有我们指定的属性,如图24-14所示。
24.5 搜索用户对象
本章的最后一节要创建一个Windows Forms应用程序UserSearch。这个应用程序是非常灵活的,因为可以输入特定的域控制器、用户名和密码,以访问Active Directory,或者使用运行进程的用户。在这个应用程序中,我们将访问Active Directory服务的模式,获得user对象的属性。该用户可以输入一个过滤器字符串,搜索域中的所有user对象。还可以设置应显示的user对象的属性。
24.5.1 用户界面
用户界面显示了一些已编好序号的步骤,说明如何使用该应用程序(如图24-15所示):
图 24-15
● 在第一步中,输入用户名、密码和域控制器。所有这些信息都是可选的。如果没有输入域控制器,就要使用无服务器绑定进行连接。如果没有输入用户名,就使用当前用户的安全环境。
● 使用一个按钮,就可以把User对象的所有属性名动态加载到listBoxProperties 列表 框中。
● 在加载了属性名后,就可以选择要显示的属性。列表框的SelectionMode设置为MultiSimple。
● 可以输入限制搜索的过滤器,在该对话框中设置的默认值是搜索所有的user对象:(objectClass=user)。
● 现在开始搜索。
24.5.2 获取模式命名环境
这个应用程序只有两个处理程序方法:按钮的第一个处理程序方法加载属性,第二个处理程序方法则在域中开始搜索操作。在第一部分,从模式中动态读取User类的属性,在用户界面中显示它。
在处理程序方法buttonLoadProperties_Click()中,使用SetLogonInformation()从对话框中读取用户名、密码和主机名,并存储在类的成员中。接着,SetNamingContext()方法设置模式的LDAP名称和默认环境的LDAP名称。这个模式LDAP名称用于调用中,以设置列表框中的属性SetUserProperties()。
private void buttonLoadProperties_Click(object sender, System.EventArgs e)
{
try
{
SetLogonInformation();
SetNamingContext();
SetUserProperties(schemaNamingContext);
}
catch (Exception ex)
{
MessageBox.Show("Check your inputs! " + ex.Message);
}
}
protected void SetLogonInformation()
{
username = (textBoxUsername.Text == "" ? null : textBoxUsername.Text);
password = (textBoxPassword.Text == "" ? null : textBoxPassword.Text);
hostname = textBoxHostname.Text;
if (hostname != "") hostname += "/";
}
在帮助方法SetNamingContext()中,使用目录树的根来获得服务器的属性。这里只考虑两个属性的值:schemaNamingContext 和 defaultNamingContext。
protected string SetNamingContext()
{
using (DirectoryEntry de = new DirectoryEntry())
{
string path = "LDAP://" + hostname + "rootDSE";
de.Username = username;
de.Password = password;
de.Path = path;
schemaNamingContext = de.Properties["schemaNamingContext"][0].ToString();
defaultNamingContext =
de.Properties["defaultNamingContext"][0].ToString();
}
}
24.5.3 获取User类的属性名
使用LDAP名称可以访问模式。使用它可以访问目录,并读取属性。我们不仅要介绍User类的属性,还将介绍User的基类:Organizational-Person、Person和Top。在这个程序中,基类的名称是硬编码的。还可以使用subClassOf属性动态读取基类。GetSchemaProperties()返回一个字符串数组和指定对象类型的所有属性名。这些属性名都在StringCollection属性中:
protected void SetUserProperties(string schemaNamingContext)
{
StringCollection properties = new StringCollection();
string[] data = GetSchemaProperties(schemaNamingContext, "User");
properties.AddRange(GetSchemaProperties(schemaNamingContext,
"Organizational-Person"));
properties.AddRange(GetSchemaProperties(schemaNamingContext, "Person"));
properties.AddRange(GetSchemaProperties(schemaNamingContext, "Top"));
listBoxProperties.Items.Clear();
foreach (string s in properties)
{
listBoxProperties.Items.Add(s);
}
}
在GetSchemaProperties()中,再次访问Active Directory服务。这次不使用rootDSE,而使用前面介绍的模式的LDAP名称。属性systemMayContain包含objectType类中的所有属性的一个集合:
protected string[] GetSchemaProperties(string schemaNamingContext,
string objectType)
{
string[] data;
using (DirectoryEntry de = new DirectoryEntry())
{
de.Username = username;
de.Password = password;
de.Path = "LDAP://" + hostname + "CN=" + objectType + "," +
schemaNamingContext;
DS.PropertyCollection properties = de.Properties;
DS.PropertyValueCollection values = properties["systemMayContain"];
data = new String[values.Count];
values.CopyTo(data, 0);
}
return data;
}
注意上述代码中的DS.PropertyCollection,这是因为在Windows Forms应用程序中,System.DirectoryServices命名空间中的PropertyCollection类与System.Data.PropertyCollection的名称相冲突。为了避免使用像System.DirectoryServices.PropertyCollection这么长的名称,可以使用下面的代码来缩短命名空间的名称:
using DS = System.DirectoryServices;
这样就完成了应用程序的第二步。Listbox控件包含User对象的所有属性名。
24.5.4 搜索用户对象
搜索按钮的处理程序只调用帮助方法FillResult();
private void buttonSearch_Click(object sender, System.EventArgs e)
{
try
{
FillResult();
}
catch (Exception ex)
{
MessageBox.Show("Check your input: " + ex.Message);
}
}
在FillResult()中,在整个Active Directory 域中进行与前面一样的正常搜索。SearchScope设置为Subtree, Filter设置为TextBox中的字符串,应加载到缓存中的属性由用户在列表框中选择的值来设置。方法GetProperties()用于把属性数组传送给方法searcher。PropertiesTo Load.AddRange()是一个帮助方法,它从列表框中把选中的属性度到一个数组中。在设置了DirectorySearcher对象的属性后,就调用SearchAll()方法搜索属性。SearchResultCollection中的搜索结果用于生成写到文本框textBoxResults中的汇总信息。
protected void FillResult()
{
using (DirectoryEntry root = new DirectoryEntry())
{
root.Username = username;
root.Password = password;
root.Path = "LDAP://" + hostname + defaultNamingContext;
using (DirectorySearcher searcher = new DirectorySearcher())
{
searcher.SearchRoot = root;
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = textBoxFilter.Text;
searcher.PropertiesToLoad.AddRange(GetProperties());
SearchResultCollection results = searcher.FindAll();
StringBuilder summary = new StringBuilder();
foreach (SearchResult result in results)
{
foreach (string propName in
result.Properties.PropertyNames)
{
foreach (string s in result.Properties[propName])
{
summary.Append(" " + propName + ": " + s + "rn");
}
}
summary.Append("rn");
}
textBoxResults.Text = summary.ToString();
}
}
}
启动该应用程序,将列出所有由过滤器选择的有效对象,如图24-16所示。
24.6 小结
本章介绍了Active Directory的体系结构,有关域、树和森林的重要概念。利用它,我们可以访问整个企业的信息。在编写访问Active Directory服务的应用程序时,必须注意读取的数据可能不是最新的,因为进行复制操作时有一定的等待时间。
使用System.DirectoryServices命名空间中的类,可以很容易地访问封装到ADSI提供程序中的Active Directory服务。DirectoryEntry类可以直接读写数据库中的对象。
使用DirectorySearcher类可以进行复杂的搜索,定义过滤器、超时、加载的属性和范围等。使用全局目录,可以加快对整个企业中的对象的搜索,因为它在森林中存储了所有对象的只读版本。
更多阅读
VBA编程中MsgBox函数怎么用 vba msgbox
VBA编程中MsgBox函数怎么用——简介Excel编程中的MsgBox函数用于输出一个对话框,该函数在编程调试以及输出结果方面具有极为重要的意义。下面小编就为大家讲解一下该函数的具体使用方法。VBA编程中MsgBox函数怎么用——方法/步骤
开机桌面变白显示还原Active Desktop怎么办 精 active desktop
虽然开启活动桌面active desktop有些好处,比如可以在桌面显示WEB的内容,包括图片和链接等。还可以将你浏览到心仪的图片可以通过单击鼠标右键将图片直接设为背景或者桌面项。但多数情况下我们并不需要此功能,但电脑却莫名其妙的开启了A
Clonedvirtualizeddomaincontroller克隆虚拟化部署的域控制器 cloned
简介:在Windows Server2012之前的版本中,在域控制器升级的过程中,添加额外的虚拟域控制器涉及到的数据复制方法有两种,分别是“复制”网络、使用IFM媒体。但如果数据库(NTDS.DIT)本身比较大,这两种方法都需要大量时间来复制Active Direc
Directory of Open Access Journals (DOAJ)_首都经济... www.doaj.org
Directory of Open Access Journals (DOAJ)时间:2010-03-16 09:58来源: 录入:luhui 点击:89次访问地址http://www.doaj.org/登录权限包含学科 社会科学综合,自然科学综合文献类型 电子期刊,OA资源内容属性 全文,摘要,索引,全文连接
重做日志文件组的三种状态(current,active,inactive) channelinactive 事件
以下内容是我从自己的测试环境中查询出来的在线重做日志信息:SQL> select* fr