Вот одно из решений.Это может быть очень медленно в зависимости от глубины поиска;но глубина 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
Depth
.
Оператор If
также проверяет тип объекта, т. е. для большинства примитивных типов этот тип содержит значение, и мыне заинтересованы в их свойствах / методах (примитивные типы не имеют никаких свойств, но имеют различные методы, которые можно сканировать в зависимости от $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 для получения дополнительной информации о различных предоставляемых параметрах.
После того, как мы выбрали, какие элементы / свойства входного объекта мы хотим проверить, мы выбираем каждый по очереди, чтобы убедиться, что они 'не ноль, затем рекурсивный (т.е. звоните Find-ValueMatchingCondition
).В этой рекурсии мы уменьшаем $Depth
на единицу (то есть, поскольку мы уже опустились на 1 уровень и останавливаемся на уровне 0), и передаем имя этого члена параметру Name
функции.
Наконец, для любых возвращаемых значений (то есть пользовательских объектов, созданных частью 1 функции, как указано выше), мы добавляем $Name
нашего текущего InputObject к имени возвращаемого значения, а затем возвращаем этот исправленный объект.Это гарантирует, что каждый возвращаемый объект имеет Имя, представляющее полный путь от корневого InputObject до члена, соответствующего условию, и дает значение, которое соответствует.