ฉันจะสร้างประเภทที่กำหนดเองใน PowerShell เพื่อให้สคริปต์ของฉันใช้งานได้อย่างไร


88

ฉันต้องการกำหนดและใช้ประเภทที่กำหนดเองในสคริปต์ 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

คำตอบ:


135

ก่อนหน้า PowerShell 3

ระบบ Extensible Type ของ PowerShell ไม่ได้ให้คุณสร้างประเภทคอนกรีตที่คุณสามารถทดสอบกับวิธีที่คุณทำในพารามิเตอร์ของคุณได้ หากคุณไม่ต้องการการทดสอบนั้นคุณสามารถใช้วิธีการอื่น ๆ ที่กล่าวถึงข้างต้นได้

หากคุณต้องการประเภทจริงที่คุณสามารถแคสต์หรือพิมพ์ตรวจสอบได้ดังในสคริปต์ตัวอย่างของคุณ ... ไม่สามารถทำได้โดยไม่ต้องเขียนใน C # หรือ VB.net และรวบรวม ใน PowerShell 2 คุณสามารถใช้คำสั่ง "Add-Type" เพื่อทำให้ง่ายขึ้น:

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

หมายเหตุทางประวัติศาสตร์ : ใน PowerShell 1 นั้นยากยิ่งกว่า คุณต้องใช้ CodeDom ด้วยตนเองมีสคริปต์ new-struct ของฟังก์ชันเก่ามากบน PoshCode.org ซึ่งจะช่วยได้ ตัวอย่างของคุณกลายเป็น:

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

การใช้Add-TypeหรือNew-Structจะให้คุณทดสอบชั้นเรียนในของคุณparam([Contact]$contact)และสร้างใหม่โดยใช้$contact = new-object Contactและอื่น ๆ ...

ใน PowerShell 3

หากคุณไม่ต้องการคลาส "ของจริง" ที่คุณสามารถแคสต์ได้คุณไม่จำเป็นต้องใช้วิธีการเพิ่มสมาชิกที่สตีเวนและคนอื่น ๆ ได้แสดงไว้ข้างต้น

เนื่องจาก 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ฟังก์ชันเพื่อให้แน่ใจว่าทุกออบเจ็กต์ออกมาเหมือนกัน แต่ตอนนี้คุณสามารถตรวจสอบพารามิเตอร์ "เป็น" หนึ่งในประเภทนั้นได้อย่างง่ายดายโดยการตกแต่งพารามิเตอร์ด้วย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"
}

ตอบโจทย์มาก! เพียงเพิ่มหมายเหตุว่าสไตล์นี้ง่ายมากสำหรับสคริปต์และยังใช้งานได้ใน PowerShell 5: New-Object PSObject -Property @ {prop here ... }
Ryan Shillington

2
ใน PowerShell 5 รุ่นแรกคุณไม่สามารถใช้ New-Object กับคลาสที่สร้างโดยใช้ไวยากรณ์ของคลาสได้ แต่ตอนนี้คุณสามารถทำได้แล้ว แต่ถ้าคุณใช้คำหลักชั้นสคริปต์ของคุณจะถูก จำกัด เพียง PS5 อยู่แล้วดังนั้นฉันยังคงแนะนำให้ใช้ :: ไวยากรณ์ใหม่หากวัตถุมีคอนสตรัคที่ใช้พารามิเตอร์ (มันมากเร็วกว่าใหม่ Object) หรือ การหล่อเป็นอย่างอื่นซึ่งเป็นทั้งไวยากรณ์ที่สะอาดกว่าและเร็วกว่า
Jaykul

คุณแน่ใจหรือไม่ว่าการตรวจสอบชนิดไม่สามารถทำได้กับชนิดสร้างขึ้นโดยใช้Add-Type? ดูเหมือนว่าจะทำงานใน PowerShell 2 บน Win 2008 R2 บอกว่าผมกำหนดcontactใช้ในขณะที่คำตอบของคุณแล้วสร้างอินสแตนซ์:Add-Type $con = New-Object contact -Property @{ First="a"; Last="b"; Phone="c" }แล้วโทรฟังก์ชั่นนี้ทำงาน: แต่การเรียกฟังก์ชั่นนี้ล้มเหลวfunction x([contact]$c) { Write-Host ($c | Out-String) $c.GetType() } x([doesnotexist]$c) { Write-Host ($c | Out-String) $c.GetType() }การโทรx 'abc'ล้มเหลวด้วยข้อความแสดงข้อผิดพลาดที่เหมาะสมเกี่ยวกับการแคสต์ ทดสอบใน PS 2 และ 4
jpmc26

