你想知道的有关 PSCustomObject 的一切

PSCustomObject 是一个很好的工具,可添加到 PowerShell 工具带中。 让我们从基础知识入手,并着手了解更高级的功能。 使用PSCustomObject的目的是提供一种简单的方法来创建结构化数据。 看看第一个示例,你将更好地了解这意味着什么。

注释

本文的 原始版本 出现在 @KevinMarquette撰写的博客上。 PowerShell 团队感谢 Kevin 与我们共享此内容。 请在 PowerShellExplained.com查看他的博客。

创建 PSCustomObject

我喜欢在 PowerShell 中使用 [pscustomobject] 。 创建可用对象从未变得容易。 因此,我将跳过创建对象的其他所有方法,但我需要提到,其中大多数示例都是 PowerShell v3.0 和更新的。

$myObject = [pscustomobject]@{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

这种方法非常适合我,因为我几乎把哈希表用到了所有事上。 但有时,我更希望 PowerShell 将哈希表视为一个对象。 当你想要使用 Format-TableExport-Csv 时,你会首先注意到区别,并意识到哈希表只是一个键/值对的集合。

然后,您可以像操作普通对象一样访问和使用这些值。

$myObject.Name

转换哈希表

既然说到这个话题,你知道你可以这样做吗:

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}
$myObject = [pscustomobject]$myHashtable

我宁愿从一开始就创建对象,但有时必须先使用哈希表。 此示例有效,因为构造函数为对象属性使用哈希表。 一个重要注意事项是,虽然此方法有效,但它不是完全等效的。 最大的区别是属性的顺序不会保留。

如果要保留顺序,请参阅已排序哈希表

传统方法

你可能已经看到人们用来 New-Object 创建自定义对象。

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

$myObject = New-Object -TypeName psobject -Property $myHashtable

这种方法要慢一点,但它可能是早期版本的 PowerShell 的最佳选择。

将数据保存到文件中

我发现将哈希表保存到文件的最佳方式是将其另存为 JSON。 可以将其导入回 [pscustomobject]

$myObject | ConvertTo-Json -Depth 1 | Set-Content -Path $Path
$myObject = Get-Content -Path $Path | ConvertFrom-Json

我在文章中介绍了将对象保存到文件的更多方法:读取和写入文件的方法。

使用属性

添加属性

你仍然可以使用Add-MemberPSCustomObject添加新属性。

$myObject | Add-Member -MemberType NoteProperty -Name 'ID' -Value 'KevinMarquette'

$myObject.ID

删除属性

还可以从对象中删除属性。

$myObject.psobject.Properties.Remove('ID')

.psobject这是一个内部成员,可用于访问基本对象元数据。 有关内部成员的详细信息,请参阅 about_Intrinsic_Members

枚举属性名称

有时需要对象上所有属性名称的列表。

$myObject | Get-Member -MemberType NoteProperty | select -ExpandProperty Name

也可以从 psobject 属性中获取此相同的列表。

$myobject.psobject.Properties.Name

注释

Get-Member 按字母顺序返回属性。 使用成员访问运算符来列举属性名称,并按它们在对象上定义的顺序返回这些属性。

动态访问属性

我已提到,你可以直接访问属性值。

$myObject.Name

可以将字符串用于属性名称,并且它仍然有效。

$myObject.'Name'

我们可以再执行一个步骤,并使用变量作为属性名称。

$property = 'Name'
$myObject.$property

我知道这看起来很奇怪,但它是可行的。

将 PSCustomObject 转换为哈希表

若要从最后一部分继续作,可以动态遍历属性并从中创建哈希表。

$hashtable = @{}
foreach( $property in $myobject.psobject.Properties.Name )
{
    $hashtable[$property] = $myObject.$property
}

测试属性

如果需要知道某个属性是否存在,可以检查该属性是否具有值。

if( $null -ne $myObject.ID )

如果该值可能是 $null,则可以通过检查 psobject.Properties 来确认它是否存在。

if( $myobject.psobject.Properties.Match('ID').Count )

添加对象方法

如果需要向对象添加脚本方法,可以使用 Add-Member 和 a ScriptBlock. 必须使用 this 自动变量引用当前对象。 下面是将对象转换为哈希表的 scriptblock。 (和上一个示例相同的代码形式)

$ScriptBlock = {
    $hashtable = @{}
    foreach( $property in $this.psobject.Properties.Name )
    {
        $hashtable[$property] = $this.$property
    }
    return $hashtable
}

然后将它作为脚本属性添加到对象。

$memberParam = @{
    MemberType = "ScriptMethod"
    InputObject = $myobject
    Name = "ToHashtable"
    Value = $scriptBlock
}
Add-Member @memberParam

然后,我们可以像这样调用函数:

$myObject.ToHashtable()

对象与值类型

对象和值类型不会以相同的方式处理变量赋值。 如果将值类型分配给对方,则仅将值复制到新变量。

$first = 1
$second = $first
$second = 2

在这种情况下, $first 为 1 且 $second 为 2。

