如何有效地使用Active Directory cmdlet中的“-Filter”参数?

5

我经常在这个网站上看到以下类型的代码,与AD cmdlets相关:

Get-ADUser -Filter * | Where-Object { $_.EmailAddress -eq $email }

问题在于您返回了Active Directory中的每个用户对象,然后进行第二次处理。我们如何改进此操作,以减少脚本运行所需的时间,并减轻Active Directory和可能的网络不必要的负担?
1个回答

15

关于 Azure AD cmdlets 的说明

本答案是围绕安装和可用于 远程服务器管理工具(RSAT) 的 Active Directory cmdlet 进行的。但是,Azure AD cmdlet 则利用 Microsoft Graph(OData v4.0 规范)来针对 Azure AD 运行查询,而 RSAT cmdlet[1] 则依赖于旨在替换 LDAP 过滤器的 PowerShell 表达式引擎的实现。

因此,下面的筛选器示例将无法与 Azure AD cmdlet 兼容,除非进行一些修改以符合 Microsoft Graph 规范,特别是其筛选语法。然而,这里提到的一般实践仍应适用。

[1] - 这是我能找到的最新版本的文档。

-Filter * 有什么问题?

你实际上是根据所使用的 cmdlet(例如 Get-ADUserGet-ADComputerGet-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进行全局匹配。

比较运算符

MS 将这些称为FilterOperators,但它们的用法与 PowerShell 的比较运算符相同(忽略了 技术上 -bor-band 是算术运算符的事实)。这些用于比较值:

注意:当使用-like-notlike时,DistinguishedName格式中的 AD 属性不会应用全局匹配,换句话说,您必须寻找精确匹配。如果需要使 DN 与任何模式匹配,则不能使用-Filter-LDAPFilter。您将需要在可能的情况下使用-Filter,并在Get-ADObject cmdlet 返回后使用-like-match 运算符进行额外处理。

-eq-le-ge-ne-lt-gt-approx-bor-band-recursivematch-like-notlike

仅有 -approx-recursivematch 两个运算符是唯一的 -Filter 查询语法所特有的。不要担心 -approx , 它在 Active Directory 中与 -eq 功能等效。

尽管名称中带有“匹配”(match),-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 是一个可分辨名称(Distinguished Names)数组-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返回32,000个用户,然后再应用其他过滤器。

Note: Most users will actually have Domain Users set as their PrimaryGroup. This is the default and most times this doesn't need to be changed. However, you must use
-Filter on PrimaryGroup instead as the PrimaryGroup is not stored under MemberOf for an ADUser. It is also a single value, not a collection, so use -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}*"'

在这种情况下,您将需要在两个地方使用双引号字符串,但幸运的是转义并不太糟糕。像之前一样使用$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中的筛选器示例和this AD Escape Characters帖子中的LDAP筛选器部分,因为-LDAPFilter超出了此答案的范围。

多属性匹配

多属性匹配并不复杂,但最好将每个条件用括号()括起来。以下是一个示例,假设我们通过用户名命名法*-da知道非域管理员帐户,并且这些帐户没有与之关联的电子邮件地址。

Get-ADUser -Filter "(samaccountname -notlike '*-da') -and (EmailAddress -notlike '*')"

这个有点棘手,因为我们不能在-Filter中传递一个空值作为条件的右侧,例如对于EmailAddress的情况。但是'*'匹配任何非空值,所以我们可以利用-notlike比较运算符的行为来查找EmailAddress的空值。为了分解过滤器,请确保任何以-da结尾的帐户都不会被过滤器匹配,然后也只匹配没有EmailAddress值的帐户。

