Làm cách nào để tạo một loại tùy chỉnh trong PowerShell để các tập lệnh của tôi sử dụng?


88

Tôi muốn có thể xác định và sử dụng loại tùy chỉnh trong một số tập lệnh PowerShell của mình. Ví dụ: giả sử tôi cần một đối tượng có cấu trúc sau:

Contact
{
    string First
    string Last
    string Phone
}

Tôi sẽ làm cách nào để tạo cái này để tôi có thể sử dụng nó trong chức năng như sau:

function PrintContact
{
    param( [Contact]$contact )
    "Customer Name is " + $contact.First + " " + $contact.Last
    "Customer Phone is " + $contact.Phone 
}

Điều gì đó như thế này có thể thực hiện được hay thậm chí được đề xuất trong PowerShell không?

Câu trả lời:


133

Trước PowerShell 3

Hệ thống Kiểu mở rộng của PowerShell ban đầu không cho phép bạn tạo các kiểu cụ thể mà bạn có thể kiểm tra theo cách bạn đã làm trong tham số của mình. Nếu bạn không cần kiểm tra đó, bạn vẫn ổn với bất kỳ phương pháp nào khác được đề cập ở trên.

Nếu bạn muốn một kiểu thực tế mà bạn có thể truyền đến hoặc kiểm tra kiểu, như trong tập lệnh mẫu của bạn ... thì không thể thực hiện được nếu không viết nó trong C # hoặc VB.net và biên dịch. Trong PowerShell 2, bạn có thể sử dụng lệnh "Add-Type" để thực hiện khá đơn giản:

add-type @"
public struct contact {
   public string First;
   public string Last;
   public string Phone;
}
"@

Lưu ý lịch sử : Trong PowerShell 1, điều đó thậm chí còn khó hơn. Bạn phải sử dụng CodeDom theo cách thủ công, có một chức năng rất cũscript new-struct trên PoshCode.org sẽ hữu ích. Ví dụ của bạn trở thành:

New-Struct Contact @{
    First=[string];
    Last=[string];
    Phone=[string];
}

Sử dụng Add-Typehoặc New-Structsẽ cho phép bạn thực sự kiểm tra lớp trong của bạn param([Contact]$contact)và tạo ra những lớp mới bằng cách sử dụng $contact = new-object Contact, v.v.

Trong PowerShell 3

Nếu bạn không cần một lớp "thực" mà bạn có thể truyền đến, bạn không cần phải sử dụng cách Add-Member mà Steven và những người khác đã trình bày ở trên.

Vì PowerShell 2, bạn có thể sử dụng tham số -Property cho New-Object:

$Contact = New-Object PSObject -Property @{ First=""; Last=""; Phone="" }

Và trong PowerShell 3, chúng tôi có khả năng sử dụng trình PSCustomObjecttăng tốc để thêm TypeName:

[PSCustomObject]@{
    PSTypeName = "Contact"
    First = $First
    Last = $Last
    Phone = $Phone
}

Bạn vẫn chỉ nhận được một đối tượng duy nhất, vì vậy bạn nên tạo một New-Contacthàm để đảm bảo rằng mọi đối tượng đều xuất hiện giống nhau, nhưng bây giờ bạn có thể dễ dàng xác minh tham số "là" một trong những loại đó bằng cách trang trí một tham số với PSTypeNamethuộc tính:

function PrintContact
{
    param( [PSTypeName("Contact")]$contact )
    "Customer Name is " + $contact.First + " " + $contact.Last
    "Customer Phone is " + $contact.Phone 
}

Trong PowerShell 5

Trong PowerShell 5 thay đổi tất cả mọi thứ, và cuối cùng chúng tôi đã nhận classenumnhư từ khóa ngôn ngữ để xác định các loại (không có structnhưng đó là ok):

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
    }
}

Chúng tôi cũng có một cách mới để tạo các đối tượng mà không cần sử dụng New-Object: [Contact]::new()- trên thực tế, nếu bạn giữ cho lớp của mình đơn giản và không xác định hàm tạo, bạn có thể tạo các đối tượng bằng cách ép kiểu bảng băm (mặc dù không có hàm tạo, sẽ không có cách nào để thực thi rằng tất cả các thuộc tính phải được đặt):

