Komut dosyalarımın kullanması için PowerShell'de nasıl özel bir tür oluşturabilirim?


88

Bazı PowerShell betiklerimde özel bir tür tanımlayıp kullanabilmek istiyorum. Örneğin, aşağıdaki yapıya sahip bir nesneye ihtiyacım olduğunu varsayalım:

Contact
{
    string First
    string Last
    string Phone
}

Bunu aşağıdaki gibi bir işlevde kullanabilmek için nasıl oluşturabilirim:

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

Bunun gibi bir şey mümkün mü, hatta PowerShell'de öneriliyor mu?

Yanıtlar:


135

PowerShell 3'ten önce

PowerShell'in Genişletilebilir Tip Sistemi, başlangıçta parametrenizde yaptığınız şekilde test edebileceğiniz somut türler oluşturmanıza izin vermedi. Bu teste ihtiyacınız yoksa, yukarıda belirtilen diğer yöntemlerden herhangi birinde sorun yok.

Örnek betiğinizde olduğu gibi çevirebileceğiniz veya yazarak kontrol edebileceğiniz gerçek bir tür istiyorsanız ... C # veya VB.net'e yazmadan ve derlemeden yapılamaz. PowerShell 2'de, bunu oldukça basit bir şekilde yapmak için "Add-Type" komutunu kullanabilirsiniz:

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

Tarihsel Not : PowerShell 1'de daha da zordu. CodeDom'umanuel olarak kullanmanız gerekiyordu, PoshCode.org'da yardımcı olacakçok eski bir işlev yeni yapı betiği var. Örneğiniz şöyle olur:

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

Add-TypeVeya kullanmak New-Struct, sınıfınızı gerçekten test etmenize param([Contact]$contact)ve kullanarak yenilerini yapmanıza izin verir $contact = new-object Contactve benzeri ...

PowerShell 3'te

Cast yapabileceğiniz "gerçek" bir sınıfa ihtiyacınız yoksa, Steven ve diğerlerinin yukarıda gösterdiği Üye Ekle yöntemini kullanmak zorunda değilsiniz .

PowerShell 2'den bu yana New-Object için -Property parametresini kullanabilirsiniz:

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

Ve PowerShell 3'te, PSCustomObjecthızlandırıcıyı TypeName eklemek için kullanma yeteneğine sahibiz :

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

Hâlâ tek bir nesne alıyorsunuz, bu nedenle New-Contacther nesnenin aynı şekilde çıktığından emin olmak için bir işlev yapmalısınız, ancak artık bir parametreyi PSTypeNameöznitelikle süsleyerek bir parametrenin bu türlerden biri olduğunu kolayca doğrulayabilirsiniz :

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

PowerShell 5'te

PowerShell 5 her şey değişir, ve nihayet got içinde classve enumtürlerini tanımlamak için dil anahtar (hiçbir olduğu gibi structama sorun değil):

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

Ayrıca kullanmadan nesneler oluşturmanın yeni bir yolunu da bulduk New-Object: [Contact]::new()- aslında, sınıfınızı basit tuttuysanız ve bir kurucu tanımlamadıysanız, bir hashtable oluşturarak nesneler yaratabilirsiniz (ancak bir kurucu olmadan hiçbir şekilde olmazdı) tüm özelliklerin ayarlanmasını zorunlu kılmak için):

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

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

Mükemmel cevap! Bu stilin komut dosyaları için çok kolay olduğunu ve PowerShell 5: Yeni Nesne PSObject -Property @ {prop here ...}
Ryan Shillington

2
PowerShell 5'in ilk sürümlerinde, sınıf sözdizimi kullanılarak oluşturulan sınıflarda New-Object kullanamazsınız, ancak şimdi yapabilirsiniz. ANCAK, class anahtar sözcüğünü kullanıyorsanız, betiğiniz zaten sadece PS5 ile sınırlıdır, bu nedenle nesnenin parametreleri alan bir kurucusu varsa ( New-Object'ten çok daha hızlıdır) veya aksi takdirde, hem daha temiz sözdizimi hem de daha hızlıdır.
Jaykul

Kullanılarak oluşturulan türlerle tür denetiminin yapılamayacağından emin misiniz Add-Type? Win 2008 R2'de PowerShell 2'de çalışıyor gibi görünüyor. Ben algıladığına Say contactkullanarak Add-TypeCevabınız olduğu gibi ve daha sonra bir örneğini oluşturun: $con = New-Object contact -Property @{ First="a"; Last="b"; Phone="c" }. Daha sonra bu işlevi çağırmak çalışır:, function x([contact]$c) { Write-Host ($c | Out-String) $c.GetType() }ancak bu işlevi çağırmak başarısız olur x([doesnotexist]$c) { Write-Host ($c | Out-String) $c.GetType() }. Arama x 'abc'ayrıca, yayınlama ile ilgili uygun bir hata mesajıyla başarısız olur. PS 2 ve 4'te test edildi
jpmc26

