为什么在使用基于括号的 -Filter 参数时 $PSItem 的行为不符合预期?

Why doesn't $PSItem behave as expected when using a bracket-based -Filter argument?

我正在协助用户解决这个问题,链接到我的回答:Powershell script to add users to A/D group from .csv using email address only?

最初我编写的脚本如下,对 Get-AdUser 使用基于括号的过滤器,如下所示:

Import-CSV "C:\users\Balbahagw\desktop\test1.csv" | 
  Foreach-Object {

    # Here, $_.EmailAddress refused to resolve
    $aduser = Get-ADUser -Filter { EmailAddress -eq $_.EmailAddress }

    if( $aduser ) {
      Write-Output "Adding user $($aduser.SamAccountName) to groupname"
      Add-ADGroupMember -Identity groupname -Members $aduser
    } else {
      Write-Warning "Could not find user in AD with email address $($_.EmailAddress)"
    }
  }

但是,$_.EmailAddress 无法填充值。但是,将 Get-ADUser 过滤器更改为基于字符串的过滤器会按预期工作:

$aduser = Get-ADUser -Filter "EmailAddress -eq '$($_.EmailAddress)'"

我遇到了什么奇怪的事情,为什么?是不是因为当我使用括号时,它被视为一个新的范围并且 $PSItem 不会跟随?

你没有看错,是模块的问题

您必须与 -Filter 参数一起使用的负载类型因您使用的提供商而异,这是一个非常令人困惑的设计决定!

Get-Help Get-ADUser -Parameter Filter 的输出为您提供了一些非常详细的示例,说明了可用于 Active Directory Provider 的过滤器语法实现的不同语法选项。

这是一个例子:

#To get all user objects that have an e-mail message attribute, use one of the following commands:

Get-ADUser -Filter {EmailAddress -like "*"}

似乎 ActiveDirectory 提供程序设置了特定限制,您必须将输入用引号引起来。下面是当我查找我的帐户时没有在我的电子邮件中加引号的情况。

Get-ADUser -Filter {EmailAddress -eq stephen@foxdeploy.com}
Get-ADUser : Error parsing query: 'EmailAddress -eq stephen@foxdeploy.com' 
Error Message: 'syntax error' at position: '18'.

但是添加引号?有效!

Get-ADUser -Filter {EmailAddress -eq "stephen@foxdeploy.com"}


DistinguishedName : CN=Stephen,CN=Users,DC=FoxDeploy,DC=local
Enabled           : True
GivenName         : Stephen
Name              : Stephen
ObjectClass       : user
ObjectGUID        : 6428ac3f-8d17-45d6-b615-9965acd9675b
SamAccountName    : Stephen
SID               : S-1-5-21-3818945699-900446794-3716848007-1103
Surname           : 
UserPrincipalName : Stephen@FoxDeploy.local

如何让你的工作发挥作用

现在,由于这个令人困惑的过滤器实现,您需要将第 5 行的用户查找更改为以下内容:

 $aduser = Get-ADUser -Filter "EmailAddress -eq `"$($_.EmailAddress)`""

我们以字符串形式提供 -Filter 负载。接下来我们要使用 String Expansion 来拉出 .EmailAddress 属性,所以我们将字符串包裹在 $( ) 中以表示字符串扩展。最后,提供者希望我们的过滤器比较用引号引起来,所以我们在它周围加上双引号,然后使用反引号字符转义引号。

现在应该可以了。

TLDR - 责怪供应商和模块,与 Active Directory 模块有太多的不一致。

  • -Filter参数一般为字符串参数(用
    验证 Get-Help Get-AdUser -Parameter Filter)

    • 他们通常 接受 PowerShell 代码 - 过滤器是 提供商特定的 并且经常有自己的语法,尽管在 AD cmdlet 的情况下恰好是 PowerShell-like
      此外,他们通常不了解 PowerShell 变量(见下文)。
  • 因此,当一个脚本块{ ... })被传递时,它被转换为字符串,计算其 文字 内容(开头 { 和结尾 } 之间的所有内容):

    • { EmailAddress -eq $_.EmailAddress }.ToString() 产生文字字符串 EmailAddress -eq $_.EmailAddress - 没有任何评估 - 这就是 Get-AdUser 看到的 - no进行评估。

    • 为了支持将脚本块传递给 AD cmdlet 的 -Filter 参数的广泛但不明智的做法,可能是出于善意但误入歧途,似乎这些 cmdlet实际上显式扩展 simple 变量引用,例如 $_ 在它们接收的字符串文字中,但这不适用于 expressions,例如作为访问变量 ($_.EmailAddress)

    • 属性

因此,-Filter 参数通常应作为 可扩展字符串 ("...") 传递;在手头的案例中:

 -Filter  "EmailAddress -eq '$($_.EmailAddress)'"

也就是说,唯一稳健的解决方案是使用 strings 和预先 baked in 的可变部分,通过字符串展开,如上图

对于既不是数字也不是字符串的值,例如 dates,您可能必须使用 literal字符串 ('...') 并依赖于 AD 提供商评估对 PowerShell 变量的简单引用的能力(例如,$date - 请参阅我的 this answer 了解详细信息。

如前所述,AD 过滤器的语法只是 PowerShell-like:它仅支持 PowerShell 支持的运算符的子集,而支持的运算符在行为上略有不同 -参见 Get-Help about_ActiveDirectory_Filter

  • 使用脚本块很诱人,因为里面的代码不需要转义嵌入引号/不需要交替引号字符,也不需要使用子表达式运算符$(...)。然而,除了使用脚本块作为字符串通常效率低下之外,这里的问题是 脚本块做出了它无法保持的承诺:它看起来 就像你正在传递一段 PowerShell 代码,但你不是 - 它只在简单的情况下有效(而且只是由于上面提到的错误调整);通常,很难记住它在什么情况下不能工作以及如果它失败了如何使它工作。

  • 因此,官方文档在其示例中使用脚本块真的很不幸。

有关更全面的讨论,请参阅我的 this answer