Вот одно решение. Это может быть очень медленным в зависимости от того, на какой глубине вы ищите; но глубина 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:[email protected] InputObject.msExchShadowProxyAddresses
SMTP:[email protected] InputObject.msExchShadowProxyAddresses
smtp:[email protected] InputObject.msExchShadowProxyAddresses
smtp:[email protected] InputObject.msExchShadowProxyAddresses
smtp:[email protected] InputObject.proxyAddresses
SMTP:[email protected] InputObject.proxyAddresses
smtp:[email protected] InputObject.proxyAddresses
smtp:[email protected] InputObject.proxyAddresses
SMTP:[email protected] 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
проверяет, насколько глубоко мы вошли в исходный объект (т. Е. Поскольку каждое из свойств объекта может иметь свои собственные свойства до потенциально бесконечного уровня (поскольку свойства могут указывать на родителя), лучше всего ограничить, как мы можем пойти глубже. Это, по сути, та же цель, что и параметр Depth
ConvertTo-Json
.
Оператор 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 до члена, соответствующего условию, и дает соответствующее значение.
person
JohnLBevan
schedule
02.10.2018
grep
находит одну строку и показывает только эту строку - person Kellen Stuart   schedule 03.10.2018