如何在Active Directory cmdlet上有效地使用"-Filter"参数



我经常在这个网站上看到以下类型的代码,特定于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-ADUserGet-ADComputerGet-ADGroup、通用Get-ADObject等)有效地选择并返回AD中存在的每个对象。这是一项成本高昂的操作,尤其是在较大的AD环境中。在足够大的环境中,即使您合法地需要对给定类型的每个AD对象进行操作,您也会希望分批拆分查询。除此之外,您的脚本最终处理的数据将远远超过其需要的数据,从而增加执行时间,并在不需要时使用处理时间。

-Filter参数可以做的不仅仅是匹配所有内容,这实际上就是
-Filter *所做的。-Filter字符串非常像Powershell语法(虽然不太像,但大部分都是这样)。您可以使用Powershell支持的大多数逻辑运算符,它们的工作方式与Powershell运算符的工作方式大致相同。这个答案旨在澄清这一点,并解释如何使用这个难以捉摸的参数。这些示例将使用Get-ADUsercmdlet,但这也扩展到其他也使用筛选器的Get-ADObjectcmdlet。


语法

-Filter字符串的语法为"PropertyName -comparisonoperator 'somevalue'",但您可以将多个条件与逻辑运算符(如-and-or)串在一起。请注意,没有正则表达式匹配运算符,因此您将不得不使用-like
-notlikeglobbing。

比较运算符

MS调用这些FilterOperators,但它们的使用方式与PowerShell的比较运算符是(忽略了技术上-bor-band是算术运算符这一事实)。这些用于比较值:

注意:DistinguishedName格式的AD属性在使用
-like-notlike时将不会应用globbing,换句话说,您必须查找完全匹配的属性。如果需要DN来匹配任何模式,则无法使用-Filter
-LDAPFilter执行此操作。您必须尽可能使用-Filter,并在Get-ADObjectcmdlet返回后使用-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-ADUsermemberOf进行筛选可能会有效。它可能在交互方面很不方便,但在任何与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值的帐户。


需要避免的事情

  1. 不要尝试使用{ 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。

    每次使用字符串筛选器,如果需要使用变量值或对象属性作为筛选器的一部分,请使用变量替换或命令替换。

  2. 如果只关心要筛选的属性,则无需指定其他-Properties。AD cmdlet可以评估-Filter参数中的所有属性,而无需将它们向下传递到管道中。

  3. 当我在做这件事的时候,永远不要使用-Properties *,除非你出于某种原因检查返回对象的所有属性,比如在脚本开发过程中,或者以交互方式你不太确定你在寻找什么(注意,并非所有属性都是默认返回的)

    仅指定返回AD对象后需要处理的属性。这是有原因的——有些房产的价格特别昂贵。最佳做法是只转发需要处理的属性。

  4. 不能使用-Filter参数对使用
    -Filter-LDAPFilter的构造属性进行筛选。这是因为根据定义,构造的属性是动态计算的(或"构造的"),实际上并不是Active Directory中存储的值。我想这是因为许多计算属性的计算成本很高,必须对每个相关的ADObject执行计算,才能从AD端对其进行过滤。

    如果需要对构造属性进行筛选,则需要首先返回一组ADObjects,用-Properties指定Computed Attribute,然后用Where-Object或其他技术进行进一步筛选。

  5. 通配符*不适用于返回DistinguishedName类型的字段,如DistinguishedNamemanagerPrimaryGroup等。此外,DistinguishedNames携带自己的一组转义规则。

  6. 一些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年以来一直没有更新。在修复这个错误并更新文档之前,以上链接就足够了

最新更新