Поделиться через


Все, что вы хотели знать о PSCustomObject

PSCustomObject — это отличный инструмент, который стоит добавить в ваш инструментарий PowerShell. Начнем с основных принципов и работаем над более сложными функциями. Идея использования PSCustomObject — простой способ создания структурированных данных. Посмотрите на первый пример, и вы получите лучшее представление о том, что это означает.

Примечание.

Оригинальная версия этой статьи появилась в блоге, написанном @KevinMarquette. Команда PowerShell благодарит Кевина за предоставление этого содержимого нам. Пожалуйста, ознакомьтесь с его блогом на PowerShellExplained.com.

Создание PSCustomObject

Я люблю использовать [pscustomobject] в PowerShell. Создание удобного объекта никогда не было проще. Из-за этого я собираюсь пропустить все другие способы создания объекта, но мне нужно упомянуть, что большинство из этих примеров — PowerShell версии 3.0 и более новые.

$myObject = [pscustomobject]@{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

Этот метод хорошо подходит для меня, потому что я использую хеш-таблицы почти для всего. Но есть случаи, когда мне хотелось бы, чтобы PowerShell обрабатывал хэш-таблицы больше как объекты. Первое, на что вы обращаете внимание, это когда вы хотите использовать Format-Table или Export-Csv и понимаете, что хэш-таблица — это просто коллекция пар "ключ-значение".

Затем можно получить доступ к значениям и использовать их так же, как обычный объект.

$myObject.Name

Преобразование хеш-таблицы

Кстати, знали ли вы, что вы можете сделать это:

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}
$myObject = [pscustomobject]$myHashtable

Я предпочитаю создавать объект с самого начала, но бывают случаи, когда необходимо сначала работать с хеш-таблицей. Этот пример работает, так как конструктор принимает хэш-файл для свойств объекта. Важно отметить, что в то время как этот метод работает, он не является точным эквивалентом. Самое большое различие заключается в том, что порядок свойств не сохраняется.

Если вы хотите сохранить порядок, см. упорядоченные хэш-таблицы.

Устаревший подход

Возможно, вы могли видеть, как люди используют New-Object для создания пользовательских объектов.

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

$myObject = New-Object -TypeName psobject -Property $myHashtable

Это довольно медленнее, но это может быть ваш лучший вариант в ранних версиях PowerShell.

Сохранение в файл

Я считаю, что лучший способ сохранить хэш-таблицу в файл — это сохранить её в формате JSON. Его можно импортировать обратно в [pscustomobject]

$myObject | ConvertTo-Json -Depth 1 | Set-Content -Path $Path
$myObject = Get-Content -Path $Path | ConvertFrom-Json

Я охватываю больше способов сохранения объектов в файл в моей статье по Многие способы чтения и записи в файлы.

Работа со свойствами

Добавление свойств

Вы по-прежнему можете добавить новые свойства в PSCustomObject с помощью Add-Member.

$myObject | Add-Member -MemberType NoteProperty -Name 'ID' -Value 'KevinMarquette'

$myObject.ID

Удалить свойства

Вы также можете удалить свойства из объекта.

$myObject.psobject.Properties.Remove('ID')

.psobject — это встроенный элемент, который предоставляет доступ к метаданным базового объекта. Дополнительные сведения о встроенных элементах см. в about_Intrinsic_Members.

Перечисление имен свойств

Иногда вам нужен список всех имен свойств объекта.

$myObject | Get-Member -MemberType NoteProperty | select -ExpandProperty Name

Мы также можем получить этот же список из свойства psobject.

$myobject.psobject.Properties.Name

Примечание.

Get-Member возвращает свойства в алфавитном порядке. С помощью оператора доступа к члену для перечисления имен свойств возвращаются свойства в том порядке, в котором они определены на объекте.

Динамический доступ к свойствам

Я уже упомянул, что вы можете напрямую получить доступ к значениям свойств.

$myObject.Name

Можно использовать строку для имени свойства, и она по-прежнему будет работать.

$myObject.'Name'

Мы можем выполнить этот еще один шаг и использовать переменную для имени свойства.

$property = 'Name'
$myObject.$property

Я знаю, что выглядит странно, но это работает.

Преобразование PSCustomObject в хеш-таблицу

Чтобы продолжить с последним разделом, вы можете динамически перебирать свойства и создавать хеш-таблицу из них.

$hashtable = @{}
foreach( $property in $myobject.psobject.Properties.Name )
{
    $hashtable[$property] = $myObject.$property
}

Тестирование свойств

Если необходимо знать, существует ли свойство, можно просто проверить наличие этого свойства.

if( $null -ne $myObject.ID )

Но если значение может быть $null вы можете проверить, существует ли оно, проверив psobject.Properties для него.

if( $myobject.psobject.Properties.Match('ID').Count )

Добавление методов объекта

Если необходимо добавить метод скрипта в объект, его можно сделать с помощью Add-Member и ScriptBlock. Вам нужно использовать автоматическую переменную this для ссылки на текущий объект. Вот scriptblock, чтобы превратить объект в хеш-таблицу. (тот же код формирует последний пример)

$ScriptBlock = {
    $hashtable = @{}
    foreach( $property in $this.psobject.Properties.Name )
    {
        $hashtable[$property] = $this.$property
    }
    return $hashtable
}

Затем мы добавим его в объект в качестве свойства скрипта.

$memberParam = @{
    MemberType = "ScriptMethod"
    InputObject = $myobject
    Name = "ToHashtable"
    Value = $scriptBlock
}
Add-Member @memberParam

Затем мы можем вызвать эту функцию следующим образом:

$myObject.ToHashtable()

Объекты и типы значений

