Что такое Windows PowerShell и с чем его едят? Часть 4: Работа с объектами, собственные классы

-bi_73lwdxmnzvtveeghrwvtqay.png

Текстовый вывод команд в окне интерпретатора PowerShell — всего лишь способ отображения информации в пригодном для человеческого восприятия виде. На самом деле среда ориентирована на работу с объектами: командлеты и функции получают их на входе и возвращают на выходе, а доступные в интерактивном режиме и в сценариях типы переменных базируются на классах .NET. В четвертой статье цикла мы изучим работу с объектами более детально.

Оглавление:


Объекты в PowerShell
Просмотр структуры объектов
Фильтрация объектов
Сортировка объектов
Выделение объектов и их частей
ForEach-Object, Group-Object и Measure-Object
Создание объектов .NET и COM (New-Object)
Вызов статических методов
Тип PSCustomObject
Создание собственных классов

Объекты в PowerShell


Напомним, что объект — это совокупность полей данных (свойств, событий и т.д.) и способов их обработки (методов). Его структура задается типом, который как правило базируется на использующихся в унифицированной платформе .NET Core классах. Также есть возможность работать с объектами COM, CIM (WMI) и ADSI. Свойства и методы нужны для выполнения различных действий над данными, кроме того в PowerShell объекты можно передавать как аргументы в функции и командлеты, присваивать их значения переменным, а также существует механизм композиции команд (конвейер или pipeline). Каждая команда в конвейере передает свой вывод следующей поочередно — объект за объектом. Для обработки можно использовать скомпилированные командлеты или создавать собственные расширенные функции, чтобы производить различные манипуляции с объектами в конвейере: фильтрацию, сортировку, группировку и даже изменение их структуры. Передача данных в таком виде имеет серьезное преимущество: принимающей команде не нужно заниматься синтаксическим разбором потока байтов (текста), вся нужная информация легко извлекается с помощью обращения к соответствующим свойствам и методам.

Просмотр структуры объектов


Для примера запустим командлет Get-Process, позволяющий получить информацию о работающих в системе процессах:

4kfoqsdoj0-r5h7lz6y5ecwjskc.png

Он выведет на экран некие отформатированные текстовые данные, не дающие представления о свойствах возвращаемых объектов и их методах. Для тонкого препарирования вывода необходимо научиться исследовать структуру объектов и в этом нам поможет командлет Get-Member:

Get-Process | Get-Member


86fqttuizhkkraqk9eoh9lnrfc4.png

Здесь мы уже видим тип и структуру, а с помощью дополнительных параметров можем, например, вывести только свойства попавшего на вход объекта:

Get-Process | Get-Member -MemberType Property


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

Фильтрация объектов


PowerShell позволяет пропускать по конвейеру объекты, удовлетворяющие определенному условию:

Where-Object { блок сценария }


Результатом выполнения блока сценария в операторных скобках должно быть логические значение. Если оно истинно ($true) попавший на вход командлету Where-Object объект будет передан по конвейеру дальше, в противном случае (значение $false) он будет удален. Для примера выведем список остановленных служб Windows Server, т.е. таких, у которых свойство Status имеет значение «Stopped»:

Get-Service | Where-Object {$_.Status -eq "Stopped"}


eqw6gyo6ww_hha2hw_cnbse9_ki.png

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

Get-Service | Where-Object {$_.Status -eq "Stopped"} | Get-Member


quukuy29t6wndu8hhktdyei1rsi.png

Сортировка объектов


При конвейерной обработке объектов часто возникает необходимость их сортировки. В командлет Sort-Object передаются имена свойств (ключей сортировки), а он возвращает упорядоченные по их значениям объекты. Вывод запущенных процессов несложно отсортировать по затраченному процессорному времени (свойство cpu):

Get-Process | Sort-Object –Property cpu


Параметр -Property при вызове командлета Sort-Object можно не указывать — он используется по умолчанию. Для обратной сортировки применяется параметр -Descending:

Get-Process | Sort-Object cpu -Descending


