PSCustomObject
是一个很好的工具,可添加到 PowerShell 工具带中。 让我们从基础知识入手,并着手了解更高级的功能。 使用PSCustomObject
的目的是提供一种简单的方法来创建结构化数据。 看看第一个示例,你将更好地了解这意味着什么。
注释
本文的 原始版本 出现在 @KevinMarquette撰写的博客上。 PowerShell 团队感谢 Kevin 与我们共享此内容。 请在 PowerShellExplained.com查看他的博客。
创建 PSCustomObject
我喜欢在 PowerShell 中使用 [pscustomobject]
。 创建可用对象从未变得容易。
因此,我将跳过创建对象的其他所有方法,但我需要提到,其中大多数示例都是 PowerShell v3.0 和更新的。
$myObject = [pscustomobject]@{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
这种方法非常适合我,因为我几乎把哈希表用到了所有事上。 但有时,我更希望 PowerShell 将哈希表视为一个对象。 当你想要使用 Format-Table
或 Export-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-Member
向PSCustomObject
添加新属性。
$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-Command
和 Get-Help
之类。 如果需要更多信息,请查看其帮助: about_Functions_OutputTypeAttribute。
也就是说,如果使用 Pester 对函数进行单元测试,最好验证输出对象是否与 OutputType 匹配。 这可能会捕获那些不应该落入管道的变量。
结束语
这一切的上下文都是关于 [pscustomobject]
的,但很多此信息都适用于一般对象。
我以前只是匆匆看到过这些功能,但从来没有看到过它们以一种信息集合的形式在PSCustomObject
上出现。 就在上周,我偶然发现了另一个,惊讶于我以前没有见过它。 我想把这些想法汇总在一起,这样当你有机会使用它们时,就能看到整体情况并意识到这些想法。 我希望你学到了一些东西,并可以找到一种方法将其融入到你的脚本中。