Тип Microsoft.PowerShell.Commands.PSPropertyExpression (его акселератор — [pspropertyexpression]) присутствует в PowerShell с самого начала, но в версии 6.1 он стал публичным (public), что делает его доступным и для нас, как авторов скриптов и модулей.
Сам по себе этот тип используется, например, параметром Property командлета Measure-Object.
Get-Process pwsh | Measure-Object -Property CPU -Average
Count : 3 Average : 39.8645833333333 Sum : Maximum : Minimum : StandardDeviation : Property : CPU
В данном случае, в качестве значения параметра -Property мы указали строчное значение, однако, особенность этого типа данных в том, что кроме строк он может принимать и скриптблоки — scriptblock, и это позволяет нам отбирать для измерений не только уже существующие свойства объектов, но также и результаты их преобразований.
Get-Process pwsh | Measure-Object -Property {$_.Threads.Count} -Sum
Count : 3 Average : Sum : 71 Maximum : Minimum : StandardDeviation : Property : $_.Threads.Count
В PowerShell 6.2 появился командлет Join-String, также использующий PSPropertyExpression в качестве типа данных для значений параметра -Property.
Get-Service R* | Join-String -Property { $_.DisplayName + "`n" + $_.Description } -Separator "`n`n"
Remote Access Auto Connection Manager Creates a connection to a remote network whenever a program references a remote DNS or NetBIOS name or address. Remote Access Connection Manager Manages dial-up and virtual private network (VPN) connections from this computer to the Internet or other remote networks. If this service is disabled, any services that explicitly depend on it will fail to start. ...
Также этот тип неявным образом используется несколькими командлетами, например — Select-Object.
PSPropertyExpression
Теперь давайте посмотрим, как мы можем использовать тип Microsoft.PowerShell.Commands.PSPropertyExpression.
Однако сначала мы получим объект процесса pwsh, который мы и будем использовать в дальнейших примерах.
$ps = Get-Process pwsh | Select-Object -First 1
Property
Создать объект PSPropertyExpression мы можем следующим образом.
$PathProperty = [pspropertyexpression]::new("Path")
Получить значение указанного свойства (в данном случае — Path) определенного объекта мы можем при помощи метода GetValues объекта PSPropertyExpression.
$PathProperty.GetValues($ps)
Result ResolvedExpression Exception ------ ------------------ --------- C:\Program Files\PowerShell\7\pwsh.exe Path
В качестве результата мы получим объект Microsoft.PowerShell.Commands.PSPropertyExpressionResult, состоящий из следующих свойств: Result, в котором расположено значение интересующего нас свойства, ResolvedExpression, где указано имя этого свойства, а также Exception, содержащее исключение, в случае если таковое произошло при попытке это значение получить.
Если же нас интересует исключительно значение указанного свойства, мы можем запросить свойство Result полученного объекта.
$PathProperty.GetValues($ps).Result
C:\Program Files\PowerShell\7\pwsh.exe
Wildcards
В качестве аргумента при создании нового объекта PSPropertyExpression мы можем использовать и строки с символами подстановки.
$MemorySize = [pspropertyexpression]::new("*MemorySize64")
Свойство HasWildCardCharacters объекта PSPropertyExpression теперь будет содержать значение True.
$MemorySize.HasWildCardCharacters
True
Теперь, как и в предыдущем примере, вызовем метод GetValues.
$MemorySize.GetValues($ps)
Result ResolvedExpression Exception ------ ------------------ --------- 66896 NonpagedSystemMemorySize64 61038592 PagedMemorySize64 381112 PagedSystemMemorySize64 61190144 PeakPagedMemorySize64 2204026884096 PeakVirtualMemorySize64 61038592 PrivateMemorySize64 2204008886272 VirtualMemorySize64
При создании объекта PSPropertyExpression с использованием строки мы можем указать второй параметр типа bool. Установка его в True укажет конструктору, что первый аргумент указан в его окончательном виде и дальнейшие попытки раскрыть встречающиеся в нем символы подстановки производить не нужно. Это может пригодиться, например, если ваши объекты содержат такие символы в именах свойств.
$WildcardProperty = [pspropertyexpression]::new("Property*Name?", $true)
Parameter Sets
Еще одной особенностью типа PSPropertyExpression является то, что он позволяет нам указывать наборы свойств — Property Set.
Например, объект процесса обладает двумя наборами свойств — PSConfiguration и PSResources.
Get-Process | Get-Member -MemberType PropertySet | Select-Object -Property Name
Name ---- PSConfiguration PSResources
Первый из них — PSConfiguration, к примеру, содержит следующие свойства.
$ps.PSConfiguration.ReferencedPropertyNames
Name Id PriorityClass FileVersion
Давайте создадим объект PSPropertyExpression на основе этого набора свойств.
$PSConfiguration = [pspropertyexpression]::new("PSConfiguration")
Теперь, если мы вызовем метод GetValues, то в качестве результата получим четыре объекта PSPropertyExpressionResult, каждый из которых соответствует одному из свойств набора PSConfiguration.
$PSConfiguration.GetValues($ps)
Result ResolvedExpression Exception ------ ------------------ --------- pwsh Name 12752 Id Normal PriorityClass 7.0.1.0 FileVersion
Метод GetValues обладает и второй перегрузкой (overload), которая позволяет нам указать три параметра: первый — target, как и в предыдущих примерах, это объект, значения свойств которого мы получаем, второй параметр — expand, при установке которого в $false, мы можем предотвратить раскрытие набора свойств на составляющие его отдельные свойства, и третий — eatExceptions, установка которого в $false приведет к тому, что любое возникшее при получении значений свойств исключение (exception) приведет к прекращению выполнения метода GetValues. По умолчанию же исключения видны только в свойстве Exception объекта PSPropertyExpressionResult.
$PSConfiguration.GetValues($ps, $false, $false)
Понятно, что в данном случае никакого результата не будет.
Интересно, что на основе объекта PSPropertyExpression для набора свойств, мы можем получить объекты PSPropertyExpression для каждого входящего в набор свойства. Сделать это можно при помощи метода ResolveNames.
$PSConfiguration.ResolveNames($ps)
Script HasWildCardCharacters ------ --------------------- False False False False
И хотя в обычном их виде мы не можем сказать, какой из них которому свойству соответствует, однако узнать это мы можем при помощи метода ToString.
$PSConfiguration.ResolveNames($ps) | ForEach-Object ToString
Name Id PriorityClass FileVersion
Кроме того, метод ResolveNames обладает и второй перегрузкой, позволяющей указать параметр expand, установка которого в $false, так же, как и в методе GetValues, ведет к предотвращению раскрытия набора свойств на его составляющие свойства.
$PSConfiguration.ResolveNames($ps, $false)
ScriptBlock
Как мы уже говорили, кроме строчных значений объект PSPropertyExpression поддерживает и скриптблоки, что открывает перед нами еще большее число возможностей по получению и преобразованию значений различных свойств.
$WorkingSet = [pspropertyexpression]::new({$_.WorkingSet64 / 1mb}) $WorkingSet.GetValues($ps)
Result ResolvedExpression Exception ------ ------------------ --------- 98.66015625 $_.WorkingSet64 / 1mb
$WorkingSet.GetValues($ps).Result
98.66015625
Result
Что же нам дает объект PSPropertyExpression?
Действительно, в случае с указанием определенного свойства
[pspropertyexpression]::new("Path").GetValues($ps)
мы можем получить его значение и напрямую.
$ps.Path
Однако когда мы указываем строку, содержащую символы подстановки, набор свойств или же скриптблок, то в этих случаях объект PSPropertyExpression позволяет нам получить значения нужных свойств без необходимости использования командлетов ForEach-Object или Select-Object.
То есть вместо команд
$ps | Select-Object -Property *MemorySize64 $ps | ForEach-Object -Process {$_.WorkingSet64 / 1mb}
мы можем использовать уже упоминавшиеся выше
[pspropertyexpression]::new("*MemorySize64").GetValues($ps) [pspropertyexpression]::new({$_.WorkingSet64 / 1mb}).GetValues($ps)
и тем самым несколько сократить количество задействованных ресурсов, а также время, требуемое на выполнение команд.