Tout ce que vous avez toujours voulu savoir sur PSCustomObject

PSCustomObject est un très bon outil à ajouter à votre gamme d’outils PowerShell. Commençons par les concepts de base, puis nous passerons aux fonctionnalités plus avancées. L’idée sous-jacente de l’utilisation d’un PSCustomObject est d’avoir un moyen simple pour créer des données structurées. Jetez un coup d’œil au premier exemple et vous aurez une meilleure idée de ce que cela signifie.

Notes

La version originale de cet article est parue sur le blog écrit par @KevinMarquette. L’équipe PowerShell remercie Kevin d’avoir partagé ce contenu. Consultez son blog sur PowerShellExplained.com.

Création d’un PSCustomObject

J’aime bien utiliser [PSCustomObject] dans PowerShell. La création d’un objet utilisable n’a jamais été aussi facile. Pour cette raison, je vais passer sous silence toutes les autres façons de créer un objet, mais je dois mentionner que la plupart de ces exemples sont en PowerShell 3.0 et ultérieur.

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

Cette méthode fonctionne bien pour moi, car j’utilise des tables de hachage pour à peu près tout. Mais il y a des fois où je voudrais que PowerShell traite les tables de hachage comme un objet. Là où vous remarquez d’abord la différence est quand vous voulez utiliser Format-Table ou Export-CSV, et que vous réalisez qu’une table de hachage est simplement une collection de paires clé/valeur.

Vous pouvez ensuite accéder aux valeurs et les utiliser comme vous le feriez avec un objet normal.

$myObject.Name

Conversion d’une table de hachage

Tant que nous traitons de ce sujet : savez-vous que vous pouvez faire ceci :

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

Je préfère créer l’objet entièrement, mais vous devez parfois utiliser d’abord une table de hachage. Cet exemple fonctionne, car le constructeur prend une table de hachage pour les propriétés de l’objet. Il est important de remarquer que si cette méthode fonctionne, elle n’est pas exactement équivalente. La plus grande différence est que l’ordre des propriétés n’est pas conservé.

Si vous souhaitez conserver l’ordre, consultez Tables de hachage ordonnées.

Approche héritée

Vous avez peut-être vu des personnes utiliser New-Object pour créer des objets personnalisés.

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

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

Cette façon de faire est un peu lente, mais elle peut être votre meilleure option sur les premières versions de PowerShell.

Enregistrement dans un fichier

Je trouve que la meilleure façon d’enregistrer une table de hachage dans un fichier est de l’enregistrer au format JSON. Vous pouvez la réimporter dans un [PSCustomObject]

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

Je présente d’autres moyens d’enregistrer des objets dans un fichier dans mon article sur Les nombreuses façons de lire et d’écrire des fichiers.

Utilisation de propriétés

Ajout de propriétés

Vous pouvez toujours ajouter de nouvelles propriétés à votre PSCustomObject avec Add-Member.

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

$myObject.ID

Supprimer des propriétés

Vous pouvez aussi supprimer des propriétés dans un objet.

$myObject.psobject.properties.remove('ID')

.psobject est un membre intrinsèque qui vous permet d’accéder aux métadonnées de l’objet de base. Pour plus d’informations sur les membres intrinsèques, consultez about_Intrinsic_Members.

Énumération des noms des propriétés

Vous avez parfois besoin d’une liste de tous les noms des propriétés sur un objet.

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

Nous pouvons également obtenir cette même liste à partir de la propriété psobject.

$myobject.psobject.properties.name

Remarque

Get-Member renvoie les propriétés dans l’ordre alphabétique. L’utilisation de l’opérateur d’accès aux membres pour énumérer les noms de propriété permet de renvoyer les propriétés dans l’ordre de leur définition sur l’objet.

Accès dynamique aux propriétés

J’ai déjà mentionné que vous pouvez accéder directement aux valeurs des propriétés.

$myObject.Name

Vous pouvez aussi utiliser une chaîne pour le nom de la propriété.

$myObject.'Name'

Nous pouvons aller plus loin et utiliser une variable pour le nom de la propriété.

$property = 'Name'
$myObject.$property

Je sais que ça paraît étrange, mais cela fonctionne.

Convertir PSCustomObject en table de hachage

Pour continuer à partir de la dernière section, vous pouvez parcourir dynamiquement les propriétés et créer une table de hachage à partir de celles-ci.

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

Test des propriétés

Si vous avez besoin de savoir si une propriété existe, vous pouvez simplement vérifier que cette propriété a une valeur.

if( $null -ne $myObject.ID )

Toutefois, comme la valeur peut être $null, vous pouvez vérifier si elle existe en la recherchant dans psobject.properties.

if( $myobject.psobject.properties.match('ID').Count )

Ajout de méthodes d’objet

Si vous devez ajouter une méthode de script à un objet, vous pouvez le faire avec Add-Member et un ScriptBlock. Vous devez utiliser la variable automatique this pour référencer l’objet actif. Voici un scriptblock pour transformer un objet en table de hachage. (même code que dans le dernier exemple)

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

Ensuite, nous l’ajoutons à notre objet en tant que propriété de script.

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

Nous pouvons ensuite appeler notre fonction comme suit :

$myObject.ToHashtable()

Objets et types valeur

Les objets et les types valeur ne gèrent pas les affectations de variables de la même façon. Si vous affectez des types valeur entre eux, seule la valeur est copiée dans la nouvelle variable.

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

Dans ce cas, $first vaut 1 et $second vaut 2.