Объекты и типы значений не обрабатывают назначения переменных так же. При назначении типов значений друг другу только значение копируется в новую переменную.

$first = 1
$second = $first
$second = 2

В этом случае $first равно 1, а $second — 2.

Переменные объекта содержат ссылку на фактический объект. При назначении одного объекта новой переменной они по-прежнему ссылаются на тот же объект.

$third = [pscustomobject]@{Key=3}
$fourth = $third
$fourth.Key = 4

Так как $third и $fourth ссылаются на один и тот же экземпляр объекта, $third.key и $fourth.Key равны 4.

psobject. Copy()

Если вам нужна истинная копия объекта, ее можно клонировать.

$third = [pscustomobject]@{Key=3}
$fourth = $third.psobject.Copy()
$fourth.Key = 4

Клон создает неглубокую копию объекта. В этом примере они имеют разные экземпляры, а $third.key — 3, а $fourth.Key — 4.

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

PSTypeName для пользовательских типов объектов

Теперь, когда у нас есть объект, есть несколько других вещей, которые мы можем сделать с ним, что может быть не так очевидно. Первое, что нам нужно сделать, это дать ему PSTypeName. Вот как это чаще всего делают люди, по моим наблюдениям:

$myObject.psobject.TypeNames.Insert(0,"My.Object")

Недавно я обнаружил еще один способ сделать это от пользователя Reddit u/markekraus. Он говорит об этом подходе, который позволяет определить его непосредственно.

$myObject = [pscustomobject]@{
    PSTypeName = 'My.Object'
    Name       = 'Kevin'
    Language   = 'PowerShell'
    State      = 'Texas'
}

Я люблю, как хорошо это просто вписывается в язык. Теперь, когда у нас есть объект с правильным именем типа, мы можем сделать еще несколько действий.

Примечание.

Вы также можете создавать пользовательские типы PowerShell с помощью классов PowerShell. Дополнительные сведения см. в обзоре класса PowerShell.

Использование DefaultPropertySet (длинный путь)

PowerShell решает, какие свойства будут отображаться по умолчанию. Многие встроенные команды имеют файл форматирования .ps1xml, который выполняет всю основную работу. Из этой публикации Boe Proxесть еще один способ сделать это на нашем пользовательском объекте с помощью Просто PowerShell. Мы можем дать ему MemberSet для его использования.

$defaultDisplaySet = 'Name','Language'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
$MyObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers

Теперь, когда объект просто попадает в оболочку, он будет отображать только эти свойства по умолчанию.

Update-TypeData с набором свойств по умолчанию

Это хорошо, но недавно я видел лучший способ использовать Update-TypeData, чтобы указать свойства по умолчанию.

$TypeData = @{
    TypeName = 'My.Object'
    DefaultDisplayPropertySet = 'Name','Language'
}
Update-TypeData @TypeData

Это достаточно просто, что я мог бы почти вспомнить это, если бы у меня не было этого поста в качестве краткой ссылки. Теперь я могу легко создать объекты с большим количеством свойств и сделать их внешний вид аккуратным и чистым при просмотре из оболочки. Если мне нужно получить доступ к другим свойствам или увидеть их, они все ещё доступны.

$myObject | Format-List *

Update-TypeData с ScriptProperty

Из этого видео я узнал о создании свойств скрипта для ваших объектов. Это было бы хорошим временем, чтобы отметить, что это работает для существующих объектов тоже.

$TypeData = @{
    TypeName = 'My.Object'
    MemberType = 'ScriptProperty'
    MemberName = 'UpperCaseName'
    Value = {$this.Name.ToUpper()}
}
Update-TypeData @TypeData

Это можно сделать до создания или после создания объекта, и он по-прежнему будет работать. Это то, что делает его отличным от использования Add-Member со свойством скрипта. При использовании Add-Member так, как я ссылался ранее, он существует только в этом конкретном экземпляре объекта. Это относится ко всем объектам с этим TypeName.

Параметры функции

Теперь эти настраиваемые типы можно использовать для параметров в функциях и сценариях. Вы можете использовать одну функцию для создания этих пользовательских объектов, а затем передать их в другие функции.

param( [PSTypeName('My.Object')]$Data )

PowerShell требует, чтобы объект был заданным типом. При несоответствии типа автоматически возникает ошибка проверки, чтобы избавить вас от необходимости тестировать его в коде. Отличный пример того, как PowerShell позволяет выполнять свои задачи наилучшим образом.

Выходной тип функции

Вы также можете определить OutputType для расширенных функций.

function Get-MyObject
{
    [OutputType('My.Object')]
    [CmdletBinding()]
        param
        (
            ...

Значение атрибута OutputType является только примечанием к документации. Он не вычисляется из кода функции и не сравнивается с фактическими выходными данными функции.

Основная причина использования типа вывода заключается в том, что метаданные о функции отражают ваши намерения. Такие вещи, как Get-Command и Get-Help, которыми может воспользоваться ваша среда разработки. Если вы хотите получить дополнительные сведения, просмотрите справочную информацию: about_Functions_OutputTypeAttribute.

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

Выводы

Контекстом этого было все [pscustomobject], но большая часть этой информации относится к объектам в целом.

Я видел большинство из этих особенностей мельком раньше, но никогда не видел их представленными как коллекцию информации о PSCustomObject. Только на этой прошлой неделе я наткнулся на другую и был удивлен, что я не видел его раньше. Я хотел вытащить все эти идеи вместе, чтобы вы могли, надеюсь, увидеть большую картину и помнить о них, когда у вас есть возможность использовать их. Надеюсь, вы узнали что-то и сможете найти способ работать с этим в ваших сценариях.