แน่นอนคุณสามารถตรวจสอบประเภทที่สร้างด้วยAdd-Type@ jpmc26 สิ่งที่ฉันพูดคือคุณไม่สามารถทำได้โดยไม่ต้องรวบรวม (เช่น: โดยไม่ต้องเขียนใน C # และเรียกAdd-Type) แน่นอนจาก PS3 คุณสามารถ - มี[PSTypeName("...")]แอตทริบิวต์ที่ให้คุณระบุประเภทเป็นสตริงซึ่งรองรับการทดสอบกับ PSCustomObjects ด้วยชุด PSTypeNames ...
Jaykul

58

การสร้างชนิดที่กำหนดเองสามารถทำได้ใน PowerShell
Kirk Munro มีโพสต์ที่ยอดเยี่ยมสองโพสต์ที่ให้รายละเอียดกระบวนการอย่างละเอียด

หนังสือWindows PowerShell In Action by 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
}

17

นี่คือวิธีทางลัด:

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

3
โดยทั่วไป cmdlet Select-Object จะเพิ่มคุณสมบัติให้กับอ็อบเจ็กต์ที่ได้รับหากอ็อบเจ็กต์ยังไม่มีคุณสมบัตินั้น ในกรณีนี้คุณกำลังส่งวัตถุ String ว่างให้กับ Select-Object cmdlet เป็นการเพิ่มคุณสมบัติและส่งผ่านวัตถุไปตามท่อ หรือถ้าเป็นคำสั่งสุดท้ายในไปป์ก็จะส่งออกอ็อบเจ็กต์ ฉันควรชี้ให้เห็นว่าฉันจะใช้วิธีนี้ก็ต่อเมื่อฉันทำงานตามพร้อมต์เท่านั้น สำหรับสคริปต์ฉันมักจะใช้ Add-Member หรือ New-Object cmdlets ที่ชัดเจนกว่าเสมอ
EBGreen

แม้ว่านี่จะเป็นเคล็ดลับที่ยอดเยี่ยม แต่คุณสามารถทำให้สั้นลงได้:$myPerson = 1 | Select First,Last,Phone
RaYell

สิ่งนี้ไม่อนุญาตให้คุณใช้ฟังก์ชันประเภทเนทีฟเนื่องจากตั้งค่าประเภทของสมาชิกแต่ละคนเป็นสตริง ได้รับ Jaykul ผลงานข้างต้นแสดงให้เห็นว่าแต่ละครั้งเป็นสมาชิกNotePropertyของstringประเภทมันเป็นPropertyสิ่งที่คุณพิมพ์ที่ได้รับมอบหมายในวัตถุ สิ่งนี้รวดเร็วและได้ผล
mbrownnyc

สิ่งนี้อาจทำให้คุณมีปัญหาหากคุณต้องการคุณสมบัติ Length เนื่องจากสตริงมีอยู่แล้วและอ็อบเจ็กต์ใหม่ของคุณจะได้รับค่าที่มีอยู่ซึ่งคุณอาจไม่ต้องการ ขอแนะนำให้ส่ง [int] ตามที่ @RaYell แสดง
FSCKur

9

คำตอบของ Steven Murawski นั้นยอดเยี่ยมมาก แต่ฉันชอบสิ่งที่สั้นกว่า (หรือมากกว่าแค่เลือกวัตถุที่ดีกว่าแทนที่จะใช้ไวยากรณ์ของสมาชิกเพิ่ม):

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-Objectไม่จำเป็นด้วยซ้ำ สิ่งนี้จะทำเช่นเดียวกัน:... = 1 | select-object First, Last, Phone
Roman Kuzmin

1
ใช่ แต่เหมือนกับ EBGreen ด้านบน - สิ่งนี้สร้างประเภทพื้นฐานแปลก ๆ (ในตัวอย่างของคุณจะเป็น Int32) ดังที่คุณจะเห็นว่าคุณพิมพ์: $ person | กรัม ฉันชอบให้ประเภทพื้นฐานเป็น PSCustomObject
Nick Meldrum