Elbette Add-Type@ jpmc26 ile oluşturulmuş türleri kontrol edebilirsiniz , bunu derlemeden (yani: C # ile yazmadan ve çağırmadan Add-Type) yapamayacağınızı söyledim . Tabii ki, PS3 dan yapabilirsiniz - bir var [PSTypeName("...")]sen destekler set PSTypeNames ile PSCustomObjects karşı test bir dize olarak türünü belirlemenizi sağlar nitelik ...
Jaykul

58

Özel türler oluşturmak PowerShell'de yapılabilir.
Kirk Munro'nun aslında süreci ayrıntılı olarak detaylandıran iki harika yazısı var.

Manning tarafından hazırlanan Windows PowerShell In Action kitabı , özel türler oluşturmak için etki alanına özel bir dil oluşturmak için bir kod örneğine de sahiptir. Kitap her yerde mükemmel, bu yüzden gerçekten tavsiye ederim.

Yukarıdakileri yapmanın hızlı bir yolunu arıyorsanız, özel nesneyi oluşturmak için bir işlev oluşturabilirsiniz.

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

Bu kısayol yöntemidir:

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

3
Temel olarak, Select-Object cmdlet'i, nesnenin zaten bu özelliğe sahip olmaması durumunda kendisine verilen özellikleri ekler. Bu durumda, Select-Object cmdlet'ine boş bir String nesnesi veriyorsunuz. Özellikleri ekler ve nesneyi boru boyunca iletir. Veya borudaki son komutsa, nesneyi çıkarır. Bu yöntemi yalnızca komut isteminde çalışıyorsam kullandığımı belirtmeliyim. Komut dosyaları için her zaman daha açık Üye Ekle veya Yeni Nesne cmdlet'lerini kullanırım.
EBGreen

Bu harika bir numara olsa da, aslında daha da $myPerson = 1 | Select First,Last,Phone
kısaltabilirsiniz

Bu, her üyenin türünü dizge olarak ayarladığı için yerel tür işlevlerini kullanmanıza izin vermez. Bir şekilde her üye notu, yukarıda Jaykul katkısı ortaya Verilen NotePropertybir stringtürü, bir olduğunu Propertysize nesnede atadığınız türü fark etmeksizin. Bu hızlı ve işi yine de yapıyor.
mbrownnyc

Bir Length özelliği istiyorsanız, bu size sorunlar verebilir, çünkü dizge zaten buna sahiptir ve yeni nesneniz, muhtemelen istemediğiniz mevcut değeri alacaktır. @RaYell'in gösterdiği gibi bir [int] geçmenizi öneririm.
FSCKur

9

Steven Murawski'nin cevabı harika, ancak ben daha kısa olanı (veya daha doğrusu add-üye sözdizimini kullanmak yerine sadece daha düzgün seçim nesnesini) seviyorum:

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-Objectgerekli bile değil. Bu aynı şeyi yapacak:... = 1 | select-object First, Last, Phone
Roman Kuzmin

1
Evet, ancak yukarıdaki EBGreen ile aynı - bu, yazmış olsanız da göreceğiniz gibi bir tür tuhaf temel tür oluşturur (örneğinizde bu bir Int32 olacaktır.): $ Person | gm. Temel türün PSCustomObject olmasını tercih ediyorum
Nick Meldrum

2
Noktayı anlıyorum. Yine de, intyolun bariz avantajları vardır : 1) çok fazla değil, daha hızlı çalışır, ancak bu özel işlev New-Personiçin fark% 20'dir; 2) Görünüşe göre yazmak daha kolay. Aynı zamanda, bu yaklaşımı temelde her yerde kullanmak, herhangi bir dezavantaj görmedim. Ama aynı fikirdeyim: PSCustomObject'in biraz daha iyi olduğu bazı nadir durumlar olabilir.
Roman Kuzmin

@RomanKuzmin Global bir özel nesneyi başlatıp bunu bir komut dosyası değişkeni olarak sakladığınızda hala% 20 daha mı hızlı?
jpmc26

5

Özel nesneler oluşturmak için bu basit seçenekten (3 veya daha sonra) kimsenin bahsetmediğine şaşırdım:

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

