我经常在这个网站上看到以下类型的代码,特定于AD cmdlet:
Get-ADUser -Filter * | Where-Object { $_.EmailAddress -eq $email }
问题是,您正在返回Active Directory中的每一个用户对象,然后进行第二次处理。我们如何改进这一点,不仅可以减少运行脚本所需的时间,还可以减少Active Directory以及网络上不必要的负载?
关于Azure AD cmdlet的说明
此答案是围绕从
Remote Server Administration Tools (RSAT)
安装并可用的Active Directory cmdlet精心编制的。但是,Azure AD cmdlet使用Microsoft Graph(OData v4.0规范)对Azure AD运行查询,而RSAT cmdlet[1]则依赖于PowerShell表达式引擎的实现来替换LDAP筛选器。因此,如果不进行一些修改以与Microsoft Graph规范(特别是其筛选器语法)兼容,下面的筛选器示例将无法与Azure AD cmdlet一起使用。然而,这里提到的一般做法仍然应该适用。
[1]-这是我能找到的这份文件的最新版本
-Filter *
有什么不好的地方
您正在根据正在使用的cmdlet(例如Get-ADUser
、Get-ADComputer
、Get-ADGroup
、通用Get-ADObject
等)有效地选择并返回AD中存在的每个对象。这是一项成本高昂的操作,尤其是在较大的AD环境中。在足够大的环境中,即使您合法地需要对给定类型的每个AD对象进行操作,您也会希望分批拆分查询。除此之外,您的脚本最终处理的数据将远远超过其需要的数据,从而增加执行时间,并在不需要时使用处理时间。
-Filter
参数可以做的不仅仅是匹配所有内容,这实际上就是-Filter *
所做的。-Filter
字符串非常像Powershell语法(虽然不太像,但大部分都是这样)。您可以使用Powershell支持的大多数逻辑运算符,它们的工作方式与Powershell运算符的工作方式大致相同。这个答案旨在澄清这一点,并解释如何使用这个难以捉摸的参数。这些示例将使用Get-ADUser
cmdlet,但这也扩展到其他也使用筛选器的Get-ADObject
cmdlet。
语法
-Filter
字符串的语法为"PropertyName -comparisonoperator 'somevalue'"
,但您可以将多个条件与逻辑运算符(如-and
和-or
)串在一起。请注意,没有正则表达式匹配运算符,因此您将不得不使用-like
和-notlike
globbing。
比较运算符
MS调用这些FilterOperators
,但它们的使用方式与PowerShell的比较运算符是(忽略了技术上-bor
和-band
是算术运算符这一事实)。这些用于比较值:
注意:DistinguishedName格式的AD属性在使用
-like
或-notlike
时将不会应用globbing,换句话说,您必须查找完全匹配的属性。如果需要DN来匹配任何模式,则无法使用-Filter
或-LDAPFilter
执行此操作。您必须尽可能使用-Filter
,并在Get-ADObject
cmdlet返回后使用-like
或-match
运算符执行其他处理。
-eq
、-le
、-ge
、-ne
、-lt
、-gt
、-approx
、-bor
、-band
、-recursivematch
、-like
、-notlike
-Filter
查询语法唯一的是-approx
和-recursivematch
。不要担心-approx
,它在功能上等同于Active Directory中的-eq
。
尽管-recursivematch
的名称是而不是正则表达式匹配运算符,但它的工作方式与PowerShell的-contains
运算符类似,因为如果集合包含目标值,它将返回$true
。
逻辑运算符
MS调用这些JoinOperators
,但它们的角色与其PowerShell逻辑运算符等效。这些用于在单个查询中将多个条件连接在一起:
-and
,-or
奇怪的是,MS否定了一种称为NotOperator
的特殊运算符类型,它由一个运算符组成:
-not
属性匹配
为了使用问题中的示例,让我们找到一个与电子邮件地址匹配的用户,但不使用管道连接到Where-Object
(疯狂的对吧??):
$email = 'box@domain.tld'
Get-ADUser -Filter "EmailAddress -eq '${email}'"
完成。Get-ADUser
将返回EmailAddress
属性等于$email
变量的任何帐户。
如果我们想查找过去30天内未登录的所有用户帐户,该怎么办?但是日期字符串比电子邮件更复杂!谁在乎呢,还是很简单!
# Get the date from 30 days ago
$notUsedSince = ( Get-Date ).AddDays( -30 )
Get-ADUser -Filter "LastLogonDate -lt '${notUsedSince}'"
这将返回过去30天内未登录的所有用户。
获取组成员的用户
如果您想获得某个组的所有ADUsers
成员,我们可以使用-recursivematch
运算符:
Get-ADUser -Filter "memberOf -recursivematch 'CN=test_group,CN=Users,DC=exampledomain,DC=net'"
memberOf
是可分辨名称的array
,如果左侧的数组包含右侧的值,则-recursivematch
返回true。
在这种情况下,您也可以完全避免使用Get-ADUser
,并使用Get-ADGroup
从中检索成员:
( Get-ADGroup group_name -Properties Members ).Members
虽然上面的Get-ADGroup
示例的键入时间较短,但当您有多个条件并且需要返回属于某个组的用户,但不一定需要返回该组进行本地处理时,使用Get-ADUser
对memberOf
进行筛选可能会有效。它可能在交互方面很不方便,但在任何与Active Directory集成的自动化过程中,它都是一种有价值的技术,并且在您有非常大的组的情况下可能是必要的。
一个例子是在一个非常大的域中枚举Domain Users
。您可能需要重新考虑从( Get-ADGroup ).Members
返回32000个用户,然后必须应用额外的筛选。
注意:大多数用户实际上会将
Domain Users
设置为他们的PrimaryGroup
。这是默认设置,大多数情况下不需要更改。但是,您必须在PrimaryGroup
上使用-Filter
,因为PrimaryGroup
不是存储在MemberOf
下的ADUser
。它也是单个值,而不是集合,因此使用-eq
:Get-ADUser -Filter "PrimaryGroup -eq 'PRIMARY_GROUP_DN'"
如果查询项包含引号怎么办
在大多数情况下,查询词中的引号会对查询造成影响。以搜索名称中包含O'Niel
的用户为例。这可能会破坏查询或脚本逻辑,具体取决于所使用的引用技术:
# Our heroic search term
$term = "O'Niel"
# Dragons abound (results in a query parsing error)
Get-ADUser -Filter "Name -like '*${term}*'"
# Your princess is in another castle ($term is not expanded
# and the literal string ${term} is instead searched for)
Get-ADUser -Filter 'Name -like "*${term}*"'
在这种情况下,您将不得不在两个位置都使用双引号字符串,但幸运的是,escape地狱并不太糟糕。使用与以前相同的$term
值:
# Your quest is over (this works as intended and returns users named O'Niel)
Get-ADUser -Filter "Name -like ""*${term}*"""
# Backticks are ugly but this also works
Get-ADUser -Filter "Name -like `"*${term}*`""
注意:如果您的查询查找包含单引号和双引号的字段值,我不知道在使用
-Filter
参数时如何使用一个命令来实现这一点。但是,-LDAPFilter
应该能够促进这一点,因为括号()
而不是引号用于内部查询边界。有关更多信息,请参阅about_ActiveDirectory_Filter中的Filter Examples
和本AD转义符帖子的LDAP Filters
部分,因为-LDAPFilter
超出了本答案的范围。
在多个属性上匹配
多个属性上的匹配没有太大区别,但最好将每个条件封装在括号()
中。这里有一个例子,让我们找到没有关联电子邮件地址的非域管理员帐户(假设我们通过用户名命名*-da
知道这一点)。
Get-ADUser -Filter "(samaccountname -notlike '*-da') -and (EmailAddress -notlike '*')"
这一个有点棘手,因为我们不能像EmailAddress
那样,在-Filter
中的条件右侧传递空值。但是"*"匹配任何非空值,因此我们可以利用-notlike
比较运算符的这种行为来查找EmailAddress
的空值。要分解筛选器,请确保以-da
结尾的任何帐户都不匹配筛选器,然后也只匹配没有EmailAddress
值的帐户。
需要避免的事情
不要尝试使用
{ ScriptBlock }
作为过滤器参数。是的,比起担心构建string
并确保其正确逃逸,我们都更愿意编写ScriptBlock
。使用它们肯定有吸引力。我看到过很多使用ScriptBlock
作为-Filter
论点的答案,或者有问题的人(包括我自己)试图做这样的事情,这让我感到惊讶!!!无任何回报:Import-Csv C:userInfoWithEmails.csv | Foreach-Object { Get-ADUser -Filter { EmailAddress -eq $_.Email } }
-Filter
不支持ScriptBlocks
,但它们有时是一种工作™,因为当它们被呈现为文本字符串时,-Filter
使用的PowerShell表达式引擎能够在运行查询之前呈现变量。正因为如此,如果你使用像$_
或$emailAddress
这样的简单变量展开,它们在技术上会起作用,但它最终会让你头疼,尤其是如果你试图访问对象属性(如上所述),因为它根本不起作用。此外,对于如何扩展这些变量,您会得到大量未记录(或难以定位信息)的行为,因为它们并不总是像您所期望的那样是
ToString'd
。弄清楚它就成了一件试错的事情。诚然,通过这种方式可以更容易地获得某些属性,但在编程时,使用您不理解且几乎没有文档的技术是有风险的。正因为如此,我不依赖于cmdlet内部变量扩展,无论您使用文字字符串还是ScriptBlock
与AD cmdlet。每次使用字符串筛选器,如果需要使用变量值或对象属性作为筛选器的一部分,请使用变量替换或命令替换。
如果只关心要筛选的属性,则无需指定其他
-Properties
。AD cmdlet可以评估-Filter
参数中的所有属性,而无需将它们向下传递到管道中。当我在做这件事的时候,永远不要使用
-Properties *
,除非你出于某种原因检查返回对象的所有属性,比如在脚本开发过程中,或者以交互方式你不太确定你在寻找什么(注意,并非所有属性都是默认返回的)
仅指定返回AD对象后需要处理的属性。这是有原因的——有些房产的价格特别昂贵。最佳做法是只转发需要处理的属性。不能使用
-Filter
参数对使用-Filter
或-LDAPFilter
的构造属性进行筛选。这是因为根据定义,构造的属性是动态计算的(或"构造的"),实际上并不是Active Directory中存储的值。我想这是因为许多计算属性的计算成本很高,必须对每个相关的ADObject
执行计算,才能从AD端对其进行过滤。如果需要对构造属性进行筛选,则需要首先返回一组
ADObjects
,用-Properties
指定Computed Attribute
,然后用Where-Object
或其他技术进行进一步筛选。通配符
*
不适用于返回DistinguishedName
类型的字段,如DistinguishedName
、manager
、PrimaryGroup
等。此外,DistinguishedNames
携带自己的一组转义规则。一些AD属性作为适当的
DateTime
返回,以便于在PowerShell中进行处理,但上面示例的基本时间比较要求将基础ADAttribute
定义为Interval
类型。一些基于时间的属性,如whenCreated
,被定义为广义时间字符串,它们是UTC时区,格式为yyyMMddHHmmss.Z
。此外,一些属性(如msDS-UserPasswordExpiryTimeComputed
)采用文件时间格式(并与AD cmdlet一起返回)。将用于筛选的目标
DateTime
转换为通用时间字符串格式,如下所示:( Get-Date ).ToUniversalTime().ToString('yyyMMddHHmmss.z').
请注意,此字符串不能直接转换回
DateTime
。将返回的文件时间转换为
DateTime
,如下所示(以上述属性为例):[DateTime]::FromFileTime($adUser.'msDS-UserPasswordExpiryTimeComputed')
总结
在大型AD环境中迭代时,这些将-Filter
参数与AD cmdlet一起使用的技术将节省昂贵的处理时间,并应提高Powershell AD操作的性能。我希望这有助于解释AD cmdlet的-Filter
参数的一些难以捉摸的行为。
其他资源
由于了解您正在使用的AD属性是一个好主意,以下是一些Microsoft资源,可帮助您识别和了解不同属性是如何在AD架构中定义和发挥作用的,并了解有关-Filter
语法的更多信息:
- OpenSpecs
- AD架构
- 关于活动目录筛选器
- 虽然已经过时,但此文档帮助仍然准确无误。由于一个错误,它自2013年以来一直没有更新。在修复这个错误并更新文档之前,以上链接就足够了