95

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

4

8 回答 8

151

在 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-TypeorNew-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"
}
于 2008-09-15T20:36:56.360 回答
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
}
于 2008-09-12T21:13:15.300 回答
18

这是快捷方法:

$myPerson = "" | Select-Object First,Last,Phone
于 2008-09-14T13:05:17.047 回答
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
}
于 2011-01-12T10:11:43.897 回答
6

惊讶于没有人提到这个用于创建自定义对象的简单选项(vs 3 或更高版本):

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

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

于 2015-09-10T15:56:43.153 回答
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 中以直接使用。

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

于 2008-09-12T20:36:22.360 回答
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
于 2014-12-15T20:03:19.490 回答
1

这里还有一个选项,它使用了与 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. 键入调用 mutator 来更新属性的值:$p.SetGivenName('Dan')
  6. 再次查看它以查看更新后的值:$p | Format-Table -AutoSize

解释

  • PS1XML 文件允许您定义类型的自定义属性。
  • 正如文档所暗示的那样,它不限于 .net 类型;所以你可以把你喜欢的东西放在'/Types/Type/Name'中,任何用匹配的'PSTypeName'创建的对象都将继承为此类型定义的成员。
  • 通过PS1XMLor添加的成员Add-Member仅限于NoteProperty, AliasProperty, ScriptProperty, CodeProperty, ScriptMethod, 和CodeMethod(或PropertySet/ MemberSet;尽管它们受到相同的限制)。所有这些属性都是只读的。
  • 通过定义 aScriptMethod我们可以欺骗上述限制。例如,我们可以定义一个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
于 2019-08-13T16:14:27.813 回答