这里有一个解决方案。根据搜索深度的不同,搜索速度可能非常慢;但是对于您的场景,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到匹配条件的成员的完整路径,并给出匹配的值。