如何有效地使用 Active Directory cmdlet 上的“-Filter"参数? [英] How to effectively use the `-Filter` parameter on Active Directory cmdlets?

查看:33
本文介绍了如何有效地使用 Active Directory cmdlet 上的“-Filter"参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常在此站点上看到以下类型的代码,特定于 AD cmdlet:

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

问题是您要返回 Active Directory 中的每个用户对象,然后再次对其进行处理.我们如何改进这一点,不仅要减少运行脚本所需的时间,还要减少 Active Directory 以及可能的网络不必要的负载?

解决方案

关于 Azure AD cmdlet 的注意事项

这个答案是围绕安装的 Active Directory cmdlet 精心设计的,可从 远程服务器管理工​​具 (RSAT) 获得.但是,Azure AD cmdlet 使用 Microsoft Graph(OData v4.0 规范)以运行针对 Azure AD 的查询,而 RSAT cmdlet[1] 依赖于旨在替换 LDAP 过滤器的 PowerShell 表达式引擎的实现.

因此,如果不进行一些修改以与 Microsoft Graph 规范兼容,特别是其 过滤语法.但是,此处提到的一般做法仍应适用.

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

-Filter * 有什么不好?

根据您使用的 cmdlet(例如 Get-ADUserGet-ADComputer),您有效地选择并返回 AD 中存在的每个对象Get-ADGroup、通用的 Get-ADObject 等).这是一件代价高昂的事情,尤其是在较大的 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<时不会应用通配符/code> 被使用,换句话说,你必须寻找一个完全匹配的.如果您需要 DN 来匹配任何模式,则不能使用 -Filter
-LDAPFilter 执行此操作.您将不得不在可能的地方 -Filter,并在 Get- 后使用 -like-match 运算符执行额外的处理ADObject cmdlet 返回.

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

-Filter 查询语法唯一的唯一的就是 -approx-recursivematch.不用担心-approx,它在功能上等价于-eq 在 Active Directory 中.

尽管有它的名字,-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 天内未登录的所有用户帐户怎么办?但是日期字符串比电子邮件更复杂!谁在乎,还是很简单的!

# 获取30天前的日期$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返回真包含右侧的值.

您也可以在这种情况下完全避免使用 Get-ADUser 并使用 Get-ADGroup 从中检索成员:

( Get-ADGroup group_name -Properties Members ).Members

虽然上面的 Get-ADGroup 示例较短,但是当您有多个时,使用 Get-ADUser 过滤 memberOf条件和需要返回用户,但不一定需要返回本地处理.它可能在交互上不方便,但在与 Active Directory 集成的任何自动化过程中都是一项有价值的技术,并且在您拥有非常大的组的情况下可能变得必要.

一个例子是在一个非常大的域中枚举域用户.您可能想重新考虑从 ( Get-ADGroup ).Members 返回 32,000 个用户,然后必须应用额外的过滤.

<块引用>

注意:大多数用户实际上会将域用户设置为他们的PrimaryGroup.这是默认设置,大多数情况下不需要更改.但是,您必须在 PrimaryGroup 上使用 -Filter,因为 PrimaryGroup 不存储在 MemberOf 下作为 >ADUser.它也是一个单一的值,而不是一个集合,所以使用 -eq:

Get-ADUser -Filter "PrimaryGroup -eq 'PRIMARY_GROUP_DN'";


如果查询词包含引号怎么办?

在大多数情况下,查询词中的引号会影响您的查询.考虑搜索名称中带有 O'Niel 的用户的示例.这可能会破坏查询或脚本逻辑,具体取决于所使用的引用技术:

# 我们的英雄搜索词$term = 奥尼尔"# 龙比比皆是(导致查询解析错误)Get-ADUser -Filter "Name -like '*${term}*'";# 你的公主在另一个城堡里($term 没有展开# 而是搜索文字字符串 ${term})Get-ADUser -Filter 'Name -like "*${term}*"'