class Contact
{
    # Optionally, add attributes to prevent invalid values
    [ValidateNotNullOrEmpty()][string]$First
    [ValidateNotNullOrEmpty()][string]$Last
    [ValidateNotNullOrEmpty()][string]$Phone
}

$C = [Contact]@{
   First = "Joel"
   Last = "Bennett"
}

Câu trả lời chính xác! Chỉ cần thêm một lưu ý rằng phong cách này là rất dễ dàng cho các kịch bản và vẫn hoạt động trong PowerShell 5: New-Object PSObject -Property @ {chống đỡ ở đây ...}
Ryan Shillington

2
Trong các bản phát hành PowerShell 5 đầu tiên, bạn không thể sử dụng New-Object với các lớp được tạo bằng cú pháp lớp, nhưng bây giờ bạn có thể. TUY NHIÊN, nếu bạn đang sử dụng từ khoá class, kịch bản của bạn được giới hạn PS5 chỉ anyway, vì vậy tôi vẫn muốn giới thiệu cách sử dụng :: cú pháp mới nếu đối tượng có một constructor mà sẽ đưa các thông số (đó là nhiều nhanh hơn so với New-Object) hoặc ép kiểu khác, đó là cú pháp gọn gàng hơn và nhanh hơn.
Jaykul

Bạn có chắc chắn không thể thực hiện kiểm tra loại với các loại được tạo bằng cách sử dụng Add-Type? Nó dường như hoạt động trong PowerShell 2 trên Win 2008 R2. Giả sử tôi xác định contactsử dụng Add-Typenhư trong câu trả lời của bạn và sau đó tạo ra một thể hiện: $con = New-Object contact -Property @{ First="a"; Last="b"; Phone="c" }. Sau đó, gọi hàm này hoạt động:, function x([contact]$c) { Write-Host ($c | Out-String) $c.GetType() }nhưng gọi hàm này không thành công x([doesnotexist]$c) { Write-Host ($c | Out-String) $c.GetType() },. Gọi x 'abc'cũng không thành công với thông báo lỗi thích hợp về truyền. Đã kiểm tra trong PS 2 và 4.
jpmc26

Tất nhiên bạn có thể kiểm tra các kiểu được tạo bằng Add-Type@ jpmc26, những gì tôi đã nói là bạn không thể làm điều đó nếu không biên dịch (tức là: không viết nó bằng C # và gọi Add-Type). Tất nhiên, từ PS3 bạn có thể - có một [PSTypeName("...")]thuộc tính cho phép bạn chỉ định các loại như là một chuỗi, trong đó hỗ trợ thử nghiệm chống lại PSCustomObjects với PSTypeNames thiết lập ...
Jaykul

58

Việc tạo các kiểu tùy chỉnh có thể được thực hiện trong PowerShell.
Kirk Munro thực sự có hai bài đăng tuyệt vời trình bày chi tiết quá trình một cách kỹ lưỡng.

Cuốn sách Windows PowerShell In Action của Manning cũng có một mẫu mã để tạo ngôn ngữ dành riêng cho miền để tạo các kiểu tùy chỉnh. Cuốn sách rất xuất sắc, vì vậy tôi thực sự khuyên bạn nên sử dụng nó.

Nếu bạn chỉ đang tìm kiếm một cách nhanh chóng để thực hiện những điều trên, bạn có thể tạo một hàm để tạo đối tượng tùy chỉnh như

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
}

17

Đây là phương pháp phím tắt:

$myPerson = "" | Select-Object First,Last,Phone

3
Về cơ bản, lệnh ghép ngắn Select-Object thêm các thuộc tính cho các đối tượng mà nó được đưa ra nếu đối tượng chưa có thuộc tính đó. Trong trường hợp này, bạn đang đưa một đối tượng Chuỗi trống vào lệnh ghép ngắn Chọn-Đối tượng. Nó thêm các thuộc tính và chuyển đối tượng dọc theo đường ống. Hoặc nếu đó là lệnh cuối cùng trong đường ống, nó sẽ xuất đối tượng. Tôi nên chỉ ra rằng tôi chỉ sử dụng phương pháp này nếu tôi đang làm việc tại dấu nhắc. Đối với các tập lệnh, tôi luôn sử dụng các lệnh ghép ngắn Thêm Thành viên hoặc Đối tượng Mới rõ ràng hơn.
EBGreen

