关于脚本:如何在PowerShell中创建自定义类型供脚本使用?

关于脚本:如何在PowerShell中创建自定义类型供脚本使用?

How do I create a custom type in PowerShell for my scripts to use?

我希望能够在某些PowerShell脚本中定义和使用自定义类型。 例如,假设我们需要一个具有以下结构的对象:

1
2
3
4
5
6
Contact
{
    string First
    string Last
    string Phone
}

我将如何创建它,以便可以在如下函数中使用它:

1
2
3
4
5
6
function PrintContact
{
    param( [Contact]$contact )
   "Customer Name is" + $contact.First +"" + $contact.Last
   "Customer Phone is" + $contact.Phone
}

是否有可能这样,甚至在PowerShell中建议这样做?


在PowerShell 3之前

PowerShell的可扩展类型系统最初不允许您创建可以根据参数设置方法进行测试的具体类型。如果您不需要该测试,则可以使用上述任何其他方法。

如果您想要一个可以转换为类型或进行类型检查的实际类型,例如在示例脚本中……除非将其写入C#或VB.net并进行编译,否则无法完成。在PowerShell 2中,您可以使用" Add-Type"命令来简化它:

1
2
3
4
5
6
7
add-type @"
public struct contact {
   public string First;
   public string Last;
   public string Phone;
}
"
@

历史记录:在PowerShell 1中,它甚至更难。您必须手动使用CodeDom,PoshCode.org上有一个非常老的函数new-struct脚本,可以帮助您。您的示例变为:

1
2
3
4
5
New-Struct Contact @{
    First=[string];
    Last=[string];
    Phone=[string];
}

使用Add-TypeNew-Struct将使您实际测试param([Contact]$contact)中的类,并使用$contact = new-object Contact创建新类,依此类推...

在PowerShell 3中

如果不需要可以转换为的"真实"类,则不必使用Steven和其他人在上面演示的Add-Member方法。

从PowerShell 2开始,您可以将-Property参数用于New-Object:

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

在PowerShell 3中,我们可以使用PSCustomObject加速器添加TypeName:

1
2
3
4
5
6
[PSCustomObject]@{
    PSTypeName ="Contact"
    First = $First
    Last = $Last
    Phone = $Phone
}

您仍然只得到一个对象,因此应该创建一个New-Contact函数以确保每个对象都相同,但是现在可以通过用修饰一个参数来轻松地验证参数"是"其中一种类型PSTypeName属性:

1
2
3
4
5
6
function PrintContact
{
    param( [PSTypeName("Contact")]$contact )
   "Customer Name is" + $contact.First +"" + $contact.Last
   "Customer Phone is" + $contact.Phone
}

在PowerShell 5中

在PowerShell 5中,一切都发生了变化,最后我们得到了classenum作为用于定义类型的语言关键字(没有struct可以):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()-实际上,如果使类保持简单并且不定义构造函数,则可以通过强制转换哈希表来创建对象(尽管没有构造函数,则无法强制必须设置所有属性):

1
2
3
4
5
6
7
8
9
10
11
12
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中创建自定义类型。
柯克·蒙罗(Kirk Munro)实际上有两个很棒的帖子,对过程进行了详尽的详细介绍。

  • 命名自定义对象
  • 定义自定义对象的默认属性

Manning撰写的《 Windows PowerShell In Action》一书还提供了一个代码示例,用于创建特定于域的语言来创建自定义类型。这本书非常出色,所以我很推荐。

如果您正在寻找一种完成上述操作的快速方法,则可以创建一个函数来创建自定义对象,例如

1
2
3
4
5
6
7
8
9
10
11
12
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
}

这是快捷方式:

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

史蒂文·穆拉夫斯基(Steven Murawski)的回答很好,但是我喜欢较短的答案(或者更简洁的选择对象,而不是使用添加成员语法):

1
2
3
4
5
6
7
8
9
10
11
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
}

没有人惊讶地提到创建自定义对象的简单方法(相对于3或更高版本):

1
2
3
4
5
[PSCustomObject]@{
    First = $First
    Last = $Last
    Phone = $Phone
}

该类型将是PSCustomObject,而不是实际的自定义类型。但这可能是创建自定义对象的最简单方法。


您可以使用PSObject和Add-Member的概念。

1
2
3
4
5
$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"

输出如下:

1
2
3
4
5
[8] ? $contact

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

另一个替代方法(我知道)是在C#/ VB.NET中定义一个类型,然后将该程序集加载到PowerShell中以直接使用。

绝对鼓励这种行为,因为它允许其他脚本或脚本的某些部分与实际对象一起使用。


这是创建自定义类型并将其存储在集合中的艰难途径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$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

这是另一个选项,它使用与Jaykul提到的PSTypeName解决方案类似的思想(因此也需要PSv3或更高版本)。

  • 创建一个TypeName.Types.ps1xml文件定义您的类型。例如。 Person.Types.ps1xml
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <?xml version="1.0" encoding="utf-8" ?>
    <Types>
      <Type>
        <Name>StackOverflow.Example.Person</Name>
        <Members>
          <ScriptMethod>
            <Name>Initialize</Name>
           
                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
           
          </ScriptMethod>
          <ScriptMethod>
            <Name>SetGivenName</Name>
           
                Param (
                    [Parameter(Mandatory = $true)]
                    [string]$GivenName
                )
                $this | Add-Member -MemberType 'NoteProperty' -Name 'GivenName' -Value $GivenName -Force
           
          </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>
  • 导入您的类型:Update-TypeData -AppendPath .\\Person.Types.ps1xml
  • 创建您的自定义类型的对象:$p = [PSCustomType]@{PSTypeName='StackOverflow.Example.Person'}
  • 使用在XML中定义的脚本方法初始化类型:$p.Initialize('Anne', 'Droid')
  • 看它;您将看到定义的所有属性:$p | Format-Table -AutoSize
  • 键入一个变量来更新属性值:$p.SetGivenName('Dan')
  • 再次查看以查看更新的值:$p | Format-Table -AutoSize
  • 说明

    • PS1XML文件允许您定义类型的自定义属性。
    • 正如文档所暗示的那样,它不限于.net类型。因此,您可以将自己喜欢的内容放在" / Types / Type / Name"中,使用匹配的" PSTypeName"创建的任何对象都将继承为此类型定义的成员。
    • 通过PS1XMLAdd-Member添加的成员仅限于NotePropertyAliasPropertyScriptPropertyCodePropertyScriptMethodCodeMethod(或PropertySet / MemberSet;尽管这些是受相同的限制)。所有这些属性都是只读的。
    • 通过定义ScriptMethod,我们可以欺骗上述限制。例如。我们可以定义一个方法(例如Initialize)来创建新属性,并为我们设置它们的值;从而确保我们的对象具有其他脚本运行所需的所有属性。
    • 我们可以使用相同的技巧来允许属性可更新(尽管通过方法而不是直接分配),如示例的SetGivenName所示。

    这种方法并非在所有情况下都理想。但对于将类类行为添加到自定义类型很有用/可以与其他答案中提到的其他方法结合使用。例如。在现实世界中,我可能只会在PS1XML中定义FullName属性,然后使用一个函数来创建具有所需值的对象,如下所示:

    更多信息

    查看文档或OOTB类型文件Get-Content
    $PSHome\\types.ps1xml
    以获得灵感。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    # 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


    推荐阅读