这是一个解决方案。根据您搜索的深度,它可能会非常慢;但是 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
。 Name
是输入对象的名称(通过函数的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 到匹配条件的成员的完整路径,并给出匹配的值。