Как создать собственный тип в PowerShell для использования моих скриптов?
Я хотел бы иметь возможность определять и использовать собственный тип в некоторых сценариях PowerShell. Например, пусть притворяется, что мне нужен объект, который имеет следующую структуру:
Contact
{
string First
string Last
string Phone
}
Как мне это сделать, чтобы я мог использовать его в следующей функции:
function PrintContact
{
param( [Contact]$contact )
"Customer Name is " + $contact.First + " " + $contact.Last
"Customer Phone is " + $contact.Phone
}
Что-то вроде этого возможно или даже рекомендуется в PowerShell?
Ответы
Ответ 1
До PowerShell 3
Изначально PowerShell Extensible Type System не позволяла вам создавать конкретные типы, которые можно тестировать в соответствии с тем, как вы это делали в параметре. если вам не нужен этот тест, вы можете использовать любой из перечисленных выше методов.
Если вам нужен фактический тип, к которому вы можете привести или проверить тип, как в вашем примере сценария... это нельзя сделать без написания его на С# или VB.net и компиляции. В PowerShell 2 вы можете использовать команду "Добавить тип", чтобы сделать ее достаточно простой:
add-type @"
public struct contact {
public string First;
public string Last;
public string Phone;
}
"@
Историческая справка: в PowerShell 1 это было еще сложнее. Вы должны были вручную использовать CodeDom, на PoshCode.org есть очень старый скрипт функции new-struct, который поможет. Ваш пример становится:
New-Struct Contact @{
First=[string];
Last=[string];
Phone=[string];
}
Использование Add-Type
или New-Struct
позволит вам на самом деле протестировать класс в вашем param([Contact]$contact)
и создавать новые, используя $contact = new-object Contact
и так далее...
В PowerShell 3
Если вам не нужен "настоящий" класс, который вы можете использовать, вам не нужно использовать способ Add-Member, который Стивен и другие продемонстрировали выше.
Начиная с PowerShell 2, вы можете использовать параметр -Property для New-Object:
$Contact = New-Object PSObject -Property @{ First=""; Last=""; Phone="" }
А в PowerShell 3 мы получили возможность использовать ускоритель PSCustomObject
для добавления TypeName:
[PSCustomObject]@{
PSTypeName = "Contact"
First = $First
Last = $Last
Phone = $Phone
}
Вы все еще получаете только один объект, поэтому вы должны сделать функцию New-Contact
, чтобы убедиться, что каждый объект получается одинаковым, но теперь вы можете легко проверить параметр "is" одного из этих типов, украшая параметр с атрибутом PSTypeName
:
function PrintContact
{
param( [PSTypeName("Contact")]$contact )
"Customer Name is " + $contact.First + " " + $contact.Last
"Customer Phone is " + $contact.Phone
}
В PowerShell 5
В PowerShell 5 все меняется, и мы наконец получили class
и enum
в качестве ключевых слов языка для определения типов (там нет struct
, но это нормально):
class Contact
{
# Optionally, add attributes to prevent invalid values
[ValidateNotNullOrEmpty()][string]$First
[ValidateNotNullOrEmpty()][string]$Last
[ValidateNotNullOrEmpty()][string]$Phone
# optionally, have a constructor to
# force properties to be set:
Contact($First, $Last, $Phone) {
$this.First = $First
$this.Last = $Last
$this.Phone = $Phone
}
}
У нас также появился новый способ создания объектов без использования New-Object
: [Contact]::new()
- фактически, если вы сохранили свой класс простым и не определяете конструктор, вы можете создавать объекты путем приведения хеш-таблицы (хотя без конструктора нельзя было бы принудительно установить, что все свойства должны быть установлены):
class Contact
{
# Optionally, add attributes to prevent invalid values
[ValidateNotNullOrEmpty()][string]$First
[ValidateNotNullOrEmpty()][string]$Last
[ValidateNotNullOrEmpty()][string]$Phone
}
$C = [Contact]@{
First = "Joel"
Last = "Bennett"
}
Ответ 2
Создание настраиваемых типов может быть выполнено в PowerShell.
У Кирка Манро есть две отличные должности, которые подробно описывают процесс.
В книге Windows PowerShell In Action Manning также есть образец кода для создания определенного для домена языка для создания пользовательских типов. Книга превосходна во всем, поэтому я действительно рекомендую ее.
Если вы просто ищете быстрый способ сделать это, вы можете создать функцию для создания пользовательского объекта, например
function New-Person()
{
param ($FirstName, $LastName, $Phone)
$person = new-object PSObject
$person | add-member -type NoteProperty -Name First -Value $FirstName
$person | add-member -type NoteProperty -Name Last -Value $LastName
$person | add-member -type NoteProperty -Name Phone -Value $Phone
return $person
}
Ответ 3
Это метод быстрого доступа:
$myPerson = "" | Select-Object First,Last,Phone
Ответ 4
Ответ Стивена Муравски велик, однако мне нравится более короткий (или, скорее, просто элемент выбора опережения вместо использования синтаксиса add-member):
function New-Person() {
param ($FirstName, $LastName, $Phone)
$person = new-object PSObject | select-object First, Last, Phone
$person.First = $FirstName
$person.Last = $LastName
$person.Phone = $Phone
return $person
}
Ответ 5
Удивленный никто не упомянул этот простой вариант (vs 3 или новее) для создания пользовательских объектов:
[PSCustomObject]@{
First = $First
Last = $Last
Phone = $Phone
}
Тип будет PSCustomObject, но не является обычным типом. Но это, вероятно, самый простой способ создания пользовательского объекта.
Ответ 6
Существует концепция PSObject и Add-Member, которые вы могли бы использовать.
$contact = New-Object PSObject
$contact | Add-Member -memberType NoteProperty -name "First" -value "John"
$contact | Add-Member -memberType NoteProperty -name "Last" -value "Doe"
$contact | Add-Member -memberType NoteProperty -name "Phone" -value "123-4567"
Это выводится следующим образом:
[8] » $contact
First Last Phone
----- ---- -----
John Doe 123-4567
Другой альтернативой (я знаю) является определение типа в С#/VB.NET и загрузка этой сборки в PowerShell для непосредственного использования.
Такое поведение определенно поощряется, поскольку позволяет другим скриптам или разделам вашего script работать с реальным объектом.
Ответ 7
Вот жесткий путь для создания пользовательских типов и хранения их в коллекции.
$Collection = @()
$Object = New-Object -TypeName PSObject
$Object.PsObject.TypeNames.Add('MyCustomType.Contact.Detail')
Add-Member -InputObject $Object -memberType NoteProperty -name "First" -value "John"
Add-Member -InputObject $Object -memberType NoteProperty -name "Last" -value "Doe"
Add-Member -InputObject $Object -memberType NoteProperty -name "Phone" -value "123-4567"
$Collection += $Object
$Object = New-Object -TypeName PSObject
$Object.PsObject.TypeNames.Add('MyCustomType.Contact.Detail')
Add-Member -InputObject $Object -memberType NoteProperty -name "First" -value "Jeanne"
Add-Member -InputObject $Object -memberType NoteProperty -name "Last" -value "Doe"
Add-Member -InputObject $Object -memberType NoteProperty -name "Phone" -value "765-4321"
$Collection += $Object
Write-Ouput -InputObject $Collection
Ответ 8
Вот еще один вариант, который использует идею, аналогичную решению PSTypeName, упомянутому Jaykul (и, следовательно, также требует PSv3 или выше).
Пример
- Создайте файл TypeName.Types.ps1xml, определяющий ваш тип. Например.
Person.Types.ps1xml
:
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>Qaru.Example.Person</Name>
<Members>
<ScriptMethod>
<Name>Initialize</Name>
<Script>
Param (
[Parameter(Mandatory = $true)]
[string]$GivenName
,
[Parameter(Mandatory = $true)]
[string]$Surname
)
$this | Add-Member -MemberType 'NoteProperty' -Name 'GivenName' -Value $GivenName
$this | Add-Member -MemberType 'NoteProperty' -Name 'Surname' -Value $Surname
</Script>
</ScriptMethod>
<ScriptMethod>
<Name>SetGivenName</Name>
<Script>
Param (
[Parameter(Mandatory = $true)]
[string]$GivenName
)
$this | Add-Member -MemberType 'NoteProperty' -Name 'GivenName' -Value $GivenName -Force
</Script>
</ScriptMethod>
<ScriptProperty>
<Name>FullName</Name>
<GetScriptBlock>'{0} {1}' -f $this.GivenName, $this.Surname</GetScriptBlock>
</ScriptProperty>
<!-- include properties under here if we don't want them to be visible by default
<MemberSet>
<Name>PSStandardMembers</Name>
<Members>
</Members>
</MemberSet>
-->
</Members>
</Type>
</Types>
- Импортируйте ваш тип:
Update-TypeData -AppendPath .\Person.Types.ps1xml
- Создайте объект вашего пользовательского типа:
$p = [PSCustomType]@{PSTypeName='Qaru.Example.Person'}
- Инициализируйте ваш тип, используя метод сценария, который вы определили в XML:
$p.Initialize('Anne', 'Droid')
- Посмотри на это; вы увидите все свойства, определенные:
$p | Format-Table -AutoSize
- Введите вызов мутатора для обновления значения свойства:
$p.SetGivenName('Dan')
- Посмотрите еще раз, чтобы увидеть обновленное значение:
$p | Format-Table -AutoSize
Объяснение
- Файл PS1XML позволяет вам определять пользовательские свойства для типов.
- Это не ограничивается типами .net, как подразумевает документация; так что вы можете поместить то, что вам нравится, в "/Types/Type/Name", любой объект, созданный с соответствующим "PSTypeName", унаследует элементы, определенные для этого типа.
- Члены, добавленные через
PS1XML
или Add-Member
, ограничены NoteProperty
, AliasProperty
, ScriptProperty
, CodeProperty
, ScriptMethod
и CodeMethod
(или PropertySet
/MemberSet
; хотя они подлежат таким же ограничениям). Все эти свойства доступны только для чтения.
- Определив
ScriptMethod
, мы можем обмануть вышеуказанное ограничение. Например. Мы можем определить метод (например, Initialize
), который создает новые свойства, устанавливая их значения для нас; таким образом, гарантируя, что наш объект обладает всеми свойствами, необходимыми для работы других наших сценариев.
- Мы можем использовать этот же прием, чтобы позволить свойствам быть обновляемыми (хотя и с помощью метода, а не прямого назначения), как показано в примере
SetGivenName
.
Этот подход не идеален для всех сценариев; но полезен для добавления поведения, подобного классу, к пользовательским типам/может использоваться в сочетании с другими методами, упомянутыми в других ответах. Например. в реальном мире я бы, вероятно, только определил свойство FullName
в PS1XML, а затем использовал бы функцию для создания объекта с требуемыми значениями, например так:
Больше информации
Взгляните на документацию или файл типа OOTB Get-Content
$PSHome\types.ps1xml
для вдохновения.
# have something like this defined in my script so we only try to import the definition once.
# the surrounding if statement may be useful if we're dot sourcing the script in an existing
# session / running in ISE / something like that
if (!(Get-TypeData 'Qaru.Example.Person')) {
Update-TypeData '.\Person.Types.ps1xml'
}
# have a function to create my objects with all required parameters
# creating them from the hash table means they're PROPERties; i.e. updatable without calling a
# setter method (note: recall I said above that in this scenario I'd remove their definition
# from the PS1XML)
function New-SOPerson {
[CmdletBinding()]
[OutputType('Qaru.Example.Person')]
Param (
[Parameter(Mandatory)]
[string]$GivenName
,
[Parameter(Mandatory)]
[string]$Surname
)
([PSCustomObject][Ordered]@{
PSTypeName = 'Qaru.Example.Person'
GivenName = $GivenName
Surname = $Surname
})
}
# then use my new function to generate the new object
$p = New-SOPerson -GivenName 'Simon' -Surname 'Borg'
# and thanks to the type magic... FullName exists :)
Write-Information "$($p.FullName) was created successfully!" -InformationAction Continue