对象变量保存对实际对象的引用。 将一个对象分配给新变量时,它们仍引用同一对象。

$third = [pscustomobject]@{Key=3}
$fourth = $third
$fourth.Key = 4

因为$third$fourth引用的是同一个对象实例,所以$third.key$fourth.Key都为4。

psobject.Copy()

如果需要对象的真实副本,可以克隆它。

$third = [pscustomobject]@{Key=3}
$fourth = $third.psobject.Copy()
$fourth.Key = 4

通过克隆可创建对象的卷影副本。 它们现在具有不同的实例, $third.key 且为 3, $fourth.Key 在此示例中为 4。

我称之为浅表副本,因为如果你有嵌套对象(具有属性的对象包含其他对象),则只会复制顶级值。 子对象将相互引用。

自定义对象类型的 PSTypeName

既然我们有了一个对象,我们还可以用它做一些可能不那么显而易见的事情。 我们需要做的第一件事是给它一个 PSTypeName。 这是我看到人们这样做的最常用方式:

$myObject.psobject.TypeNames.Insert(0,"My.Object")

我最近从雷迪托 u/markekraus发现了另一种方法来做到这一点。 他谈到这种让你能够内联定义它的方法。

$myObject = [pscustomobject]@{
    PSTypeName = 'My.Object'
    Name       = 'Kevin'
    Language   = 'PowerShell'
    State      = 'Texas'
}

这刚好适用于这种语言,我很喜欢。 现在我们有一个具有正确类型名称的对象,可以做一些其它事情了。

注释

还可以使用 PowerShell 类创建自定义 PowerShell 类型。 有关详细信息,请参阅 PowerShell 类概述

使用 DefaultPropertySet(绕远路)

PowerShell 决定默认显示哪些属性。 很多本机命令都有一个 .ps1xml格式化文件 ,用于执行所有繁重的工作。 从 Boe Prox 的此文章中,我们只需使用 PowerShell 即可在自定义对象上执行此作的另一种方法。 我们可以提供一个 MemberSet 供其使用。

$defaultDisplaySet = 'Name','Language'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
$MyObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers

现在,当我的对象刚好落到 shell 上时,默认情况下它将仅显示这些属性。

带有 DefaultPropertySet 的 Update-TypeData

这很好,但我最近看到了使用 Update-TypeData 指定默认属性的更好方法。

$TypeData = @{
    TypeName = 'My.Object'
    DefaultDisplayPropertySet = 'Name','Language'
}
Update-TypeData @TypeData

这非常简单,即使我没有此文章作为快速参考,也差不多能记住它。 现在,我可以轻松地创建具有大量属性的对象,并且当从 shell 查看对象时,依然能获得一个整洁的视图。 如果需要访问或查看这些其他属性,它们仍然存在。

$myObject | Format-List *

带有 ScriptProperty 的 Update-TypeData

我从视频中得到的其他内容是为对象创建脚本属性。 借此机会可以指明,这也适用于现有对象。

$TypeData = @{
    TypeName = 'My.Object'
    MemberType = 'ScriptProperty'
    MemberName = 'UpperCaseName'
    Value = {$this.Name.ToUpper()}
}
Update-TypeData @TypeData

可以在创建对象之前或之后进行此操作,操作仍然有效。 这就是使其不同于将 Add-Member 与脚本属性一起使用的原因。 使用 Add-Member 前面引用的方式时,它仅存在于该对象的特定实例上。 这一项适用于具有此 TypeName对象的所有对象。

函数参数

现在可以将这些自定义类型用于函数和脚本中的参数。 可以让一个函数创建这些自定义对象,然后将其传递到其他函数中。

param( [PSTypeName('My.Object')]$Data )

PowerShell 要求对象是指定的类型。 如果该类型不能自动匹配,则会引发验证错误,从而省去你在代码中执行测试的步骤。 关于让 PowerShell 执行其擅长工作的一个很好的例子。

函数 OutputType

还可以为高级函数定义一个 OutputType

function Get-MyObject
{
    [OutputType('My.Object')]
    [CmdletBinding()]
        param
        (
            ...

OutputType 属性值只是文档备注。 它不派生自函数代码或与实际函数输出进行比较。

使用输出类型的主要原因是,有关函数的元信息反映了你的意图。 你的开发环境可以利用 Get-CommandGet-Help 之类。 如果需要更多信息,请查看其帮助: about_Functions_OutputTypeAttribute

也就是说,如果使用 Pester 对函数进行单元测试,最好验证输出对象是否与 OutputType 匹配。 这可能会捕获那些不应该落入管道的变量。

结束语

这一切的上下文都是关于 [pscustomobject]的,但很多此信息都适用于一般对象。

我以前只是匆匆看到过这些功能,但从来没有看到过它们以一种信息集合的形式在PSCustomObject上出现。 就在上周,我偶然发现了另一个,惊讶于我以前没有见过它。 我想把这些想法汇总在一起,这样当你有机会使用它们时,就能看到整体情况并意识到这些想法。 我希望你学到了一些东西,并可以找到一种方法将其融入到你的脚本中。