Как искать объект по значению? - PullRequest
0 голосов
/ 02 октября 2018

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

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

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

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

$obj | Select-String "SMTP"

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

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

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

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

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

Ответы [ 2 ]

0 голосов
/ 03 октября 2018

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

0 голосов
/ 02 октября 2018

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

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

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

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

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

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

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

  • Если отформатированное представление оказывается tabular , и ваша строка поиска является свойством name , интересующее значение может быть в строке next .

    • Вы можете решить эту проблему путем принудительного отображения списка -стилей - где каждое свойство занимает собственную строку (как имя, так и значение) - следующим образом:

      $obj | Format-List | Out-String -Stream | Select-String "SMTP"
      
    • Если вы ожидаете многострочные значения свойств, вы можете использовать Select-String параметр -Context для включения строк , окружающих совпадение , напримеркак -Context 0,1 для вывода строки после совпадения.

    • Если вы знаете, что значения oЕсли вы заинтересованы в коллекционном свойстве , вы можете использовать $FormatEnumerationLimit = -1 для принудительного перечисления всех элементов (по умолчаниюотображаются только первые 4 элемента).

      • Предупреждение: Начиная с PowerShell Core 6.1.0, $FormatEnumerationLimit действует только в том случае, если задано в области global - см. эта проблема GitHub .
      • Однако, как только вы столкнулись с необходимостью установить переменную предпочтения $FormatEnumerationLimit, пришло время рассмотреть более тщательное решение на основе пользовательской функции в Ответ Джона .
  • Значения могут получить усеченные в представлении, поскольку Out-String предполагает фиксированную ширину линии;Вы можете использовать -Width, чтобы изменить это, но будьте осторожны с большими числами, потому что табличные представления затем используют полную ширину для каждой строки вывода.

...