需要避免的事情

  1. Don't try to use a { ScriptBlock } for your filter parameters. Yes, we are all more comfortable with writing a ScriptBlock than worrying about building a string and making sure it's escaped properly. There is definitely an attraction to using them. I've seen so many answers using a ScriptBlock as a -Filter argument, or people having problems (myself included) trying to do something like this, and SURPRISE!!! Nothing gets returned:

    Import-Csv C:\userInfoWithEmails.csv | Foreach-Object {
      Get-ADUser -Filter { EmailAddress -eq $_.Email }
    }
    

    -Filter doesn't support ScriptBlocks, but they Kind of Work Sometimes™ because while they get rendered as a literal string, the PowerShell Expression Engine used by -Filter is capable of rendering your variables before running the query. Because of this, they will technically work if you use simple variable expansion like $_ or $emailAddress, but it will eventually cause you a headache, especially if you try to access an object property (like above) because it simply won't work.

    In addition, you get largely undocumented (or difficult to locate info) behavior for how these variables are expanded, because they are not always ToString'd like you would expect. Figuring it out becomes a trial-and-error affair. Some attributes are admittedly easier to obtain this way, but using techniques you don't understand and which have little documentation is a risky move when programming. Because of this, I don't relying on the cmdlet-internal variable expansion that occurs whether you use a literal string or a ScriptBlock with the AD cmdlets.

    Use a string filter every time, and if you need to use a variable value or object property as a portion of the filter, use Variable Substitution or Command Substitution.

  2. You do not need to specify additional -Properties if you only care about a property to filter on it. The AD cmdlets can evaluate all properties within the -Filter parameter without needing to pass them down the pipeline.

  3. And while I'm at it, don't ever use -Properties *, excepting maybe if you are inspecting all properties on a returned object for some reason, such as during script development, or interactively you're not quite sure what you're looking for (note that not all attributes are returned by default).

    Only specify the properties you need to process after the AD object has been returned. There is a reason for this - some properties are particularly expensive to get the values for. Best practice is to only forward the properties you need to process down the pipeline.

  4. You cannot use the -Filter parameter to filter on Constructed Attributes using either
    -Filter or -LDAPFilter. This is because constructed attributes are, by definition, computed (or "constructed") on the fly, and are not actually stored values within Active Directory. I imagine it's because many Computed Attributes are expensive to compute, which would have to be performed on every relevant ADObject to filter on it from the AD side.

    If you need to filter on Constructed Attributes, you will need to first return a set of ADObjects, specifying the Computed Attribute with -Properties, then further filter with Where-Object or some other technique.

  5. Wildcards * do not work for fields that return a DistinguishedName type, such as DistinguishedName, manager, PrimaryGroup, etc. In addition, DistinguishedNames carry their own set of escaping rules.

  6. Some AD attributes are returned as a proper DateTime for easier processing in PowerShell, but the basic time comparison exemplified above requires the underlying ADAttribute to be defined as the Interval type. Some time-based properties, such as whenCreated are defined as Generalized-Time strings, which are UTC timezone and formatted as yyyMMddHHmmss.Z. Additionally, some properties like
    msDS-UserPasswordExpiryTimeComputed are in file-time format (and is returned as such with the AD cmdlets).

    • Convert a target DateTime for filtering to the Generalized-Time string format like so:

      ( Get-Date ).ToUniversalTime().ToString('yyyMMddHHmmss.z').
      

      Note that this string is not directly convertible back to a DateTime.

    • Convert a returned file-time to a DateTime like so (using the aforementioned property as an example):

      [DateTime]::FromFileTime($adUser.'msDS-UserPasswordExpiryTimeComputed')
      

总结

在迭代大型AD环境时,使用带有AD cmdlets的-Filter参数的这些技术将节省您宝贵的处理时间,并应该提高您的Powershell AD操作的性能。我希望这有助于解释AD cmdlets的-Filter参数的一些难以捉摸的行为。

额外资源

由于了解您正在使用的AD属性是一个好主意,因此以下是一些微软资源,可帮助您识别和了解不同属性在AD架构中如何定义和工作,以及了解更多关于-Filter语法的知识:


1
这简直让我的生活轻松太多了!!! - undefined
有关PowerShell运算符的更多信息,请参阅https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.3。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接