如何在对象中搜索值?

时间:2018-10-02 19:07:37

标签: powershell object properties powershell-v5.1

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

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

并且我要不区分大小写地在该对象中搜索字符串SMTP ...

我尝试过的事情

$obj | Select-String "SMTP"

但是它不起作用,因为匹配项位于嵌套的 Collection ...中,简而言之,它位于属性$obj.proxyAddresses中。

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

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

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

因此,它不仅是字符串,而且是对象的grep

2 个答案:

答案 0 :(得分:3)

注意:此答案包含背景信息,并提供了一种不需要自定义功能的快捷方法。< br /> 有关基于自定义功能进行反思的更更彻底,系统的方法,请参见JohnLBevan's helpful answer

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

如果您要查找的是在对象的显示字符串表示中找到子字符串,则可以在传递给Out-String -Stream之前输送到Select-String

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

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

当然,仅当用于展示的表示实际显示了感兴趣的数据时,此方法才有效-请参见以下注意事项。

注意事项

  • 如果格式表示形式恰好是表格,并且您的搜索字符串是属性 name ,则感兴趣的值可能在 next < / em>行。

    • 您可以通过以下方式解决此问题:强制使用 list 样式的显示-每个属性都占据一行内容(名称和值)-如下:

      $obj | Format-List | Out-String -Stream | Select-String "SMTP"
      
    • 如果您期望多行属性值,则可以使用Select-String的{​​{1}}参数以包含环绕匹配项的行,例如-Context,也将在匹配后的行输出

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

      • 注意事项:从PowerShell Core 6.1.0开始,$FormatEnumerationLimit = -1仅在 global 范围内设置时有效-请参见this GitHub issue
      • 但是,一旦遇到需要设置首选项变量$FormatEnumerationLimit的时候,就该考虑基于John's answer中的自定义函数的更彻底的解决方案了。
  • 表示中的值可能会被截断 ,因为$FormatEnumerationLimit假定行宽是固定的;您可以使用Out-String进行更改,但是要小心,因为表格表示形式会在每条输出行中使用完整宽度。

答案 1 :(得分:2)

这是一个解决方案。取决于您搜索的深度,它可能会非常慢。但是深度为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的{​​{1}}参数本质上是相同的目的。

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

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

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

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