Tür, gerçek bir özel tür değil, PSCustomObject olacaktır. Ancak, özel bir nesne oluşturmanın muhtemelen en kolay yolu budur.


PSObject ve PSCustomObject arasındaki fark hakkında Will Anderson tarafından yazılan bu blog gönderisine de bakın .
CodeFox 01

@CodeFox, bağlantının artık koptuğunu fark etti
superjos

2
@superjos, ipucu için teşekkürler. Gönderinin yeni konumunu bulamadım. En azından gönderi arşiv tarafından yedeklendi .
CodeFox

2
görünüşe göre burada bir Git kitabına dönüştü :)
superjos

4

Kullanabileceğiniz PSObject ve Add-Member kavramı var.

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

Bu, aşağıdaki gibi çıktılar:

[8] » $contact

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

Diğer alternatif (bildiğim kadarıyla), C # / VB.NET'te bir tür tanımlamak ve bu derlemeyi doğrudan kullanım için PowerShell'e yüklemek.

Bu davranış kesinlikle teşvik edilir çünkü komut dosyanızın diğer komut dosyalarının veya bölümlerinin gerçek bir nesneyle çalışmasına izin verir.


3

İşte özel türler oluşturmanın ve bunları bir koleksiyonda depolamanın zor yolu.

$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

Nesneye tür adını ekleyerek hoş bir dokunuş.
2014

0

İşte bahsettiği PSTypeName çözümüne benzer bir düşünce kullanan bir diğer seçenektir var Jaykul (ve dolayısıyla da yukarıda PSv3 veya gerektirir).

Misal

  1. Türünüzü tanımlayan bir TypeName .Types.ps1xml dosyası oluşturun . Örneğin 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. Türünüzü içe aktarın: Update-TypeData -AppendPath .\Person.Types.ps1xml
  2. Özel türünüzde bir nesne oluşturun: $p = [PSCustomType]@{PSTypeName='StackOverflow.Example.Person'}
  3. XML'de tanımladığınız komut dosyası yöntemini kullanarak türünüzü başlatın: $p.Initialize('Anne', 'Droid')
  4. Ona bak; tanımlanmış tüm özellikleri göreceksiniz:$p | Format-Table -AutoSize
  5. Bir özelliğin değerini güncellemek için bir mutatör çağırarak yazın: $p.SetGivenName('Dan')
  6. Güncellenen değeri görmek için tekrar bakın: $p | Format-Table -AutoSize

Açıklama

  • PS1XML dosyası, türlerde özel özellikler tanımlamanıza olanak tanır.
  • Belgelerin ima ettiği gibi .net türleriyle sınırlı değildir; böylece '/ Types / Type / Name' içine istediğinizi koyabilirsiniz, eşleşen bir 'PSTypeName' ile oluşturulan herhangi bir nesne bu tip için tanımlanan üyeleri miras alır.
  • Üye yoluyla eklenmiş PS1XMLveya Add-Memberkısıtlanır NoteProperty, AliasProperty, ScriptProperty, CodeProperty, ScriptMethod, ve CodeMethod(veya PropertySet/ MemberSet; o, aynı kısıtlamalara tabidir rağmen). Bu özelliklerin tümü salt okunurdur.
  • A tanımlayarak ScriptMethodyukarıdaki kısıtlamayı hile yapabiliriz. Örneğin Initialize, yeni özellikler yaratan, değerlerini bizim için belirleyen bir yöntem tanımlayabiliriz (örneğin ); böylece nesnemizin diğer betiklerimizin çalışması için ihtiyaç duyduğumuz tüm özelliklere sahip olmasını sağlamak.
  • Örneklerde gösterildiği gibi özelliklerin güncellenebilir olmasına izin vermek için (doğrudan atama yerine yöntem yoluyla da olsa) bu aynı numarayı kullanabiliriz SetGivenName.

Bu yaklaşım tüm senaryolar için ideal değildir; ancak özel türlere sınıf benzeri davranışlar eklemek için kullanışlıdır / diğer yanıtlarda belirtilen diğer yöntemlerle birlikte kullanılabilir. Örneğin, gerçek dünyada muhtemelen sadece FullNamePS1XML'de özelliği tanımlar , ardından nesneyi gerekli değerlerle oluşturmak için bir işlev kullanırdım, örneğin:

Daha fazla bilgi

İlham almak için belgelere veya OOTB tipi dosyaya bir göz atın 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

ps. VSCode kullananlar için, PS1XML desteği ekleyebilirsiniz: code.visualstudio.com/docs/languages/…
JohnLBevan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.