i5-logdefolkpn4oiqab5549qac.png

Выделение объектов и их частей


Командлет Select-Object позволяет выделить определенное количество объектов в начале или в конце конвейера с помощью параметров -First или -Last. С его помощью можно выбрать единичные объекты или определенные свойства, а также создать на их основе новые объекты. Разберем работу командлета на простых примерах.

Следующая команда выводит информацию о 10 процессах, потребляющих максимальный объем оперативной памяти (свойство WS):

Get-Process | Sort-Object WS -Descending | Select-Object -First 10


uztk4gok8nqkynaqcaupcito0ri.png

Можно выделить только определенные свойства проходящих через конвейер объектов и создать на их основе новые:

Get-Process | Select-Object ProcessName, Id -First 1


В результате работы конвейера мы получим новый объект, структура которого будет отличаться от структуры возвращаемых командлетом Get-Process. Убедимся в этом при помощи Get-Member:

Get-Process | Select-Object ProcessName, Id -First 1 | Get-Member


fw2y0cn_xin2jr571bsy1crcite.png

Обратите внимание, что Select-Object возвращает единичный объект (-First 1), у которого всего два указанных нами поля: их значения были скопированы из первого переданного в конвейер командлетом Get-Process объекта. На использовании Select-Object основан один из способов создания объектов в сценариях PowerShell:

$obj = Get-Process | Select-Object ProcessName, Id -First 1
$obj.GetType()


lfrpfonoe8e6xidulqfn8s6xfh8.png

С помощью Select-Object можно добавлять объектам вычисляемые свойства, которые необходимо представить в виде хэш-таблицы. При этом значение ее первого ключа соответствует имени свойства, а значение второго — значению свойства для текущего элемента конвейера:

Get-Process | Select-Object -Property ProcessName, @{Name="StartTime"; Expression = {$_.StartTime.Minute}}


vmoq6l8umcadkrnqomarqazayms.png

Посмотрим на структуру проходящих через конвейер объектов:

Get-Process | Select-Object -Property ProcessName, @{Name="StartTime"; Expression = {$_.StartTime.Minute}} | Get-Member


zt_adfbgvhdylarb7lxrrj1gpp4.png

ForEach-Object, Group-Object и Measure-Object


Для работы с объектами существуют и другие командлеты. Для примера расскажем о трех наиболее полезных:

ForEach-Object позволяет выполнить код на языке PowerShell для каждого объекта в конвейере:

ForEach-Object { блок сценария }


Group-Object группирует объекты по значению свойства:

Group-Object PropertyName


Если запустить его с параметром -NoElement, можно узнать количество элементов в группах.

Measure-Object агрегирует различные сводные параметры по значениям полей объектов в конвейере (вычисляет сумму, а также находит минимальное, максимальное или среднее значение):

Measure-Object -Property PropertyName -Minimum -Maximum -Average -Sum


Обычно рассмотренные командлеты используются в интерактивном режиме, а в скриптах чаще создаются функции с блоками Begin, Process и End.

Создание объектов .NET и COM (New-Object)


Есть множество программных компонентов с интерфейсами .NET Core и COM, которые пригодятся системным администраторам. С помощью класса System.Diagnostics.EventLog можно управлять системными журналами непосредственно из Windows PowerShell. Разберем пример создания экземпляра этого класса при помощи командлета New-Object с параметром -TypeName:

New-Object -TypeName System.Diagnostics.EventLog


xuqrdgtwodglftnhuszvu4uxaws.png

Поскольку мы не указали определенный журнал событий, полученный экземпляр класса не содержит данных. Чтобы это изменить, необходимо во время его создания вызвать специальный метод-конструктор при помощи параметра -ArgumentList. Если мы хотим получить доступ к журналу приложений, в конструктор следует передать строку «Application» в качестве аргумента:

$AppLog = New-Object -TypeName System.Diagnostics.EventLog -ArgumentList Application
$AppLog


gjdlvvpxyezep9mwm180v702fb0.png

