Как искать в объекте значение?

Допустим, у вас есть гигантский объект, который может иметь или не иметь вложенных массивов / объектов,

# Assuming 'user1' exists in the current domain    
$obj = Get-ADUser 'user1' -Properties *

и я хочу найти в этом объекте строку SMTP без учета регистра ...

Что я пробовал

$obj | Select-String "SMTP"

Но это не работает, потому что совпадение находится внутри вложенной Collection ... чтобы быть кратким, оно находится внутри свойства $obj.proxyAddresses.

Если я запустил $obj.proxyAddress.GetType(), он вернет:

IsPublic IsSerial Name                      BaseType
-------- -------- ----                      --------
True     False    ADPropertyValueCollection System.Collections.CollectionBase

Как лучше всего это сделать? Я знаю, что вы можете перебирать свойства и искать их вручную, используя подстановочные знаки или .Contains(), но я бы предпочел встроенное решение.

Таким образом, это будет grep для объектов, а не только для строк.


person Kellen Stuart    schedule 02.10.2018    source источник
comment
Что вы хотите вернуть? Верно или неверно, родительский объект?   -  person ArcSet    schedule 02.10.2018
comment
@ArcSet Возможно, просто объект или строковое значение в зависимости от того, какой тип он найдет. Подобно тому, как grep находит одну строку и показывает только эту строку   -  person Kellen Stuart    schedule 03.10.2018


Ответы (2)


Вот одно решение. Это может быть очень медленным в зависимости от того, на какой глубине вы ищите; но глубина 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

Примечание. Этот ответ содержит общую информацию и предлагает быстрый и грязный подход, не требующий специальных функций.
Для более более тщательный и систематический подход, основанный на размышлении с помощью настраиваемой функции, см. Полезный ответ JohnLBevan.

Select-String работает с строками, и когда он приводит объект ввода другого типа к строке, он по существу вызывает .ToString() для него, что часто дает общие представления, такие как простое имя типа и обычно не перечисление свойств.
Обратите внимание, что представление объекта .ToString() не совпадает с выводом PowerShell по умолчанию на консоль, который намного богаче.

Если все, что вам нужно, - это найти подстроку в строковом представлении объекта for-display, вы можете передать по конвейеру Out-String -Stream перед тем, как передать по конвейеру Select-String:

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

Out-String создает строковое представление, такое же, как то, которое отображается в консоли по умолчанию (оно использует систему форматирования вывода PowerShell); добавление -Stream испускает это представление построчно, тогда как по умолчанию выводится однострочная строка.

Конечно, этот метод будет работать только в том случае, если представление для отображения действительно показывает интересующие данные - см. Предостережения ниже.

Предостережения:

person mklement0    schedule 02.10.2018