Compartir a través de


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 .ps1xmlarchivo 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.