Les variables d’objet contiennent une référence à l’objet réel. Quand vous affectez un objet à une nouvelle variable, elle fait toujours référence au même objet.

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

Comme $third et $fourth référencent la même instance d’un objet, $third.key et $fourth.Key valent tous deux 4.

psobject.copy()

Si vous avez besoin d’une copie réelle d’un objet, vous pouvez le cloner.

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

Le clonage crée une copie superficielle de l’objet. Ils ont maintenant des instances différentes et dans cet exemple, $third.key vaut 3 et $fourth.Key vaut 4.

J’appelle cela une copie superficielle, car seules les valeurs de niveau supérieur sont copiées s’il existe des objets imbriqués (objets avec des propriétés contenant d’autres objets). Les objets enfants se référencent mutuellement.

PSTypeName pour les types d’objets personnalisés

Maintenant que nous disposons d’un objet, nous pouvons effectuer quelques autres opérations qui ne sont peut-être pas si évidentes. La première chose à faire est de lui donner un PSTypeName. Voici comment je vois procéder la plupart des gens :

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

J’ai récemment découvert une autre façon de le faire à partir de ce Posté par /u/markekraus. J’ai un peu investigué et j’ai trouvé d’autres billets sur l’idée de Adam Bertram et Mike Shepard, où ils parlent de cette approche qui vous permet de le définir inline.

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

J’aime bien la façon dont cela s’intègre parfaitement dans le langage. Maintenant que nous disposons d’un objet avec un nom de type correct, nous pouvons effectuer d’autres opérations.

Notes

Vous pouvez également créer des types PowerShell personnalisés à l’aide de classes PowerShell. Pour plus d’informations, consultez Présentation des classes PowerShell.

Utilisation de DefaultPropertySet (la méthode longue)

PowerShell décide pour nous des propriétés à afficher par défaut. Un grand nombre de commandes natives ont un .ps1xmlfichier de mise en forme qui effectue tout ce gros travail de mise en forme. Selon ce Posté par Boe Prox, il existe un autre moyen d’effectuer cette opération sur notre objet personnalisé en utilisant simplement PowerShell. Nous pouvons lui fournir un MemberSet qu’il va utiliser.

$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

Maintenant, quand mon objet apparaît dans le shell de commandes, il ne montre par défaut que ces seules propriétés.

Update-TypeData avec DefaultPropertySet

C’est bien, mais j’ai récemment vu une meilleure façon d’utiliser Update-TypeData pour spécifier les propriétés par défaut.

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

C’est tellement simple que je pourrais pratiquement m’en rappeler si je n’avais pas ce billet comme référence rapide. Je peux maintenant créer facilement des objets avec un grand nombre de propriétés et en avoir néanmoins une vue bien propre quand je les regarde dans le shell. Si j’ai besoin d’accéder à ces autres propriétés ou de les voir, elles sont cependant toujours là.

$myObject | Format-List *

Update-TypeData avec ScriptProperty

Une autre chose que j’ai tirée de cette vidéo était la création de propriétés de script pour vos objets. C’est le bon moment pour souligner que cela fonctionne également pour les objets existants.

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

Vous pouvez faire cela avant que votre objet soit créé ou bien après : cela fonctionne dans les deux cas. Voici ce qui diffère de l’utilisation de Add-Member avec une propriété de script. Quand vous utilisez Add-Member de la façon dont j’ai parlé plus haut, il existe seulement sur cette instance spécifique de l’objet. Celui-ci s’applique à tous les objets avec ce TypeName.

Paramètres de fonction

Vous pouvez maintenant utiliser ces types personnalisés pour les paramètres dans vos fonctions et vos scripts. Vous pouvez avoir une fonction qui crée ces objets personnalisés, puis les passer à d’autres fonctions.

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

PowerShell exige que l’objet soit du type que vous avez spécifié. Il génère une erreur de validation si le type ne correspond pas automatiquement, pour vous éviter de le tester dans votre code. Un bon exemple où on laisse PowerShell faire ce qu’il fait le mieux.

OutputType pour les fonctions

Vous pouvez également définir un OutputType pour vos fonctions avancées.

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

La valeur de l’attribut OutputType n’est qu’une note de documentation. Elle n’est pas dérivée du code de la fonction et n’est pas comparée à la sortie réelle de la fonction.

La raison principale pour laquelle vous utilisez un type de sortie est de faire en sorte que les méta-informations sur votre fonction reflètent vos intentions. Par exemple, Get-Command et Get-Help peuvent en tirer parti dans votre environnement de développement. Si vous voulez plus d’informations, consultez l’aide pour celui-ci : about_Functions_OutputTypeAttribute.

Cela dit, si vous utilisez Pester pour effectuer les tests unitaires de vos fonctions, il serait judicieux de vérifier que les objets en sortie correspondent à votre OutputType. Cela pourrait intercepter les variables qui arrivent dans le pipe alors qu’elles ne le devraient pas.

Réflexions finales

Le contexte de tout ceci concernait [PSCustomObject], mais beaucoup de ces informations s’appliquent aux objets en général.

J’ai vu la plupart de ces fonctionnalités en passant, mais je ne les avait jamais vues présentées sous la forme d’une collection d’informations sur PSCustomObject. Pas plus tard que la semaine dernière, je suis tombé sur une autre fonctionnalité et j’ai été étonné de ne pas l’avoir déjà vue. Je voulais rassembler toutes ces idées pour vous en donner la vision la plus large possible et pour vous les faire découvrir si jamais vous avez l’occasion de les utiliser. J’espère que vous avez appris quelque chose et que vous pourrez vous en servir dans vos scripts.