Mặc dù đây là một thủ thuật tuyệt vời nhưng bạn thực sự có thể làm cho nó ngắn hơn nữa:$myPerson = 1 | Select First,Last,Phone
RaYell

Điều này không cho phép bạn sử dụng các hàm kiểu gốc, vì nó đặt kiểu của mỗi thành viên là chuỗi. Với Jaykul đóng góp trên, cho thấy mỗi thành viên lưu ý như một NotePropertysố stringloại, nó là một Propertycủa bất cứ loại mà bạn đã gán trong đối tượng. Điều này nhanh chóng và thực hiện công việc.
mbrownnyc

Điều này có thể gây ra sự cố nếu bạn muốn có thuộc tính Độ dài, vì chuỗi đã có thuộc tính đó và đối tượng mới của bạn sẽ nhận giá trị hiện có - điều mà bạn có thể không muốn. Tôi khuyên bạn nên chuyển [int], như @RaYell hiển thị.
FSCKur

9

Câu trả lời của Steven Murawski rất tuyệt, tuy nhiên tôi thích câu trả lời ngắn hơn (hoặc đúng hơn là chỉ chọn đối tượng gọn gàng hơn thay vì sử dụng cú pháp thêm thành viên):

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
}

New-Objectthậm chí không cần thiết. Điều này sẽ làm tương tự:... = 1 | select-object First, Last, Phone
La Mã Kuzmin

1
Đúng vậy, nhưng giống như EBGreen ở trên - điều này tạo ra một kiểu cơ bản kỳ lạ (trong ví dụ của bạn, nó sẽ là Int32.) Như bạn sẽ thấy nếu bạn nhập: $ person | gm. Tôi thích có các loại cơ bản trở thành một PSCustomObject
Nick Meldrum

2
Tôi thấy vấn đề. Tuy nhiên, có những lợi thế rõ ràng của intcách này: 1) nó hoạt động nhanh hơn, không nhiều, nhưng đối với chức năng cụ thể này, New-Personsự khác biệt là 20%; 2) nó rõ ràng là dễ nhập hơn. Đồng thời, sử dụng cách tiếp cận này về cơ bản ở khắp mọi nơi, tôi chưa bao giờ thấy bất kỳ nhược điểm nào. Nhưng tôi đồng ý: có thể có một số trường hợp hiếm hoi khi PSCustomObject tốt hơn.
Roman Kuzmin

@RomanKuzmin Có còn nhanh hơn 20% nếu bạn khởi tạo một đối tượng tùy chỉnh toàn cục và lưu trữ nó dưới dạng một biến tập lệnh không?
jpmc26

5

Ngạc nhiên là không ai đề cập đến tùy chọn đơn giản này (so với 3 hoặc mới hơn) để tạo các đối tượng tùy chỉnh:

[PSCustomObject]@{
    First = $First
    Last = $Last
    Phone = $Phone
}

Loại sẽ là PSCustomObject, không phải là loại tùy chỉnh thực tế. Nhưng nó có lẽ là cách dễ nhất để tạo một đối tượng tùy chỉnh.


Xem thêm bài đăng trên blog này của Will Anderson về sự khác biệt của PSObject và PSCustomObject.
CodeFox

@CodeFox vừa nhận thấy rằng liên kết bị hỏng ngay bây giờ
superjos

2
@superjos, cảm ơn vì gợi ý. Tôi không thể tìm thấy vị trí mới của bài đăng. Ít nhất thì bài đăng đã được sao lưu bởi kho lưu trữ .
CodeFox

2
rõ ràng có vẻ như nó đã được chuyển thành một cuốn sách Git ở đây :)
superjos

4

Có khái niệm về PSObject và Add-Member mà bạn có thể sử dụng.

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

Kết quả đầu ra như sau:

[8] » $contact

First                                       Last                                       Phone
-----                                       ----                                       -----
John                                        Doe                                        123-4567

Cách thay thế khác (mà tôi biết) là xác định một kiểu trong C # / VB.NET và tải lắp ráp đó vào PowerShell để sử dụng trực tiếp.