在这种情况下,您将不得不在两个地方都使用双引号字符串,但幸运的是,转义地狱还不算太糟糕.使用与以前相同的 $term 值:

# 你的任务已经结束(这会按预期工作并返回名为 O'Niel 的用户)Get-ADUser -Filter "Name -like ""*${term}*"""";# 反引号很难看,但这也有效Get-ADUser -Filter "Name -like `"*${term}*`""

<块引用>

注意:如果您的查询查找包含单引号和双引号的字段值,我不确定在使用-Filter 参数.但是,-LDAPFilter 应该能够促进这一点,因为括号 () 而不是引号用于内部查询边界.请参阅过滤器示例"nofollow noreferrer">about_ActiveDirectory_Filterthis AD Escape Characters 帖子以获取更多信息,因为 -LDAPFilter 超出了本答案的范围.


匹配多个属性

匹配多个属性没有太大区别,但最好将每个条件用括号括起来().这是一个示例,让我们查找没有关联电子邮件地址的非域管理员帐户(假设我们通过用户名命名法*-da 知道这一点).

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

这个有点棘手,因为我们不能在 -Filter 中为条件的右侧传入一个空值,就像 EmailAddress.但是 '*' 匹配任何非空值,因此我们可以利用 -notlike 比较运算符利用该行为来查找 EmailAddress 的空值.要分解过滤器,请确保任何以 -da 结尾的帐户都不会被过滤器匹配,然后也只匹配没有 的帐户>EmailAddress 值.


要避免的事情

  1. 不要尝试使用 { ScriptBlock } 作为过滤器参数.是的,我们更愿意编写 ScriptBlock,而不是担心构建 string 并确保它正确转义.使用它们绝对有吸引力.我已经看到很多使用 ScriptBlock 作为 -Filter 参数的答案,或者有问题的人(包括我自己)试图做这样的事情,而且很惊喜!!!什么都没有返回:

    导入-Csv C:userInfoWithEmails.csv |Foreach-对象{Get-ADUser -Filter { EmailAddress -eq $_.Email }}

    -Filter 不支持 ScriptBlocks,但它们Kind of Work Some™ 因为当它们被呈现为文字字符串时,-Filter 使用的 PowerShell 表达式引擎能够在运行查询之前呈现您的变量.正因为如此,如果你使用像 $_$emailAddress 这样的简单变量扩展,它们在技术上是可行的,但它最终会让你头疼,特别是如果你试图访问一个对象属性(如上),因为它根本不起作用.

    此外,对于这些变量的扩展方式,您会获得大量未记录(或难以找到信息)的行为,因为它们并不总是 ToString'd 如您所愿.弄清楚它变成了一个反复试验的事情.诚然,某些属性通过这种方式更容易获得,但是在编程时使用您不理解且几乎没有文档的技术是一个冒险的举动.因此,我不依赖于 AD cmdlet 使用文字字符串或 ScriptBlock 时发生的 cmdlet 内部变量扩展.

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

  2. 如果您只关心要过滤的属性,则无需指定额外的 -Properties.AD cmdlet 可以评估 -Filter 参数中的所有属性,而无需将它们传递到管道中.

  3. 虽然我在做,但永远不要使用 -Properties *,除非您出于某种原因检查返回对象的所有属性,例如在脚本期间开发,或者交互式地您不太确定您在寻找什么(请注意并非所有属性都默认返回).

    仅指定AD对象返回后需要处理的属性.这是有原因的 - 某些属性获取值的成本特别高.最佳做法是仅转发您需要在管道中处理的属性.

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

    如果你需要过滤构造属性,你需要首先返回一组ADObjects,用-Properties指定计算属性>,然后使用 Where-Object 或其他一些技术进一步过滤.

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

  6. 某些 AD 属性作为适当的 DateTime 返回,以便在 PowerShell 中进行更轻松的处理,但上面举例说明的基本时间比较需要将基础 ADAttribute 定义为Interval 类型.一些基于时间的属性,例如 whenCreated 被定义为 Generalized-Time 字符串,它们是 UTC 时区,格式为 yyyMMddHHmmss.Z.此外,msDS-UserPasswordExpiryTimeComputed 等一些属性采用文件时间格式(并通过 AD cmdlet 返回).

    • 将用于过滤的目标 DateTime 转换为 Generalized-Time string 格式,如下所示:

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

      请注意,此字符串不能直接转换回 DateTime.

    • 将返回的 file-time 转换为 DateTime 像这样(以上述属性为例):

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


