Windows Powershell+ADSI
使用 Active Directory |
|
如果您曾经玩过 Windows PowerShell,那么您一定尝试过 Get-WMIObject cmdlet — 也称为 gwmi,这是它更为方便的别名。如我在上个月的专栏中所述,此 cmdlet 展示了 Windows PowerShell 相对于以前的脚本语言和命令行外壳的一项重要改进
(请参阅 technetmagazine.com/issues/2007/05/PowerShell)。通过 Gwmi 可以快速、方便地访问 Windows® Management Instrumentation (WMI),从而可以完成许多令人难以置信的工作,而无需编写复杂的脚本。
Get-WMIObject 就是尽显 Windows PowerShell™ 能力的一个最佳例证,每当我在课堂上展示它时,学生们总是能够将它的功能拓展到 Windows 管理的其他方面。但是,通常人们提出的第一个问题总是围绕着“存在 Get-ADSIObject cmdlet 吗?”。
Windows PowerShell 中的 ADSI 支持
ADSI 是 Active Directory® 服务接口的缩写。尽管其名称如此,但实际上 ADSI 并不是特定于 Active Directory 的。该名称更为精确的解读应该是“Active(停顿)Directory Services Interface”(活动的目录服务接口),因为 ADSI 可以连接到各种目录服务,其中包括 Windows NT® 样式的目录 — 注意,不仅仅是 Windows NT 域,还可以连接到独立的或成员计算机上的本地安全帐户管理器 (SAM)。凭借这种灵活性,Get-ADSIObject cmdlet 无疑用途广泛。但遗憾的是,Windows PowerShell 团队未能在其第一版中纳入该 cmdlet。
但这并不是说 Windows PowerShell 缺乏 ADSI 支持。事实上,Windows PowerShell 中的 ADSI 支持是如此优秀,您甚至不会注意到 Get-ADSIObject cmdlet 的缺失!
这一切始于 [ADSI] 类型适配器。我们需要对类型适配器稍加解释。我们知道 Windows PowerShell 是在 Microsoft® .NET Framework(具体而言,是 2.0 或更高版本)上构建的。因此,Windows PowerShell 中的几乎所有内容 — 从 WMI 对象到文件和文件夹 — 都表示为该 Framework 中的对象。但是,Windows PowerShell 团队并不喜欢只将原始的 Framework 对象呈现给 Windows 管理员。例如,许多 Framework 对象的使用都有一定的不一致性,而一致性是 Windows PowerShell 的一个主要设计目标。另一个原因是某些 Framework 对象在涉及 Windows 管理时有些多余,而简洁性也是 Windows PowerShell 设计思想的一个主要方面。这样就诞生了类型适配器。
顾名思义,类型适配器将 .NET Framework 对象类型“适配”为一种更一致、有时也是更简单的格式。例如,假设您创建一个如下所示的新字符串变量:
[string]$var = “Hello”
您使用了一个由 .NET 适配器处理的 System.String 类型的实例。[ADSI] 类型适配器连接至 .NET Framework DirectoryServices 类,其中包括 DirectoryEntry 类型。
从目录中检索对象
连接到目录对象比较直接。例如,要连接到 Contoso.com 域的 IT 组织单位 (OU) 中名为 Don 的用户,可使用如下代码:
$user = [ADSI]”LDAP://cn=Don,ou=IT,dc= Contoso,dc=com”
如果您在 VBScript 或其他语言中使用过 ADSI,对此应当并不陌生。它是一个标准轻型目录访问协议 (LDAP) 查询字符串,是访问 Active Directory 的本机方式。您无需指定域控制器 (DC) 名称或任何其他详细信息;此完全限定域名 (FQDN) 已包含足够信息来定位 DC 和检索对象(当然,假设您具有相应权限)。
本地安全帐户的处理过程基本相同,只是这时要使用 WinNT 提供程序,而不是 LDAP 提供程序:
$user = [ADSI]”WinNT://don-pc/don”
请注意,LDAP 和 WinNT 别名都区分大小写。虽然 Windows PowerShell 本身通常不区分大小写,但这些 ADSI 资源却区分 — 因此在键入时应当注意。
图 1 显示了获取所得到的 $user 变量并将其通过管道传送至 Get-Member 时发生的情况:我将得到一个对象属性列表。但是,这不是所有用户属性的完整列表,并且其中不含用户对象的任何方法。从这个角度讲,基础 System.DirectoryServices.DirectoryEntry Framework 类的工作方式有些奇怪,并且 Windows PowerShell 只能显示 Framework 类所提供的内容。
检索对象主要是为了了解如何构造查询字符串。WinNT 提供程序的使用非常简单。只需指定一个计算机名或域名、一条斜线,再加上一个对象名即可。此技术非常适合 Active Directory 域(毕竟这些域与 Windows NT 域向后兼容)。事实上,有时使用 WinNT 提供程序从 Active Directory 中检索对象是最简单的方式,因为 WinNT 提供程序提供了一个域的平面视图。这意味着,您无需知道对象位于哪个 OU 中,因为 WinNT 提供程序看不到这些 OU。
LDAP 对象的检索要求更为精确些。首先要尽可能提供最精确的信息,如对象的规范名称 (cn) 或 OU 的名称。然后,应提供对象的完整路径,从其父容器开始,直至域本身。例如,检索位于 Sales OU(位于 Contoso.com 域的 West OU 中)中的用户 Don 应如下所示:
LDAP://cn=Don,ou=Sales,ou=West,dc=Contoso, dc=com
此格式需要一点点的实践并要求您知道对象所在位置。图 2 汇总了 FQDN 的各种组件。
来自朋友的一些帮助
创建和删除对象通常要求首先连接到对象的容器(如 OU),然后请求容器创建或删除该对象。我总不记得正确的语法,幸运的是,Microsoft 脚本专家可以通过 ADSI Scriptomatic 助我们一臂之力。
虽然 ADSI Scriptomatic 最初是为 VBScript 创建的,但它仍是 Windows PowerShell 的一个非常有用的指南,因为实际的 ADSI 语法是相同的。例如,在下载该工具后,我选择了创建新用户的选项。我忽略了该工具生成的所有脚本代码,直至“End connect to a container”代码之后,如下所示:
Set objUser = objContainer.Create(“user”, “cn=” & strName) objUser.Put “sAMAccountName”, strName objUser.SetInfo
转换到 Windows PowerShell 很简单。首先,我连接到要在其中创建用户的容器。为与 Scriptomatic 代码保持一致,我将使用变量 $objContainer 来表示 OU。我还要创建一个变量 $strName,用于存储新用户的名称:
$strName = “Don” $objContainer = “LDAP//ou=Test,dc=Contoso, dc=com”
接下来,我复制所有 Scriptomatic 代码,删除任何 VBScript Set 语句的所有痕迹,并向变量名添加 $:
$objUser = $objContainer.Create(“user”, “cn=” & $strName) $objUser.Put “sAMAccountName”, $strName $objUser.SetInfo()
这就是所需的全部工作 — 一个新用户在 Contoso .com 域的 Test OU 中创建完毕。
修改目录对象
连接到对象后 — 例如一个用户或组 — 您可以修改它。使用 WinNT 提供程序,您可以直接修改许多属性,如图 1 中所示的那些属性。例如,要显示然后更改用户的 Description 属性,可以编写如下代码:
Write-Host $user.Description $user.Description = “New Description” $user.SetInfo()
有一点必须牢记,属性更改在本地属性缓存中进行,这是 ADSI 在计算机上维护的一个临时存储区域。调用 SetInfo 方法会将缓存写回您从中检索对象的计算机或域,其实质是将您的更改保存回目录。
对于多数对象属性,Active Directory 对象并不直接提供属性。实际上,您应当使用 Get 和 Put 方法:
Write-Host $user.Get(“Description”) $user.Put(“Description”,”New Description”) $user.SetInfo()
WinNT 对象倾向于简洁化,因为 Windows NT 目录服务没有存储有关对象(如用户和组)的许多信息。相反,Active Directory 存储了众多信息,要找出正确的属性名称会有些难度。例如,如果要更改用户的姓氏,则必须更改 Sn 属性。为什么必须更改 Sn 呢?因为它代表姓氏。
幸运的是,Microsoft 提供了一个很好的交叉引用,您可以通过 msdn2.microsoft.com/ms677286.aspx 在线获得该引用。该引用显示了“Active Directory Users & Computers”(Active Directory 用户和计算机)控制台每个主要区域的属性名称,因此如果您知道要在控制台中进行哪些更改,则可以找出基础属性名称。请注意,Cade Fassett 所著的“ADSI Scripting:TFM”(ADSI 脚本:TFM)(SAPIEN Press, 2007) 一书中提供了更完整的控制台至属性交叉引用。
不能完成的任务
遗憾的是,Windows PowerShell 中的 ADSI 支持不像对其他技术(如 WMI)的支持那样强。例如,枚举本地计算机的组成员就有些困难。在处理 Active Directory 时,同样会遇到一些困难情况。根据我的经验,基本的对象检索、创建和删除比较容易。修改对象属性也相对简单,虽然有些属性不可访问。最大的难度来自通过 WinNT 提供程序对本地对象和组的处理。
这并不是说没有一点办法,只是有些事情要更困难一些,或者不那么直观。请看下面的例子:
$group = [ADSI]”LDAP://cn=Techs,cn=users, dc=contoso,dc=com” $group.Member.Add(“cn=user1,cn=users, dc=contoso , dc=com”) $group.psbase.CommitChanges()
该示例将名为 User1 的用户添加到 Techs 组中。它非常明了。它使用 FQDN 来检索该组,然后使用该组的 Member 集合 — 具体而言,是该集合的 Add 方法 — 来添加所需用户的 FQDN。但是,最后一步并不那么显而易见。它调用隐藏的 PSBase 类的 CommitChanges 方法来保存更改。
PSBase 是一个特殊类,直接代表基本的 .NET Framework 对象。在本例中,没有直接在 Windows PowerShell 中公开 CommitChanges(实际上,ADSI 适配器没有公开任何方法),因此您必须转向基础类。尽管这不是不可能的,但显然不够直观。这也表明目录服务功能尚未完全适合 Windows PowerShell。
这有许多原因。首先,在整个开发周期中,Windows PowerShell 团队向外壳添加当前 ADSI 支持的时机有些晚。就像外壳架构师 Jeffrey Snover 经常说的“提供就是选择”。该团队用来开发和交付外壳的时间是固定的,他们必须做出决定要为下一版本保存哪些内容。如果不是这样,他们可以轻易地花上几年时间来添加新功能,而永远不会实际发布一款支持的产品。
不过,还有一个原因就是基础 .NET Framework。它的 DirectoryServices 类非常复杂,使那种复杂性与 Windows PowerShell 相配需要大量时间和认真规划。在我看来,Framework 类也有些侧重于 Active Directory,并且没有为 WinNT 提供程序提供有力支持 — 当然,Windows PowerShell 会从 Framework 继承任何这类局限性。
我预计 Windows PowerShell 会逐渐拥有广泛的目录服务支持 — 甚至是可以不通过 ADSI 层,而是通过 .NET Framework 中的基本改进获得的支持。同时,Windows PowerShell 中的 ADSI 支持还允许您执行绝大部分管理任务,如创建用户、修改通用用户属性等。
请不要忘记,许多目录服务任务可通过 WMI 完成。请查阅 microsoft.com/technet/scriptcenter/scripts 中提供的某些优秀脚本示例,它们使用 WMI 提供有关本地组的更多信息,比 ADSI 本身提供的信息还要广泛。
Don Jones 是 SAPIEN Technologies 的首席脚本权威,也是 ScriptingTraining.com 的一名讲师。您可以通过网站 ScriptingAnswers.com 与 Don 联系。
摘自 June 2007 期刊 TechNet Magazine.













历史评论
暂无保留的历史评论。
评论为旧站历史记录,本站不再接受新评论。