2
ฉันเห็นประเด็น ยังคงมีข้อดีที่ชัดเจนintคือ 1) ทำงานได้เร็วขึ้นไม่มาก แต่สำหรับฟังก์ชันนี้New-Personความแตกต่างคือ 20% 2) มันง่ายกว่าที่จะพิมพ์ ในขณะเดียวกันการใช้แนวทางนี้โดยทั่วไปทุกที่ฉันไม่เคยเห็นข้อบกพร่องใด ๆ แต่ฉันเห็นด้วย: อาจมีบางกรณีที่เกิดขึ้นได้ยากเมื่อ PSCustomObject ดีกว่า
Roman Kuzmin

@RomanKuzmin ยังเร็วกว่า 20% หากคุณสร้างอินสแตนซ์อ็อบเจ็กต์ที่กำหนดเองส่วนกลางและเก็บไว้เป็นตัวแปรสคริปต์?
jpmc26

5

ไม่แปลกใจที่ไม่มีใครพูดถึงตัวเลือกง่ายๆนี้ (เทียบกับ 3 หรือใหม่กว่า) สำหรับการสร้างวัตถุที่กำหนดเอง:

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

ประเภทนี้จะเป็น PSCustomObject ไม่ใช่ประเภทที่กำหนดเองจริง แต่น่าจะเป็นวิธีที่ง่ายที่สุดในการสร้างวัตถุแบบกำหนดเอง


ดูบล็อกโพสต์นี้โดย Will Andersonเกี่ยวกับความแตกต่างของ PSObject และ PSCustomObject
CodeFox

@CodeFox เพิ่งสังเกตว่าลิงค์เสียตอนนี้
superjos

2
@superjos ขอบคุณสำหรับคำใบ้ ฉันไม่พบตำแหน่งใหม่ของโพสต์ อย่างน้อยการโพสต์ได้รับการสนับสนุนโดยการจัดเก็บ
CodeFox

2
ดูเหมือนว่าจะกลายเป็นหนังสือ Git ที่นี่ :)
superjos

4

มีแนวคิดของ 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

ทางเลือกอื่น (ที่ฉันทราบ) คือการกำหนดประเภทใน C # / VB.NET และโหลดแอสเซมบลีนั้นลงใน PowerShell เพื่อใช้งานโดยตรง

พฤติกรรมนี้ได้รับการสนับสนุนอย่างแน่นอนเนื่องจากอนุญาตให้สคริปต์หรือส่วนอื่น ๆ ของสคริปต์ของคุณทำงานร่วมกับออบเจ็กต์จริงได้


3

นี่คือเส้นทางที่ยากในการสร้างประเภทที่กำหนดเองและเก็บไว้ในคอลเลกชัน

$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

สัมผัสได้ดีกับการเพิ่มชื่อประเภทให้กับวัตถุ
oɔɯǝɹ

0

นี่เป็นอีกหนึ่งตัวเลือกที่ใช้ความคิดที่คล้ายกับการแก้ปัญหาดังกล่าว PSTypeName โดยJaykul (และยังต้อง PSv3 หรือสูงกว่า)

ตัวอย่าง

  1. สร้างไฟล์TypeName .Types.ps1xmlเพื่อกำหนดประเภทของคุณ เช่น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. นำเข้าประเภทของคุณ: Update-TypeData -AppendPath .\Person.Types.ps1xml
  2. สร้างวัตถุประเภทที่คุณกำหนดเอง: $p = [PSCustomType]@{PSTypeName='StackOverflow.Example.Person'}
  3. เริ่มต้นประเภทของคุณโดยใช้วิธีการสคริปต์ที่คุณกำหนดใน XML: $p.Initialize('Anne', 'Droid')
  4. ดูสิ; คุณจะเห็นคุณสมบัติทั้งหมดที่กำหนด:$p | Format-Table -AutoSize
  5. พิมพ์เรียก mutator เพื่ออัปเดตค่าคุณสมบัติ: $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 '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

ปล. สำหรับผู้ที่ใช้ VSCode คุณสามารถเพิ่ม PS1XML support: code.visualstudio.com/docs/languages/…
JohnLBevan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.