내 스크립트에서 사용할 사용자 지정 형식을 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에서 권장됩니까?

답변:


133

PowerShell 3 이전

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을 수동으로 사용해야했습니다. 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에서

캐스팅 할 수있는 "실제"클래스가 필요하지 않은 경우 Steven과 다른 사람들이 위에서 보여준 Add-Member 방식을 사용할 필요가 없습니다 .

PowerShell 2부터 New-Object에 -Property 매개 변수를 사용할 수 있습니다.

$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 명 모든 변경, 우리는 마침내 도착에서 classenum유형을 정의하기위한 언어 키워드 (어떤 거기의로 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에서 작동 메모 추가 : 새로운 객체 PSObject -Property를 @ {... 여기 소품}
라이언 Shillington

2
초기 PowerShell 5 릴리스에서는 클래스 구문을 사용하여 만든 클래스에 New-Object를 사용할 수 없었지만 지금은 가능합니다. 그러나 class 키워드를 사용하는 경우 스크립트는 어쨌든 PS5로만 제한되므로 개체에 매개 변수를 사용하는 생성자가있는 경우 ( New-Object보다 훨씬 빠름) :: new 구문을 사용하는 것이 좋습니다. 그렇지 않으면 더 깔끔한 구문이고 더 빠릅니다.
Jaykul

당신이 있습니까 확인 유형 검사를 사용하여 생성 된 유형 수행 할 수 없습니다 Add-Type? Win 2008 R2의 PowerShell 2에서 작동하는 것 같습니다. 대답에서 as를 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("...")]가능합니다. 유형을 문자열로 지정할 수 있는 속성이 있습니다.이 속성은 PSTypeNames가 설정된 PSCustomObjects에 대한 테스트를 지원합니다 ...
Jaykul

58

PowerShell에서 사용자 지정 형식을 만들 수 있습니다.
Kirk Munro에는 실제로 프로세스를 자세히 설명하는 두 개의 훌륭한 게시물이 있습니다.

Manning의 Windows PowerShell In Action에는 사용자 지정 형식을 만들기위한 도메인 별 언어를 만드는 코드 샘플도 있습니다. 이 책은 모든면에서 훌륭하기 때문에 정말 추천합니다.

위의 작업을 빠르게 수행하는 방법을 찾고 있다면 다음과 같은 사용자 지정 개체를 만드는 함수를 만들 수 있습니다.

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
기본적으로 Select-Object cmdlet은 개체에 해당 속성이 아직없는 경우 제공되는 개체에 속성을 추가합니다. 이 경우 빈 문자열 개체를 Select-Object cmdlet에 전달합니다. 속성을 추가하고 파이프를 따라 개체를 전달합니다. 또는 파이프의 마지막 명령 인 경우 개체를 출력합니다. 프롬프트에서 작업하는 경우에만이 방법을 사용한다는 점을 지적해야합니다. 스크립트의 경우 항상보다 명시적인 Add-Member 또는 New-Object cmdlet을 사용합니다.
EBGreen 2013

:이 멋진 트릭이지만 실제로는도 단축 할 수 있습니다$myPerson = 1 | Select First,Last,Phone
RaYell

이것은 각 멤버의 유형을 문자열로 설정하므로 기본 유형 함수를 사용할 수 없습니다. A와 각 구성원 노트, 위의 Jaykul 기여 보여 주어 NotePropertystring유형, 그것은 것입니다 Property당신이 개체에 할당 한 어떤 유형. 이것은 빠르며 작업을 수행합니다.
mbrownnyc

이것은 Length 속성을 원하는 경우 문제를 일으킬 수 있습니다. 문자열에 이미 속성이 있고 새 개체가 기존 값을 가져 오므로 원하지 않을 수도 있습니다. @RaYell이 보여주는 것처럼 [int]를 전달하는 것이 좋습니다.
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 2011 년

1
예,하지만 위의 EBGreen과 동일합니다. 이것은 다음과 같이 입력하면 볼 수있는 일종의 이상한 기본 유형 (예 : Int32)을 만듭니다. $ person | gm. 기본 유형이 PSCustomObject 인 것을 선호합니다
Nick Meldrum

2
나는 요점을 본다. 그럼에도 불구하고 방법에는 분명한 이점이 int있습니다. 1) 더 빠르게 작동하지만,이 특정 기능의 New-Person경우 차이는 20 %입니다. 2) 타이핑하기가 더 쉽습니다. 동시에 기본적으로 모든 곳에서이 접근 방식을 사용하여 단점을 본 적이 없습니다. 하지만 동의합니다. 드물게 PSCustomObject가 더 나은 경우가있을 수 있습니다.
Roman Kuzmin 2011 년

@RomanKuzmin 전역 사용자 정의 개체를 인스턴스화하고 스크립트 변수로 저장하면 여전히 20 % 더 빠릅니까?
jpmc26

5

놀랍게도 아무도 사용자 지정 개체를 만드는 데이 간단한 옵션 (3 이상)을 언급하지 않았습니다.

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

유형은 실제 사용자 정의 유형이 아니라 PSCustomObject입니다. 그러나 이것은 아마도 사용자 정의 개체를 만드는 가장 쉬운 방법 일 것입니다.


PSObject와 PSCustomObject의 차이점에 대한 Will Anderson 의이 블로그 게시물을 참조하십시오 .
CodeFox 2015

@CodeFox는 방금 링크가 끊어진 것을 발견했습니다
superjos

2
@superjos, 힌트 주셔서 감사합니다. 게시물의 새 위치를 찾을 수 없습니다. 적어도 게시물은 아카이브에 의해 백업되었습니다 .
CodeFox 2017

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ɔɯǝɹ 2014 dec.

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 지원을 추가 할 수 있습니다 code.visualstudio.com/docs/languages/...
JohnLBevan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.