总结

-Filter 参数与 AD cmdlet 一起使用的这些技术将在迭代大型 AD 环境时为您节省成本高昂的处理时间,并且应该会提高 Powershell AD 操作的性能.我希望这有助于解释 AD cmdlet 的 -Filter 参数的一些难以捉摸的行为.

其他资源

了解您正在使用的 AD 属性是一个好主意,以下是一些 Microsoft 资源,可帮助您识别和了解不同属性在 AD 架构中的定义和功能,以及了解有关 -Filter 语法:

All too often I see the following type of code on this site, specific to the AD cmdlets:

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

The problem is that you are returning every single user object in Active Directory, and then processing it a second time. How can we improve upon this, not only to reduce the time it takes to run the script, but to also take the unnecessary load off of Active Directory, and possibly the network?

解决方案

Note about Azure AD cmdlets

This answer is crafted around the Active Directory cmdlets installed and available from Remote Server Administration Tools (RSAT). However, the Azure AD cmdlets make use of Microsoft Graph (OData v4.0 specification) to run queries against Azure AD while the RSAT cmdlets[1] rely on an implementation of the PowerShell Expression Engine intended to replace LDAP filters.

As such, the filter examples below will not work with Azure AD cmdlets without some modification to be compatible with the Microsoft Graph specification, specifically, its filter syntax. However, the general practices mentioned here should still apply.

[1] - This is the most recent version of this document I could find.

What is so bad about -Filter *?

You are effectively selecting and returning every object that exists in AD, based on the cmdlet you are using (e.g. Get-ADUser, Get-ADComputer, Get-ADGroup, the generic Get-ADObject, etc.). This is an expensive thing to do, especially in larger AD environments. It is fine to do this if you legitimately need to operate on every possible object, but in most cases you do not need to return everything. On top of this, your script will end up processing far more data than it needs to, increasing execution time and used processing time when it just isn't necessary.

The -Filter parameter can do more than just match on everything, which is effectively what
-Filter * does. The -Filter string is very much like Powershell syntax (not quite, but most of the way there). You can use most of the same logical operators that Powershell supports, and they work much in the same way that Powershell operators do. This answer aims to clarify this and explain how to use this elusive parameter. These examples will use the Get-ADUser cmdlets but this also extends to the other Get-ADObject cmdlets which use filters as well.


Syntax

The syntax for the -Filter string is "PropertyName -comparisonoperator 'somevalue'", though you can string multiple conditions together with logical operators such as -and and -or. Note that there are no regex matching operators, so you will have to make do with -like and -notlike globbing.

Comparison Operators

MS calls these FilterOperators but they are used in the same way as PowerShell's comparison operators are (ignoring the fact that technically -bor and -band are arithmetic operators). These are used for comparing values:

Note: AD attributes in DistinguishedName format will not have globbing applied when
-like or -notlike are used, in other words you have to look for an exact match. If you need a DN to match any pattern, this cannot be performed with -Filter or
-LDAPFilter. You will have to -Filter where you can, and perform additional processing with the -like or -match operators once your Get-ADObject cmdlet returns.

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

The only ones which are unique to the -Filter query syntax are -approx and -recursivematch. Don't worry about -approx, it is functionally equivalent to -eq in Active Directory.

Despite its name, -recursivematch is not a regex matching operator, it works like PowerShell's
-contains operator in that it will return $true if the collection contains the target value.

