Подтвердить что ты не робот

Как создать собственный тип в 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?

4b9b3361

Ответ 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 или выше).

Пример

  1. Создайте файл 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>
  1. Импортируйте ваш тип: Update-TypeData -AppendPath .\Person.Types.ps1xml
  2. Создайте объект вашего пользовательского типа: $p = [PSCustomType]@{PSTypeName='Qaru.Example.Person'}
  3. Инициализируйте ваш тип, используя метод сценария, который вы определили в XML: $p.Initialize('Anne', 'Droid')
  4. Посмотри на это; вы увидите все свойства, определенные: $p | Format-Table -AutoSize
  5. Введите вызов мутатора для обновления значения свойства: $p.SetGivenName('Dan')
  6. Посмотрите еще раз, чтобы увидеть обновленное значение: $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