Обратите внимание: выходные данные команды мы сохранили в переменной $AppLog. Хотя в интерактивном режиме обычно используются конвейеры, написание сценариев часто требует сохранения ссылки на объект. Кроме того основные классы .NET Core содержатся в пространстве имен System: PowerShell по умолчанию ищет в нем указанные типы, поэтому написание Diagnostics.EventLog вместо System.Diagnostics.EventLog вполне корректно.

Для работы с журналом можно обращаться к соответствующим методам:

$AppLog | Get-Member -MemberType Method


t790c5xr4ripd1fr9la0euc5yym.png

Скажем очищается он методом Clear () при наличии прав доступа:

$AppLog.Clear()


Командлет New-Object применяется и для работы с СОМ-компонентами. Их довольно много — от поставляемых с сервером сценариев Windows библиотек до приложений ActiveX, таких, например, как Internet Explorer. Чтобы создать СОМ-объект, требуется задать параметр -ComObject с программным идентификатора ProgId нужного класса:

New-Object -ComObject WScript.Shell
New-Object -ComObject WScript.Network
New-Object -ComObject Scripting.Dictionary
New-Object -ComObject Scripting.FileSystemObject


Для создания собственных объектов с произвольной структурой использование New-Object выглядит слишком архаичным и громоздким, этот командлет используется для работы с внешними по отношению к PowerShell программными компонентами. В следующих статьях этот вопрос будет разобран более подробно. Помимо объектов .NET и COM мы также изучим объекты CIM (WMI) и ADSI.

Вызов статических методов


Экземпляры некоторых классов .NET Core создать невозможно: к их числу относятся System.Environment и System.Math. Они являются статическими и содержат только статические свойства и методы. По сути это справочные библиотеки, которые используются без создания объектов. Сослаться на статический класс можно через литерал, заключив имя типа в квадратные скобки. При этом если посмотреть на структуру объекта с помощью Get-Member, мы увидим тип System.RuntimeType вместо System.Environment:

[System.Environment] | Get-Member


ikhek0k2z6vca22zc3eycacvo5i.png

Для просмотра только статических элементов нужно вызвать Get-Member с параметром -Static (обратите внимание на тип объекта):

[System.Environment] | Get-Member -Static


o0g7pb_kewdzes-asln_mcgslz4.png

Для доступа к статическим свойствам и методам используются два идущих подряд двоеточия вместо точки после литерала:

[System.Environment]::OSVersion


Или

$test=[System.Math]::Sqrt(25) 
$test
$test.GetType()


yidacsgbamkq7sxl32r51ifmie0.png

Тип PSCustomObject


Среди многочисленных доступных в PowerShell типов данных отдельно стоит упомянуть PSCustomObject, предназначенный для хранения объектов с произвольной структурой. Создание такого объекта с помощью командлета New-Object считается классическим, но громоздким и устаревшим способом:

$object = New-Object  –TypeName PSCustomObject -Property @{Name = 'Ivan Danko'; 
                                          City = 'Moscow';
                                          Country = 'Russia'}


Посмотрим на структуру объекта:

$object | Get-Member


m8jrvxzu6wvnk4x2oi3bt-6w6zg.png

Начиная с PowerShell 3.0 доступен и другой синтаксис:

$object = [PSCustomObject]@{Name = 'Ivan Danko'; 
                                          City = 'Moscow';
                                          Country = 'Russia'
}


Получить доступ к данным можно одним из эквивалентных способов:

$object.Name

$object.'Name'

$value = 'Name'
$object.$value


Приведем пример преобразования в объект существующей хэштаблицы:

$hash = @{'Name'='Ivan Danko'; 'City'='Moscow'; 'Country'='Russia'}
$hash.GetType()
$object = [pscustomobject]$hash
$object.GetType()


m1uugarnq3obtfdapd18xzqecva.png

Один из недостатков объектов этого типа — порядок их свойств может поменяться. Чтобы этого избежать, необходимо использовать атрибут [ordered]:

$object = [PSCustomObject][ordered]@{Name = 'Ivan Danko'; 
                                          City = 'Moscow';
                                          Country = 'Russia'
}