Hành vi này chắc chắn được khuyến khích vì nó cho phép các tập lệnh hoặc phần khác trong tập lệnh của bạn hoạt động với một đối tượng thực tế.


3

Đây là cách khó để tạo các loại tùy chỉnh và lưu trữ chúng trong một bộ sưu tập.

$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

Cảm ứng tốt với việc thêm tên loại vào đối tượng.
oɔɯǝɹ

0

Dưới đây là một lựa chọn hơn, trong đó sử dụng một ý tưởng tương tự như các giải pháp được đề cập bởi PSTypeName Jaykul (và do đó cũng đòi hỏi PSv3 hoặc cao hơn).

Thí dụ

  1. Tạo tệp TypeName .Types.ps1xml xác định loại của bạn. Vd Person.Types.ps1xml:
<?xml version="1.0" encoding="utf-8" ?>
<Types>
  <Type>
    <Name>StackOverflow.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. Nhập loại của bạn: Update-TypeData -AppendPath .\Person.Types.ps1xml
  2. Tạo một đối tượng thuộc loại tùy chỉnh của bạn: $p = [PSCustomType]@{PSTypeName='StackOverflow.Example.Person'}
  3. Khởi tạo kiểu của bạn bằng phương pháp tập lệnh mà bạn đã xác định trong XML: $p.Initialize('Anne', 'Droid')
  4. Nhìn nó; bạn sẽ thấy tất cả các thuộc tính được xác định:$p | Format-Table -AutoSize
  5. Nhập gọi một trình đột biến để cập nhật giá trị của thuộc tính: $p.SetGivenName('Dan')
  6. Nhìn lại nó để thấy giá trị cập nhật: $p | Format-Table -AutoSize

Giải trình

  • Tệp PS1XML cho phép bạn xác định các thuộc tính tùy chỉnh trên các loại.
  • Nó không bị giới hạn đối với các loại .net như tài liệu ngụ ý; vì vậy bạn có thể đặt những gì bạn thích trong '/ Loại / Loại / Tên' bất kỳ đối tượng nào được tạo bằng 'PSTypeName' phù hợp sẽ kế thừa các thành viên được xác định cho loại này.
  • Viên bổ sung thông qua PS1XMLhoặc Add-Memberbị hạn chế NoteProperty, AliasProperty, ScriptProperty, CodeProperty, ScriptMethod, và CodeMethod(hoặc PropertySet/ MemberSet, mặc dù những người này tùy thuộc vào các hạn chế tương tự). Tất cả các thuộc tính này chỉ được đọc.
  • Bằng cách xác định a, ScriptMethodchúng ta có thể gian lận hạn chế ở trên. Ví dụ: Chúng ta có thể xác định một phương thức (ví dụ Initialize) tạo ra các thuộc tính mới, thiết lập các giá trị của chúng cho chúng ta; do đó đảm bảo đối tượng của chúng tôi có tất cả các thuộc tính chúng tôi cần để các tập lệnh khác của chúng tôi hoạt động.
  • Chúng ta có thể sử dụng thủ thuật tương tự này để cho phép các thuộc tính có thể được cập nhật (mặc dù thông qua phương thức thay vì gán trực tiếp), như được hiển thị trong ví dụ SetGivenName.

Cách tiếp cận này không lý tưởng cho tất cả các tình huống; nhưng hữu ích để thêm các hành vi giống lớp vào các kiểu tùy chỉnh / có thể được sử dụng kết hợp với các phương thức khác được đề cập trong các câu trả lời khác. Ví dụ: trong thế giới thực, tôi có thể chỉ xác định thuộc FullNametính trong PS1XML, sau đó sử dụng một hàm để tạo đối tượng với các giá trị bắt buộc, như sau:

Thêm thông tin

Hãy xem tài liệu hoặc tệp loại OOTB Get-Content $PSHome\types.ps1xmlđể tìm cảm hứng.

# 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 'StackOverflow.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('StackOverflow.Example.Person')]
    Param (
        [Parameter(Mandatory)]
        [string]$GivenName
        ,
        [Parameter(Mandatory)]
        [string]$Surname
    )
    ([PSCustomObject][Ordered]@{
        PSTypeName = 'StackOverflow.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

ps. Đối với những người sử dụng VSCode, bạn có thể thêm hỗ trợ PS1XML
JohnLBevan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.