如何在PowerShell中创建自定义类型以供我的脚本使用?

时间:2008-09-12 19:43:52

标签: powershell scripting

我希望能够在我的一些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中推荐的吗?

7 个答案:

答案 0 :(得分:103)

PowerShell 3之前

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

如果你想要一个你可以强制转换或输入类型的实际类型,就像在你的示例脚本中一样......如果没有在C#或VB.net中编写它就不能 。在PowerShell 2中,您可以使用“添加类型”命令来完成它:

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-TypeNew-Struct可让您在param([Contact]$contact)中对该课程进行实际测试,并使用$contact = new-object Contact制作新课程等等...

在PowerShell 3中

如果您不需要可以投射到的“真实”课程,则不必使用上面Steven and others have demonstrated的添加成员方式。

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

答案 1 :(得分:57)

可以在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
}

答案 2 :(得分:16)

这是快捷方式:

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

答案 3 :(得分:9)

Steven Murawski的答案很棒,但是我喜欢较短的(或者更简洁的选择对象而不是使用add-member语法):

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
}

答案 4 :(得分: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中以便直接使用。

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

答案 5 :(得分: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

答案 6 :(得分:0)

这里还有一个选项,它使用与Jaykul提到的PSTypeName解决方案类似的思想(因此也需要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. 类型调用变量以更新属性值:$p.SetGivenName('Dan')
  6. 再次查看它以查看更新后的值:$p | Format-Table -AutoSize

说明

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

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

更多信息

看看documentation或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