Logical Operators

MS calls these JoinOperators but they fill the same role as their PowerShell logical operator equivalent. These are used to join multiple conditions together in a single query:

-and, -or

Strangely enough, MS gives negation to a special operator type called NotOperator, which consists of a single operator:

-not


Matching on a property

To use the example in the question, let's find a user matching an email address, but without piping to Where-Object (crazy right???):

$email = 'box@domain.tld'
Get-ADUser -Filter "EmailAddress -eq '${email}'"

Done. Get-ADUser will return any accounts where the EmailAddress property equals whatever the $email variable is.

What if we want to find all user accounts that haven't been logged onto in the last 30 days? But a date string is more complex than an email! Who cares, still pretty simple!

# Get the date from 30 days ago

$notUsedSince = ( Get-Date ).AddDays( -30 )
Get-ADUser -Filter "LastLogonDate -lt '${notUsedSince}'"

This returns all users who have not logged on in the last 30 days.


Getting users who are members of a group

If you want to get all ADUsers who are members of a certain group, we can make use of the
-recursivematch operator for this:

Get-ADUser -Filter "memberOf -recursivematch 'CN=test_group,CN=Users,DC=exampledomain,DC=net'"

memberOf is an array of Distinguished Names, -recursivematch returns true if the array on the lefthand side contains the value on the righthand side.

You can alternatively avoid the use of Get-ADUser at all in this scenario and use Get-ADGroup to retrieve the members from that:

( Get-ADGroup group_name -Properties Members ).Members

While the Get-ADGroup example above is shorter to type, filtering on memberOf with Get-ADUser can be effective when you have multiple conditions and need to return users that , but not necessarily need to return it for local processing. It may be interactively inconvenient but it is a worthy technique in any automated process integrating with Active Directory and may become necessary in cases where you have extremely large groups.

One example is when enumerating Domain Users in a very large domain. You might want to rethink returning 32,000 users from ( Get-ADGroup ).Members to then have to apply additional filtering.

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'"


What if the query term contains a quote?

Quotes in the query term will throw a wrench in your query in most cases. Consider the example of searching for users with O'Niel in the name. This can break either the query or your script logic depending on the quoting technique used:

# 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}*"'

In this case you will have to use double-quoted strings in both places, but fortunately the escape-hell isn't too bad. Using the same value for $term as before:

# 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}*`""

Note: If your query looks for a field value which contains both single and double quotes, I'm not sure how to facilitate this with one command when using the -Filter parameter. However, -LDAPFilter should be able to facilitate this, as parentheses (), not quotes, are used for the internal query bounds. See the Filter Examples in about_ActiveDirectory_Filter and the LDAP Filters section of this AD Escape Characters post for more information, as -LDAPFilter is beyond the scope of this answer.


Matching on multiple properties

Matching on multiple properties is not much different, but it's best to wrap each condition in parentheses (). Here's an example, let's find non-domain admin accounts (assuming we know this by the username nomenclature *-da) that don't have an email address associated with them.

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

This one is a little trickier, because we can't pass in an empty value for the right side of a condition in the -Filter, as in the case for EmailAddress. But '*' matches any non-empty value, so we can leverage that behavior with the -notlike comparison operator to find empty values for EmailAddress. To break down the filter, make sure that any accounts ending in -da aren't matched by the filter, and then also only match accounts that do not have an EmailAddress value.


Things to avoid

  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')
      


In summarium

These techniques using the -Filter parameter with the AD cmdlets will save you costly processing time when iterating over large AD environments, and should improve the performance of your Powershell AD operations. I hope this helps explain some of the elusive behaviors of the AD cmdlets' -Filter parameter.

Additional Resources

As it is a good idea to understand the AD attributes you are working with, below are some Microsoft resources to help you identify and understand how different attributes are defined and function within the AD schema, as well as learn more about the -Filter syntax:

这篇关于如何有效地使用 Active Directory cmdlet 上的“-Filter"参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