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


Все, что вы хотели знать о 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. Дополнительные сведения о встроенных элементах см. в этой статье.

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

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

$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")

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

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

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

Примечание.

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

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

PowerShell автоматически выбирает отображаемые свойства по умолчанию. У многих собственных команд имеется файл форматирования .ps1xml, который выполняет всю тяжелую работу. В публикации Бо Прокса предложен еще один способ сделать это с пользовательским объектом, используя только 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 с DefaultPropertySet

Это хорошо, но недавно я видел лучший способ использовать 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 функции

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

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

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

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

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

Выводы

Эта статья посвящена [PSCustomObject], но многие из этих сведений относятся к объектам в целом.

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