Todo lo que le interesa sobre PSCustomObject
PSCustomObject
es una herramienta excelente para agregar al conjunto de herramientas de PowerShell. Vamos a empezar por lo básico y, después, nos adentraremos en características más avanzadas. La idea subyacente al uso de PSCustomObject
es tener una manera sencilla de crear datos estructurados. Eche un vistazo al primer ejemplo y tendrá una idea más clara de lo que eso significa.
Nota
La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin que comparta este contenido con nosotros. Visite su blog en PowerShellExplained.com.
Creación de un PSCustomObject
Me encanta usar [PSCustomObject]
en PowerShell. La creación de un objeto útil nunca había sido tan fácil.
Por eso, voy a omitir todas las demás formas en que se puede crear un objeto, pero necesito mencionar que la mayoría de estos ejemplos se aplican a PowerShell 3.0 y versiones posteriores.
$myObject = [PSCustomObject]@{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
Este método funciona bien porque utilizo tablas hash para casi todo. Sin embargo, hay ocasiones en las que me gustaría que PowerShell tratara las tablas hash más bien como un objeto. La primera vez que observa la diferencia es cuando desea utilizar Format-Table
o Export-CSV
y se da cuenta de que una tabla hash es simplemente una colección de pares clave-valor.
Después, puede acceder a los valores y usarlos como lo haría con un objeto normal.
$myObject.Name
Conversión de una tabla hash
Ahora que estoy tratando el tema, ¿sabía que podría hacer lo siguiente?
$myHashtable = @{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
$myObject = [pscustomobject]$myHashtable
Me gustaría crear el objeto desde el principio, pero hay ocasiones en las que hay que trabajar primero con una tabla hash. Este ejemplo funciona porque el constructor adopta una tabla hash para las propiedades del objeto. Una nota importante es que, aunque este método funciona, no es un equivalente exacto. La diferencia más importante es que el orden de las propiedades no se conserva.
Si desea conservar el orden, consulte Tablas hash ordenadas.
Enfoque heredado
Es posible que haya observado que las personas usan New-Object
para crear objetos personalizados.
$myHashtable = @{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
$myObject = New-Object -TypeName PSObject -Property $myHashtable
Esta opción es bastante más lenta, pero puede ser la mejor en las primeras versiones de PowerShell.
Guardar en un archivo
Considero que la mejor manera de guardar una tabla hash en un archivo es con formato JSON. Puede importarlo de nuevo en un objeto [PSCustomObject]
.
$myObject | ConvertTo-Json -depth 1 | Set-Content -Path $Path
$myObject = Get-Content -Path $Path | ConvertFrom-Json
Se abordan más opciones para guardar objetos en un archivo en mi artículo Las distintas formas de leer y escribir en archivos.
Trabajar con propiedades
Adición de propiedades
También puede agregar nuevas propiedades a PSCustomObject
con Add-Member
.
$myObject | Add-Member -MemberType NoteProperty -Name 'ID' -Value 'KevinMarquette'
$myObject.ID
Eliminación de propiedades
También puede quitar propiedades de un objeto.
$myObject.psobject.properties.remove('ID')
.psobject
es un miembro intrínseco que proporciona acceso a los metadatos del objeto base. Para obtener más información sobre miembros intrínsecos, vea about_Intrinsic_Members.
Enumeración de nombres de propiedad
A veces, se necesita una lista de todos los nombres de propiedad de un objeto.
$myObject | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name
También podemos obtener esta misma lista de la propiedad psobject
.
$myobject.psobject.properties.name
Nota:
Get-Member
devuelve las propiedades en orden alfabético. El uso del operador de acceso a miembros para enumerar los nombres de propiedad devuelve las propiedades en el orden en que se definieron en el objeto.
Acceso dinámico a las propiedades
Ya he mencionado que puede acceder a los valores de propiedad directamente.
$myObject.Name
Puede utilizar una cadena para el nombre de la propiedad y, de esta forma, seguirá funcionando.
$myObject.'Name'
Podemos dar un paso más y usar una variable para el nombre de la propiedad.
$property = 'Name'
$myObject.$property
Sé que parece extraño, pero funciona.
Conversión de PSCustomObject en una tabla hash
Para continuar desde la última sección, puede recorrer dinámicamente las propiedades y crear una tabla hash a partir de ellas.
$hashtable = @{}
foreach( $property in $myobject.psobject.properties.name )
{
$hashtable[$property] = $myObject.$property
}
Pruebas de propiedades
Si necesita saber si existe una propiedad, puede simplemente comprobar si esa propiedad tiene un valor.
if( $null -ne $myObject.ID )
Sin embargo, si el valor pudiese ser $null
, podría comprobar si existe buscándolo en psobject.properties
.
if( $myobject.psobject.properties.match('ID').Count )
Adición de métodos a objetos
Si necesita agregar un método de script a un objeto, puede hacerlo con Add-Member
y ScriptBlock
. Tiene que utilizar la referencia automática de variable this
al objeto actual. A continuación, se muestra un elemento scriptblock
para convertir un objeto en una tabla hash. (El mismo código forma el último ejemplo).
$ScriptBlock = {
$hashtable = @{}
foreach( $property in $this.psobject.properties.name )
{
$hashtable[$property] = $this.$property
}
return $hashtable
}
Después, lo agregamos al objeto como una propiedad de script.
$memberParam = @{
MemberType = "ScriptMethod"
InputObject = $myobject
Name = "ToHashtable"
Value = $scriptBlock
}
Add-Member @memberParam
Después, se puede llamar a la función de la siguiente manera:
$myObject.ToHashtable()
Objetos frente a tipos de valor
Los objetos y los tipos de valor no controlan las asignaciones de variables de la misma manera. Si asigna tipos de valor entre sí, solo el valor se copia en la nueva variable.
$first = 1
$second = $first
$second = 2
En este caso, $first
es 1 y $second
es 2.
Las variables de objeto contienen una referencia al objeto real. Cuando se asigna un objeto a una nueva variable, siguen haciendo referencia al mismo objeto.
$third = [PSCustomObject]@{Key=3}
$fourth = $third
$fourth.Key = 4
Dado que $third
y $fourth
hacen referencia a la misma instancia de un objeto, tanto $third.key
como $fourth.Key
son 4.
psobject.copy()
Si necesita una copia verdadera de un objeto, puede clonarlo.
$third = [PSCustomObject]@{Key=3}
$fourth = $third.psobject.copy()
$fourth.Key = 4
La clonación crea una copia superficial del objeto. Tienen instancias diferentes ahora, y $third.key
es 3 y $fourth.Key
es 4 en este ejemplo.
Yo lo denomino copia superficial porque, si tiene objetos anidados (objetos con propiedades que contienen otros objetos), solo se copian los valores del nivel superior. Los objetos secundarios se hacen referencia entre sí.
PSTypeName para tipos de objeto personalizados
Ahora que tenemos un objeto, hay algunas cosas más que podemos hacer con él que puede que no sean tan evidentes. Lo primero que debemos hacer es proporcionarle un elemento PSTypeName
. Esta es la opción más común que se suele utilizar:
$myObject.PSObject.TypeNames.Insert(0,"My.Object")
Recientemente he descubierto otra manera de hacerlo desde Redditor u/markekraus
. Habla de este enfoque que le permite definirlo en línea.
$myObject = [PSCustomObject]@{
PSTypeName = 'My.Object'
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
Me encanta lo bien que esto encaja en el lenguaje. Ahora que tenemos un objeto con un nombre de tipo adecuado, podemos hacer algunas cosas más.
Nota
También puede crear tipos de PowerShell personalizados mediante clases de PowerShell. Para obtener más información, vea la información general de las clases de PowerShell.
Uso de DefaultPropertySet (el modo largo)
PowerShell decide qué propiedades se van a mostrar de forma predeterminada. Muchos de los comandos nativos tienen un .ps1xml
archivo de formato que realiza todo el trabajo pesado. A partir de esta publicación de Boe Prox, hay otra manera de hacer esto en nuestro objeto personalizado con PowerShell exclusivamente. Podemos proporcionarle un elemento MemberSet
para que lo use.
$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
Ahora, cuando mi objeto solo se encuentra en el shell, solo mostrará esas propiedades de forma predeterminada.
Update-TypeData con DefaultPropertySet
Esto está bien, pero recientemente he visto una mejor manera de utilizar Update-TypeData para especificar las propiedades predeterminadas.
$TypeData = @{
TypeName = 'My.Object'
DefaultDisplayPropertySet = 'Name','Language'
}
Update-TypeData @TypeData
Eso es lo suficientemente sencillo que casi podría recordarlo si no tuviera esta publicación como referencia rápida. Ahora puedo crear fácilmente objetos con una gran cantidad de propiedades y seguir ofreciendo una buena imagen limpia al examinarlo desde el shell. Si necesito acceder a esas otras propiedades o verlas, todavía siguen ahí.
$myObject | Format-List *
Update-TypeData con ScriptProperty
Otra cosa que se extrajo de ese vídeo fue crear propiedades de script para los objetos. Este sería un buen momento para señalar que esto funciona también con los objetos existentes.
$TypeData = @{
TypeName = 'My.Object'
MemberType = 'ScriptProperty'
MemberName = 'UpperCaseName'
Value = {$this.Name.toUpper()}
}
Update-TypeData @TypeData
Puede hacerlo antes o después de crear el objeto y seguirá funcionando. Esto es lo que hace que esto sea diferente de usar Add-Member
con una propiedad de script. Cuando se usa Add-Member
de la forma en que se hizo referencia anteriormente, solo existe en esa instancia específica del objeto. Esto se aplica a todos los objetos con este elemento TypeName
.
Parámetros de función
Ahora puede usar estos tipos personalizados para los parámetros de sus funciones y scripts. Puede hacer que una función cree estos objetos personalizados y después pasarlos a otras funciones.
param( [PSTypeName('My.Object')]$Data )
PowerShell requiere que el objeto sea el tipo especificado. Produce un error de validación si el tipo no coincide automáticamente para ahorrarle el paso de probarlo en el código. Un buen ejemplo de permitir que PowerShell haga lo que mejor sabe hacer.
Función OutputType
También puede definir un elemento OutputType
para las funciones avanzadas.
function Get-MyObject
{
[OutputType('My.Object')]
[CmdletBinding()]
param
(
...
El valor del atributo OutputType solo es una nota de la documentación. No se deriva del código de función ni se compara con la salida real de la función.
La razón principal por la que usaría un tipo de salida es que la metainformación sobre su función refleje sus intenciones. Aspectos como Get-Command
y Get-Help
de los que el entorno de desarrollo puede beneficiarse. Si desea obtener más información, eche un vistazo a la ayuda relacionada: about_Functions_OutputTypeAttribute.
Dicho esto, si usa Pester para realizar pruebas unitarias de las funciones, sería una buena idea validar los objetos de salida que coinciden con OutputType. Esto podría detectar variables que solo se encuentran en la canalización cuando realmente no debería ser así.
Reflexiones finales
El contexto de esto trataba de [PSCustomObject]
, pero una gran parte de esta información se aplica a los objetos en general.
Antes he visto la mayoría de estas características de paso, pero nunca me las encontré como una colección de información sobre PSCustomObject
. Solo la semana pasada me topé con otra y me sorprendió no haberla visto antes. Quería reunir todas estas ideas, por lo que es posible que pueda ver la imagen más completa y tenerlas en cuenta cuando tenga la oportunidad de usarlas. Espero que haya aprendido algo y pueda encontrar una manera de ponerlo en práctica en sus scripts.