Есть и другие варианты создания объекта: выше мы рассмотрели использование командлета Select-Object. Осталось разобраться с добавлением и удалением элементов. Сделать это для объекта из предыдущего примера довольно просто:

$object | Add-Member –MemberType NoteProperty –Name Age  –Value 33
$object | Get-Member


au3vfg5kkxvcg9hhotimmx_viog.png

Командлет Add-Member позволяет добавлять ранее созданному объекту $object не только свойства, но и методы посредством использования конструкции »-MemberType ScriptMethod»:

$ScriptBlock = {
    # код 
}
$object | Add-Member -Name "MyMethod" -MemberType ScriptMethod -Value $ScriptBlock
$object | Get-Member


Обратите внимание: для хранения кода нового метода мы использовали переменную $ScriptBlock типа ScriptBlock.

1kh7umvv2w2ralp_6xwyxvhq2za.png

Для удаления свойств используется соответствующий метод:

$object.psobject.properties.remove('Name')


Создание собственных классов


В PowerShell 5.0 появилась возможность определения классов с использованием характерного для объектно-ориентированных языков программирования синтаксиса. Для этого предназначено служебное слово Class, после которого следует задать имя класса и описать его тело в операторных скобках:

class MyClass
{
    # тело класса
}


Это настоящий тип .NET Core, в теле которого описываются его свойства, методы и другие элементы. Рассмотрим пример определения простейшего класса:

class MyClass 
{
     [string]$Name
     [string]$City
     [string]$Country
}


Для создания объекта (экземпляра класса) используется командлет New-Object, либо литерал типа [MyClass] и псевдостатический метод new (конструктор по умолчанию):

$object = New-Object -TypeName MyClass


или

$object = [MyClass]::new()


Проанализируем структуру объекта:

$object | Get-Member


gkptprl6q2cwswt7lsdb2-0e_w8.png

Не стоит забывать про область видимости: нельзя ссылаться на имя типа в виде строки или использовать литерал типа за пределами скрипта или модуля, в котором определен класс. При этом функции могут возвращать экземпляры класса (объекты), которые будут доступны вне модуля или скрипта.

После создания объекта заполним его свойства:

$object.Name = 'Ivan Danko'
$object.City = 'Moscow'
$object.Country = 'Russia'
$object


tjx4sqnhix6a4d7wmd_kqahdnj0.png

Отметим, что в описании класса задаются не только типы свойств, но и их значения по умолчанию:

class Example
{
     [string]$Name = 'John Doe'
}


Описание метода класса напоминает описание функции, но без использования служебного слова function. Как и в функции, в методы при необходимости передаются параметры:

class MyClass 
{
     [string]$Name
     [string]$City
     [string]$Country
     
     #описание метода
     Smile([bool]$param1)
     {
         If($param1) {
            Write-Host ':)'
         }
     }
}


Теперь представитель нашего класса умеет улыбаться:

$object = [MyClass]::new()
$object.Smile($true)


Методы можно перегружать, кроме того у класса бывают статические свойства и методы, а также конструкторы, имена которых совпадают с именем самого класса. Определенный в скрипте или модуле PowerShell класс может служить базовым для другого — так реализуется наследование. При этом в качестве базовых допускается использование существующих классов .NET:

class MyClass2 : MyClass
{
      #тело нового класса, базовым для которого является MyClass
}
[MyClass2]::new().Smile($true)


Наше описание работы с объектами в PowerShell трудно назвать исчерпывающем. В следующих публикациях попробуем его углубить на практических примерах: пятая статья цикла будет посвящена вопросам интеграции PowerShell со сторонними программными компонентами. Прошлые части можно найти по ссылкам ниже.
Часть 1: основные возможности Windows PowerShell
Часть 2: введение в язык программирования Windows PowerShell
Часть 3: передача параметров в скрипты и функции, создание командлетов

iqfib45pgphfrxv--zfemt0qnmw.jpeg

© Habrahabr.ru