powershell 如何在对象中搜索值?

vbkedwbf  于 2022-11-10  发布在  Shell
关注(0)|答案(2)|浏览(140)

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


# 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,而不仅仅是字符串。

esbemjvw

esbemjvw1#

这里有一个解决方案。它可能非常慢,这取决于您搜索的深度;但深度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]::Properties)

    )
    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-JsonDepth参数的用途相同。
If语句还检查对象的类型。也就是说,对于大多数原始类型,该类型持有值,我们对它们的属性/方法不感兴趣(原始类型没有任何属性,但有各种方法,这些方法可能会根据$PropertyTypeToSearch进行扫描)。同样,如果我们要查找-Condition {$_ -eq 6},我们不会想要所有长度为6的字符串;因此,我们不想深入到字符串的属性。此筛选器可能会进一步改进以帮助忽略其他类型/我们可以更改该函数以提供另一个可选的脚本块参数(例如$TypeCondition),以允许调用者在运行时根据其需要进行优化。
在测试了是否要向下钻取此类型的成员之后,然后获取成员列表。在这里,我们可以使用$PropertyTypesToSearch参数来更改搜索内容。默认情况下,我们对Property类型的成员感兴趣;但我们可能只想扫描NoteProperty类型的成员;特别是在处理定制对象时。有关它提供的各种选项的更多信息,请参见https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.psmembertypes?view=powershellsdk-1.1.0
一旦我们选择了要检查的输入对象的成员/属性,我们将依次获取每个成员/属性,确保它们不为空,然后递归(即调用Find-ValueMatchingCondition)。在这个递归中,我们将$Depth递减1(即,因为我们已经下降了1级&我们在0级停止),并将该成员的名称传递给函数的Name参数。
最后,对于任何返回值(即由函数的第1部分创建的定制对象,如上所述),我们将当前InputObject的$Name作为返回值的名称,然后返回这个修改后的对象。这确保返回的每个对象都有一个名称,表示从根InputObject到匹配条件的成员的完整路径,并给出匹配的值。

jgwigjjp

jgwigjjp2#

:此答案包含背景信息,并提供了不需要自定义功能的快速下线方法**。

更多更彻底、更系统的方法基于自定义函数的反射,参见JohnLBevan's helpful answer
Select-String操作字符串,当它将不同类型的输入对象强制为字符串时,它实质上是对其调用.ToString(),这通常会产生泛型表示,如仅类型名称和通常的属性的枚举。
请注意,对象的.ToString()表示与PowerShell到控制台的默认输出不同,后者要丰富得多。
如果您要查找的只是对象的
for-Display*字符串表示中找到一个子字符串**,则可以先通过管道连接到Out-String -Stream,然后再通过管道连接到Select-String

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

Out-String创建的字符串表示与默认情况下呈现给控制台的内容相同(它使用PowerShell的输出格式化系统);添加-Stream会逐行发出该表示,而默认情况下会发出一个多行字符串。
注:最新版本的PowerShell附带便利函数oss,它
Package 了*Out-String -Stream

$obj | oss | Select-String "SMTP"

当然,此方法仅在显示模式实际显示感兴趣的数据时才起作用-请参阅下面的注意事项。
也就是说,**在显示模式中搜索可以说是Select-String默认应该做的事情--参见GitHub问题#10726

注意事项

  • 如果格式化表示恰好是表格,并且您的搜索字符串是属性名称,则感兴趣的值可能在*下一行。
  • 您可以通过**强制列表样式显示-其中每个属性占据自己的一行(名称和值)-**来解决这个问题-**如下所示:
$obj | Format-List | Out-String -Stream | Select-String "SMTP"
  • 如果您预期的是多行属性值,您可以使用Select-String-Context参数来包括包围匹配的行,例如-Context 0,1,以便也输出匹配之后的行。
  • 如果您知道感兴趣的值在集合值属性中,您可以使用$FormatEnumerationLimit = -1强制列出所有个元素**(默认情况下只显示前4个元素)。
  • 注意:从PowerShell Core 6.1.0开始,$FormatEnumerationLimit仅在设置在全局范围内时才有效-请参阅this GitHub issue
  • 但是,一旦您需要设置首选项变量$FormatEnumerationLimit,就可以考虑基于John's answer中的自定义函数的更全面的解决方案了。
  • 值在表示中可能会被截断,因为Out-String假定线宽是固定的;您可以使用-Width来更改这一点,但要小心处理大数字,因为表格表示会对每一个输出行使用全宽。

相关问题