代码之家  ›  专栏  ›  技术社区  ›  Kellen Stuart Alan

如何在对象中搜索值?

  •  1
  • Kellen Stuart Alan  · 技术社区  · 6 年前

    假设你有一个巨大的物体,它可能有也可能没有嵌套的数组/物体,

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

    我想在那个对象中搜索字符串 SMTP 大小写不敏感…

    我试过什么

    $obj | Select-String "SMTP"
    

    但它不起作用,因为匹配项位于嵌套的 收藏 …简而言之,它位于酒店内部 $obj.proxyAddresses .

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

    IsPublic IsSerial Name                      BaseType
    -------- -------- ----                      --------
    True     False    ADPropertyValueCollection System.Collections.CollectionBase
    

    最好的方法是什么?我知道您可以循环访问这些属性,并使用通配符匹配或 .Contains() 但我更喜欢内置的解决方案。

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

    2 回复  |  直到 6 年前
        1
  •  2
  •   JohnLBevan    6 年前

    这里有一个解决方案。根据搜索深度的不同,搜索速度可能非常慢;但是对于您的场景,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 然后返回具有两个属性的新自定义对象; Name Value . 名字 是输入对象的名称(通过函数的 名字 参数) 价值 如您所料,是对象的值。如果 $输入对象 是一个数组,数组中的每个值都是单独评估的。传入的根对象的名称默认为 "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 Depth 参数。

    这个 如果 语句还检查对象的类型。也就是说,对于大多数基元类型,该类型保持值,而我们对它们的属性/方法不感兴趣(基元类型没有任何属性,但有各种方法,可以根据 $PropertyTypeToSearch )同样,如果我们正在寻找 -Condition {$_ -eq 6} 我们不希望所有长度为6的字符串;因此我们不希望深入到字符串的属性中。这个过滤器可能会进一步改进以帮助忽略其他类型/我们可以更改函数以提供另一个可选的脚本块参数(例如 $TypeCondition )允许调用者在运行时根据需要对其进行优化。

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

    一旦我们选择了要检查的输入对象的成员/属性,我们依次获取每个成员/属性,确保它们不为空,然后递归(即调用 查找值匹配条件 )在这个递归中,我们递减 $Depth 通过一个(即,由于我们已经下降了1级,我们将在0级停止),并将此成员的名称传递给函数的 名字 参数。

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

        2
  •  3
  •   mklement0    6 年前

    注释 :此答案包含 背景信息 并提供了一个 快速而肮脏的方法,不需要自定义功能 .
    为了更多 更彻底、更系统的方法 基于通过A的反射 自定义函数 JohnLBevan's helpful answer .

    Select-String 运行在 ,当它将不同类型的输入对象强制为字符串时,它实际上调用 .ToString() 在它上面,通常会生成泛型表示,例如类型名,通常 属性的枚举。
    请注意,对象的 toSTRIN() 代表是 与PowerShell对控制台的默认输出相同,后者要丰富得多。

    如果你只想 在中查找子字符串 用于显示 对象的字符串表示法 ,您可以通过管道 Out-String -Stream 管道铺设至 选择字符串 :

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

    Out-String 创建一个 一串 与呈现给 慰问 默认情况下(它使用PowerShell的输出格式化系统);添加 -Stream 发出这种表示 逐字逐句地 ,而默认情况下会发出一个多行字符串。

    当然, 只有当for-display表示实际显示感兴趣的数据时,此方法才有效。 -请参见下面的注意事项。

    告诫 :

    • 如果格式化的表示恰好是 表格的 你的搜索字符串是一个属性 名称 ,利息的价值可能是 下一个 线。

      • 你可以通过 强迫一 列表 -样式显示-每个属性占用其自己的一行(名称和值)- 如下:

        $obj | Format-List | Out-String -Stream | Select-String "SMTP"
        
      • 如果预期多行属性值,则可以 使用 选择字符串 -Context 要包含行的参数 周围的 火柴 ,如 -Context 0,1 同时输出线路 之后 一场比赛。

      • 如果你知道兴趣的价值在 集合值 财产 你可以 使用 $FormatEnumerationLimit = -1 强制列出 全部的 元素 (默认情况下,只显示前4个元素)。

        • 警告:从PowerShell Core 6.1.0开始, $FormatEnumerationLimit 只有在 全球的 范围-见 this GitHub issue .
        • 但是,一旦您点击需要设置参数变量 $formatEnumerationLimit格式 ,是时候考虑基于自定义函数的更彻底的解决方案了 John's answer .
    • 价值可能得到 截断的 在代表中,因为 出弦 假定行宽固定;可以使用 -Width 要改变这一点,但要小心使用大的数字,因为表格表示随后会对每个输出行使用全宽。