1

假设你有一个巨大的对象——一个可能有也可能没有嵌套数组/对象的对象,

# Assuming 'user1' exists in the current domain    
$obj = Get-ADUser 'user1' -Properties *

我想SMTP不区分大小写地搜索该对象的字符串...

我试过的

$obj | Select-String "SMTP"

但它不起作用,因为匹配在嵌套的Collection内......简而言之,它位于 property 内$obj.proxyAddresses

如果我运行$obj.proxyAddress.GetType()它返回:

IsPublic IsSerial 名称 BaseType
-------- -------- ---- --------
真 假 ADPropertyValueCollection System.Collections.CollectionBase

解决这个问题的最佳方法是什么?我知道您可以遍历属性并使用通配符匹配手动查找它.Contains(),但我更喜欢内置解决方案。

因此,它将是grep对象而不仅仅是字符串。

4

2 回答 2

4

注意:此答案包含背景信息,并提供了一种不需要自定义功能的快速而简单的方法
有关通过自定义函数基于反射的更彻底、更系统的方法,请参阅JohnLBevan 的有用答案

Select-String字符串进行操作,当它强制将不同类型的输入对象转换为字符串时,它实际上会调用.ToString()它,这通常会产生通用表示,例如单纯的类型名称,通常不是属性的枚举。
请注意,对象的.ToString()表示形式PowerShell 到控制台的默认输出不同,后者要丰富得多。

如果您要查找的只是在对象的显示字符串表示中找到子字符串,则可以在管道到Out-String -Stream之前先管道到Select-String

$obj | Out-String -Stream | Select-String "SMTP"

Out-String创建一个与默认情况下呈现给控制台的字符串表示形式相同(它使用 PowerShell 的输出格式系统);添加逐行发出该表示,而默认情况下发出单个多行字符串。-Stream

注意:最近版本的 PowerShell 带有便利函数oss,它包含 Out-String -Stream

$obj | oss | Select-String "SMTP"

当然,这种方法只有在用于显示的表示实际上显示了感兴趣的数据时才有效- 请参阅下面的警告。

也就是说,在显示表示中搜索可以说是默认情况下Select-String应该执行的操作- 请参阅GitHub 问题 #10726

注意事项

  • 如果格式化的表示恰好是表格并且您的搜索字符串是属性名称,则感兴趣的值可能在下一行。

    • 您可以通过强制列表样式显示来解决此问题 - 其中每个属性占用自己的一行(名称和值) -如下所示:

       $obj | Format-List | Out-String -Stream | Select-String "SMTP"
      
    • 如果您预期多行属性值,您可以使用Select-String's-Context参数来包含围绕match的行,例如在匹配-Context 0,1也输出该行。

    • 如果您知道感兴趣的值在集合值属性中,则可以使用$FormatEnumerationLimit = -1强制列出所有元素(默认情况下,仅显示前 4 个元素)。

      • 警告:从 PowerShell Core 6.1.0 开始,$FormatEnumerationLimit只有在全局范围内设置才有效 - 请参阅此 GitHub 问题
      • 但是,一旦您需要设置首选项变量$FormatEnumerationLimit,就该考虑基于John's answer中的自定义函数的更彻底的解决方案了。
  • 表示中的值可能会被截断,因为Out-String假定线宽是固定的;您可以使用它-Width来更改它,但要小心大数字,因为表格表示然后使用每个输出行的全宽。

于 2018-10-02T20:28:50.037 回答
3

这是一个解决方案。根据您搜索的深度,它可能会非常慢;但是 1 或 2 的深度非常适合您的场景:

function Find-ValueMatchingCondition {
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject]$InputObject
        ,
        [Parameter(Mandatory = $true)]
        [ScriptBlock]$Condition
        ,
        [Parameter()]
        [Int]$Depth = 10
        ,
        [Parameter()]
        [string]$Name = 'InputObject'
        ,
        [Parameter()]
        [System.Management.Automation.PSMemberTypes]$PropertyTypesToSearch = ([System.Management.Automation.PSMemberTypes]::Property)

    )
    Process {
        if ($InputObject -ne $null) {
            if ($InputObject | Where-Object -FilterScript $Condition) {
                New-Object -TypeName 'PSObject' -Property @{Name=$Name;Value=$InputObject}
            }
            #also test children (regardless of whether we've found a match
            if (($Depth -gt 0)  -and -not ($InputObject.GetType().IsPrimitive -or ($InputObject -is 'System.String'))) {
                [string[]]$members = Get-Member -InputObject $InputObject -MemberType $PropertyTypesToSearch | Select-Object -ExpandProperty Name
                ForEach ($member in $members) {
                    $InputObject."$member" | Where-Object {$_ -ne $null} | Find-ValueMatchingCondition -Condition $Condition -Depth ($Depth - 1) -Name $member | ForEach-Object {$_.Name = ('{0}.{1}' -f $Name, $_.Name);$_}
                }
            }
        }
    }
}
Get-AdUser $env:username -Properties * `
    | Find-ValueMatchingCondition -Condition {$_ -like '*SMTP*'} -Depth 2

示例结果:

Value                                           Name                                  
-----                                           ----                                  
smtp:SomeOne@myCompany.com                      InputObject.msExchShadowProxyAddresses
SMTP:some.one@myCompany.co.uk                   InputObject.msExchShadowProxyAddresses
smtp:username@myCompany.com                     InputObject.msExchShadowProxyAddresses
smtp:some.one@myCompany.mail.onmicrosoft.com    InputObject.msExchShadowProxyAddresses    
smtp:SomeOne@myCompany.com                      InputObject.proxyAddresses  
SMTP:some.one@myCompany.co.uk                   InputObject.proxyAddresses  
smtp:username@myCompany.com                     InputObject.proxyAddresses  
smtp:some.one@myCompany.mail.onmicrosoft.com    InputObject.proxyAddresses     
SMTP:some.one@myCompany.mail.onmicrosoft.com    InputObject.targetAddress  

解释

Find-ValueMatchingCondition是一个函数,它接受一个给定的对象 ( InputObject) 并根据给定的条件递归地测试它的每个属性。

该功能分为两部分。第一部分是根据条件测试输入对象本身:

if ($InputObject | Where-Object -FilterScript $Condition) {
    New-Object -TypeName 'PSObject' -Property @{Name=$Name;Value=$InputObject}
}

这就是说,如果$InputObject与给定的值匹配,$Condition则返回一个具有两个属性的新自定义对象;NameValueName是输入对象的名称(通过函数的Name参数传递),并且Value如您所料,是对象的值。如果$InputObject是一个数组,则单独评估数组中的每个值。传入的根对象的名称默认为"InputObject"; 但是您可以在调用函数时将此值覆盖为您喜欢的任何值。

函数的第二部分是我们处理递归的地方:

if (($Depth -gt 0)  -and -not ($InputObject.GetType().IsPrimitive -or ($InputObject -is 'System.String'))) {
    [string[]]$members = Get-Member -InputObject $InputObject -MemberType $PropertyTypesToSearch | Select-Object -ExpandProperty Name
    ForEach ($member in $members) {
        $InputObject."$member" | Where-Object {$_ -ne $null} | Find-ValueMatchingCondition -Condition $Condition -Depth ($Depth - 1) -Name $member | ForEach-Object {$_.Name = ('{0}.{1}' -f $Name, $_.Name);$_}
    }
}

If语句检查我们对原始对象的深入程度(即,由于每个对象的属性都可能有自己的属性,达到潜在的无限级别(因为属性可能指向父对象),最好限制多深我们可以走了。这和ConvertTo-Json'sDepth参数的目的本质上是一样的。

If语句还检查对象的类型。即对于大多数原始类型,该类型保存值,并且我们对它们的属性/方法不感兴趣(原始类型没有任何属性,但确实有各种方法,可能会根据 进行扫描$PropertyTypeToSearch)。同样,如果我们正在寻找,-Condition {$_ -eq 6}我们不会想要所有长度为 6 的字符串;所以我们不想深入研究字符串的属性。此过滤器可能会进一步改进以帮助忽略其他类型/我们可以更改函数以提供另一个可选的脚本块参数(例如$TypeCondition),以允许调用者在运行时根据他们的需要对其进行细化。

在我们测试了是否要深入了解该类型的成员之后,我们然后获取成员列表。在这里,我们可以使用$PropertyTypesToSearch参数来更改我们搜索的内容。默认情况下,我们对 type 的成员感兴趣Property;但我们可能只想扫描 type NoteProperty;特别是在处理自定义对象时。有关此提供的各种选项的更多信息,请参阅https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.psmembertypes?view=powershellsdk-1.1.0 。

一旦我们选择了我们希望检查的输入对象的成员/属性,我们依次获取每个成员/属性,确保它们不为空,然后递归(即调用Find-ValueMatchingCondition)。在这个递归中,我们减$Depth一(即因为我们已经下降了 1 级并且我们停止在 0 级),并将该成员的名称传递给函数的Name参数。

最后,对于任何返回值(即由函数的第 1 部分创建的自定义对象,如上所述),我们将$Name当前 InputObject 的名称添加到返回值的名称中,然后返回这个修改后的对象。这确保返回的每个对象都有一个 Name 表示从根 InputObject 到匹配条件的成员的完整路径,并给出匹配的值。

于 2018-10-02T21:34:39.940 回答