Partager via


Tout ce que vous vouliez savoir sur les tables de hachage

Je veux prendre du recul et parler des tables de hachage. Je les utilise tout le temps maintenant. J’ai enseigné quelqu’un à leur sujet après notre réunion de groupe d’utilisateurs hier soir et j’ai réalisé que j’avais la même confusion à leur sujet qu’il l’avait. Les tables de hachage sont vraiment importantes dans PowerShell. Il est donc judicieux d’avoir une bonne compréhension de celles-ci.

Note

La version originale de cet article est apparue sur le blog écrit par @KevinMarquette. L’équipe PowerShell remercie Kevin de partager ce contenu avec nous. Veuillez consulter son blog à PowerShellExplained.com.

Table de hachage sous la forme d’une collection d’éléments

Je veux que vous voyiez d’abord un Hashtable comme une collection dans la définition traditionnelle d’un hashtable. Cette définition vous donne une compréhension fondamentale de leur fonctionnement lorsqu’elles sont utilisées pour des choses plus avancées ultérieurement. Ignorer cette compréhension est souvent une source de confusion.

Qu’est-ce qu’un tableau ?

Avant de passer à la table de hachage, je dois d’abord mentionner les tableaux. Dans le cadre de cette discussion, un tableau est une liste ou une collection de valeurs ou d’objets.

$array = @(1,2,3,5,7,11)

Une fois que vous avez vos éléments dans un tableau, vous pouvez effectuer foreach une itération sur la liste ou utiliser un index pour accéder à des éléments individuels dans le tableau.

foreach($item in $array)
{
    Write-Output $item
}

Write-Output $array[3]

Vous pouvez également mettre à jour les valeurs à l’aide d’un index de la même façon.

$array[2] = 13

Je viens de gratter la surface sur des tableaux, mais cela devrait les placer dans le contexte approprié lorsque je passe à des tables de hachage.

Qu’est-ce qu’une table de hachage ?

Je vais commencer par une description technique de base de ce que sont les hashtables, au sens général, avant de passer à d'autres utilisations de PowerShell.

Une table de hachage est une structure de données, tout comme un tableau, sauf que vous stockez chaque valeur (objet) à l’aide d’une clé. Il s'agit d'un magasin de clés/valeurs simple. Tout d’abord, nous créons une table de hachage vide.

$ageList = @{}

Notez que des accolades, plutôt que des parenthèses, sont utilisées pour définir une table de hachage. Ensuite, nous ajoutons un élément à l’aide d’une clé comme suit :

$key = 'Kevin'
$value = 36
$ageList.Add( $key, $value )

$ageList.Add( 'Alex', 9 )

Le nom de la personne est la clé et leur âge est la valeur que je veux enregistrer.

Utilisation des crochets pour l’accès

Une fois que vous avez ajouté vos valeurs à la table de hachage, vous pouvez les extraire à l’aide de cette même clé (au lieu d’utiliser un index numérique comme vous le feriez pour un tableau).

$ageList['Kevin']
$ageList['Alex']

Quand je veux l’âge de Kevin, j’utilise son nom pour y accéder. Nous pouvons également utiliser cette approche pour ajouter ou mettre à jour des valeurs dans la table de hachage. C’est tout comme l’utilisation de la Add() méthode ci-dessus.

$ageList = @{}

$key = 'Kevin'
$value = 36
$ageList[$key] = $value

$ageList['Alex'] = 9

Il existe une autre syntaxe que vous pouvez utiliser pour accéder aux valeurs et les mettre à jour que je vais aborder dans une section ultérieure. Si vous arrivez à PowerShell à partir d’un autre langage, ces exemples doivent correspondre à la façon dont vous avez peut-être déjà utilisé des tables de hachage.

Création de tables de hachage avec des valeurs

Jusqu’à présent, j’ai créé une table de hachage vide pour ces exemples. Vous pouvez préremplir les clés et les valeurs lorsque vous les créez.

$ageList = @{
    Kevin = 36
    Alex  = 9
}

Sous la forme d'une table de correspondance

La valeur réelle de ce type de table de hachage est que vous pouvez les utiliser comme table de recherche. Voici un exemple simple.

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

$server = $environments[$env]

Dans cet exemple, vous spécifiez un environnement pour la $env variable et choisissez le serveur approprié. Vous pouvez utiliser une switch($env){...} pour une sélection comme celle-ci, mais une table de hachage est une bonne option.

Cela s’améliore encore lorsque vous générez dynamiquement la table de recherche pour l’utiliser ultérieurement. Pensez donc à utiliser cette approche lorsque vous devez croiser quelque chose. Je pense que nous verrions cela encore plus si PowerShell n’était pas si bon pour filtrer via le pipeline avec Where-Object. Si vous êtes toujours dans une situation où les performances sont importantes, cette approche doit être prise en compte.

Je ne dirais pas que c'est plus rapide, mais cela respecte la règle de Si la performance compte, testez-la.

Sélection multiple

En règle générale, vous pensez à une table de hachage comme une paire clé/valeur, où vous fournissez une clé et obtenez une valeur. PowerShell vous permet de fournir un tableau de clés pour obtenir plusieurs valeurs.

$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']

Dans cet exemple, j’utilise la même table de hachage de recherche ci-dessus et fournit trois styles de tableau différents pour obtenir les correspondances. Il s’agit d’un bijou caché dans PowerShell dont la plupart des gens ne sont pas conscients.

Itération des tables de hachage

Étant donné qu’une table de hachage est une collection de paires clé/valeur, vous effectuez une itération sur celle-ci différemment d’un tableau ou d’une liste normale d’éléments.

La première chose à remarquer est que si vous dirigez votre table de hachage, le canal le traite comme un objet.

PS> $ageList | Measure-Object
count : 1

Même si la Count propriété vous indique le nombre de valeurs qu’elle contient.

PS> $ageList.Count
2

Vous contournerez ce problème en utilisant la Values propriété si tout ce dont vous avez besoin est uniquement les valeurs.

PS> $ageList.Values | Measure-Object -Average
Count   : 2
Average : 22.5

Il est souvent plus utile d’énumérer les clés et de les utiliser pour accéder aux valeurs.

PS> $ageList.Keys | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_, $ageList[$_]
    Write-Output $message
}
Kevin is 36 years old
Alex is 9 years old

Voici le même exemple avec une foreach(){...} boucle.

foreach($key in $ageList.Keys)
{
    $message = '{0} is {1} years old' -f $key, $ageList[$key]
    Write-Output $message
}

Nous parcourons chaque clé d'une table de hachage, puis nous l'utilisons pour accéder à la valeur. Il s’agit d’un modèle courant lors de l’utilisation de tables de hachage en tant que collection.

GetEnumerator()

Cela nous amène à GetEnumerator() pour parcourir notre table de hachage.

$ageList.GetEnumerator() | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_.Key, $_.Value
    Write-Output $message
}

L’énumérateur vous donne chaque paire clé/valeur l’une après l’autre. Il a été conçu spécifiquement pour ce cas d’usage. Merci à Mark Kraus de me rappeler celui-ci.

MauvaiseÉnumération

Un détail important est que vous ne pouvez pas modifier une table de hachage pendant qu’elle est énumérée. Si nous commençons par notre exemple de base $environments :

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

Et tenter d'assigner chaque clé à la même valeur de serveur échoue.

$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

An error occurred while enumerating through a collection: Collection was modified;
enumeration operation may not execute.
+ CategoryInfo          : InvalidOperation: tableEnumerator:HashtableEnumerator) [],
 RuntimeException
+ FullyQualifiedErrorId : BadEnumeration

Cela échouera également même s’il semble qu’il devrait également être correct :

foreach($key in $environments.Keys) {
    $environments[$key] = 'SrvDev03'
}

Collection was modified; enumeration operation may not execute.
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

L’astuce à cette situation consiste à cloner les clés avant d’effectuer l’énumération.

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

Note

Vous ne pouvez pas cloner une table de hachage contenant une seule clé. PowerShell génère une erreur. Au lieu de cela, vous convertissez la propriété Keys en tableau, puis effectuez une itération sur le tableau.

@($environments.Keys) | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

Table de hachage sous forme de collection de propriétés

Jusqu’à présent, le type d’objets que nous avons placés dans notre table de hachage était tout le même type d’objet. J’ai utilisé des âges dans tous ces exemples et la clé était le nom de la personne. Il s’agit d’un excellent moyen de l’examiner lorsque votre collection d’objets a chacun un nom. Une autre façon courante d’utiliser des tables de hachage dans PowerShell consiste à contenir une collection de propriétés où la clé est le nom de la propriété. Je vais entrer dans cette idée dans cet exemple suivant.

Accès basé sur les propriétés

L’utilisation de l’accès basé sur des propriétés modifie le fonctionnement des tables de hachage et la façon dont vous pouvez les utiliser dans PowerShell. Voici notre exemple habituel de traiter les clés comme des propriétés.

$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9

Tout comme dans les exemples ci-dessus, cet exemple ajoute ces clés si elles n’existent pas déjà dans la table de hachage. Selon la façon dont vous avez défini vos clés et ce que sont vos valeurs, c’est soit un peu étrange, soit parfaitement adapté. L’exemple de liste d’âge a fonctionné très bien jusqu’à ce stade. Nous avons besoin d’un nouvel exemple pour que cela semble correct à l'avenir.

$person = @{
    name = 'Kevin'
    age  = 36
}

Et nous pouvons ajouter et accéder à des attributs sur le $person comme ceci.

$person.city = 'Austin'
$person.state = 'TX'

Tout à coup, cette table de hachage commence à s'apparenter à un objet par son comportement. Il s’agit toujours d’une collection d’éléments, donc tous les exemples ci-dessus s’appliquent toujours. Nous l’approchons d’un point de vue différent.

Vérification des clés et des valeurs

Dans la plupart des cas, vous pouvez simplement tester la valeur avec quelque chose comme ceci :

if( $person.age ){...}

C’est simple mais a été la source de nombreux bogues pour moi parce que j’ai négligé un détail important dans ma logique. J’ai commencé à l’utiliser pour tester si une clé était présente. Lorsque la valeur était $false ou zéro, cette instruction retournerait $false de façon inattendue.

if( $person.age -ne $null ){...}

Cela fonctionne autour de ce problème pour les valeurs zéro, mais pas $null et les clés inexistantes. La plupart du temps, vous n’avez pas besoin de faire cette distinction, mais il existe des méthodes pour quand vous le faites.

if( $person.ContainsKey('age') ){...}

Nous avons également un ContainsValue() pour le cas où vous devez tester une valeur sans connaître la clé ou parcourir l’ensemble de la collection.

Suppression et effacement des clés

Vous pouvez supprimer des clés avec la Remove() méthode.

$person.Remove('age')

Attribuer une valeur à $null vous laisse simplement avec une clé qui a une valeur $null.

Un moyen courant de vider une table de hachage consiste simplement à l’initialiser à une table de hachage vide.

$person = @{}

Bien que cela fonctionne, essayez d’utiliser la méthode Clear() à la place.

$person.Clear()

Il s’agit de l’une des instances où l’utilisation de la méthode crée du code auto-documentant et rend les intentions du code très propre.

Tous les trucs amusants

Tables de hachage ordonnées

Par défaut, les tables de hachage ne sont pas ordonnées (ou triées). Dans le contexte traditionnel, l’ordre n’a pas d’importance lorsque vous utilisez toujours une clé pour accéder aux valeurs. Vous pouvez constater que vous souhaitez que les propriétés restent dans l’ordre dans lequel vous les définissez. Heureusement, il existe un moyen de le faire avec le ordered mot clé.

$person = [ordered]@{
    name = 'Kevin'
    age  = 36
}

Maintenant, lorsque vous énumérez les clés et les valeurs, elles restent dans cet ordre.

Hashtables inline

Lorsque vous définissez une table de hachage sur une ligne, vous pouvez séparer les paires clé/valeur avec un point-virgule.

$person = @{ name = 'kevin'; age = 36; }

Cela sera pratique si vous les créez sur le pipeline.

Expressions personnalisées dans les commandes de pipeline courantes

Il existe quelques applets de commande qui prennent en charge l’utilisation de tables de hachage pour créer des propriétés personnalisées ou calculées. Vous voyez généralement cela avec Select-Object et Format-Table. Les tables de hachage possèdent une syntaxe particulière qui se présente ainsi lorsqu'elles sont complètement déployées.

$property = @{
    Name = 'TotalSpaceGB'
    Expression = { ($_.Used + $_.Free) / 1GB }
}

Le Name est ce que le cmdlet de commande désignerait comme colonne. Il Expression s’agit d’un bloc de script qui est exécuté, dans lequel $_ représente la valeur de l'objet sur le tuyau. Voici ce script en action :

$drives = Get-PSDrive | where Used
$drives | Select-Object -Property Name, $property

Name     TotalSpaceGB
----     ------------
C    238.472652435303

J’ai placé cela dans une variable, mais il pourrait facilement être défini inline et vous pouvez raccourcir Name jusqu’à n et Expression à e pendant que vous y êtes.

$drives | Select-Object -Property Name, @{n='TotalSpaceGB';e={($_.Used + $_.Free) / 1GB}}

Personnellement, je n'aime pas la longueur que cela donne aux commandes, et cela favorise souvent certains mauvais comportements que je ne vais pas aborder. Je préfère créer une nouvelle table de hachage ou pscustomobject avec tous les champs et propriétés que je souhaite, plutôt que d'utiliser cette approche dans les scripts. Mais il y a beaucoup de code là-bas qui le fait donc je voulais que vous soyez au courant. Je parle de créer un pscustomobject peu plus tard.

Expression de tri personnalisée

Il est facile de trier une collection si les objets ont les données que vous souhaitez trier. Vous pouvez ajouter les données à l’objet avant de le trier ou créer une expression personnalisée pour Sort-Object.

Get-ADUser | Sort-Object -Property @{ e={ Get-TotalSales $_.Name } }

Dans cet exemple, je prends une liste d’utilisateurs et j’utilise une applet de commande personnalisée pour obtenir des informations supplémentaires uniquement pour le tri.

Trier une liste de tables de hachage

Si vous avez une liste de tables de hachage que vous souhaitez trier, vous constaterez que Sort-Object ne traite pas vos clés comme des propriétés. Nous pouvons contourner cela à l’aide d’une expression de tri personnalisée.

$data = @(
    @{name='a'}
    @{name='c'}
    @{name='e'}
    @{name='f'}
    @{name='d'}
    @{name='b'}
)

$data | Sort-Object -Property @{e={$_.name}}

Platissement des tables de hachage sur les applets de commande

C’est l’une de mes choses préférées dans les tables de hachage que beaucoup de gens ne découvrent pas dès le début. L’idée est qu’au lieu de fournir toutes les propriétés à une cmdlet sur une seule ligne, vous pouvez d’abord les regrouper dans une table de hachage. Ensuite, vous pouvez donner la table de hachage à la fonction d’une manière spéciale. Voici un exemple de création d’une étendue DHCP de la façon normale.

Add-DhcpServerV4Scope -Name 'TestNetwork' -StartRange '10.0.0.2' -EndRange '10.0.0.254' -SubnetMask '255.255.255.0' -Description 'Network for testlab A' -LeaseDuration (New-TimeSpan -Days 8) -Type "Both"

Sans utiliser la décomposition, toutes ces choses doivent être définies sur une seule ligne. Il disparaît de l'écran ou s’enroule selon sa convenance. Comparez cela maintenant à une commande qui utilise le splatting.

$DHCPScope = @{
    Name          = 'TestNetwork'
    StartRange    = '10.0.0.2'
    EndRange      = '10.0.0.254'
    SubnetMask    = '255.255.255.0'
    Description   = 'Network for testlab A'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type          = "Both"
}
Add-DhcpServerV4Scope @DHCPScope

L’utilisation du signe @ au lieu du $ est ce qui invoque l’opération splat.

Prenez un moment pour apprécier la facilité de lecture de cet exemple. Ils sont exactement la même commande avec toutes les mêmes valeurs. La deuxième est plus facile à comprendre et à maintenir à l’avenir.

J'utilise le splatting chaque fois que la commande devient trop longue. Je définis trop longtemps est lorsque cela fait défiler ma fenêtre vers la droite. Si j’utilise trois propriétés pour une fonction, je vais probablement la réécrire en utilisant une table de hachage splattée.

Éclatement pour les paramètres facultatifs

L'une des façons les plus courantes d'utiliser le splatting est de gérer les paramètres facultatifs qui proviennent d'une autre partie de mon script. Supposons que j’ai une fonction qui encapsule un Get-CimInstance appel qui a un argument facultatif $Credential .

$CIMParams = @{
    ClassName = 'Win32_BIOS'
    ComputerName = $ComputerName
}

if($Credential)
{
    $CIMParams.Credential = $Credential
}

Get-CimInstance @CIMParams

Je commence par créer ma table de hachage avec des paramètres communs. Ensuite, j’ajoute le $Credential s’il existe. Étant donné que j'utilise le splatting ici, je n'ai besoin de faire l'appel à Get-CimInstance dans mon code qu'une seule fois. Ce modèle de conception est très propre et peut gérer facilement de nombreux paramètres facultatifs.

Pour être juste, vous pouvez écrire vos commandes pour autoriser les valeurs des paramètres $null. Vous n’avez pas toujours le contrôle sur les autres commandes que vous appelez.

Plusieurs éclatements

Vous pouvez appliquer plusieurs tables de hachage dans la même cmdlet. Si nous revisitons notre exemple original de splatting :

$Common = @{
    SubnetMask  = '255.255.255.0'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    Description = 'Network for testlab A'
}

Add-DhcpServerv4Scope @DHCPScope @Common

J’utilise cette méthode quand j’ai un ensemble commun de paramètres que je passe à beaucoup de commandes.

Platissement pour le code propre

Il n’y a rien de mal à éclater un seul paramètre si cela rend votre code plus propre.

$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log

Platissement d’exécutables

Le splatting fonctionne également sur certains exécutables utilisant une syntaxe /param:value. Robocopy.exe, par exemple, a certains paramètres comme celui-ci.

$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo

Je ne sais pas si c'est vraiment très utile, mais je l'ai trouvé intéressant.

Ajout de tables de hachage

Les tables de hachage prennent en charge l’opérateur d’ajout pour combiner deux tables de hachage.

$person += @{Zip = '78701'}

Cela fonctionne uniquement si les deux tables de hachage ne partagent pas de clé.

Tables de hachage imbriquées

Nous pouvons utiliser des tables de hachage comme valeurs dans une table de hachage.

$person = @{
    name = 'Kevin'
    age  = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'

J’ai commencé avec une table de hachage basique contenant deux clés. J’ai ajouté une clé appelée location avec une table de hachage vide. Ensuite, j’ai ajouté les deux derniers éléments à cette location table de hachage. Nous pouvons aussi faire cela en ligne.

$person = @{
    name = 'Kevin'
    age  = 36
    location = @{
        city  = 'Austin'
        state = 'TX'
    }
}

Cela crée la même table de hachage que celle que nous avons vue ci-dessus et accède aux propriétés de la même façon.

$person.location.city
Austin

Il existe de nombreuses façons d’aborder la structure de vos objets. Voici une deuxième façon de considérer une table de hachage imbriquée.

$people = @{
    Kevin = @{
        age  = 36
        city = 'Austin'
    }
    Alex = @{
        age  = 9
        city = 'Austin'
    }
}

Cela combine le concept d’utilisation de tables de hachage en tant que collection d’objets et d’une collection de propriétés. Les valeurs sont toujours faciles à accéder même lorsqu’elles sont imbriquées à l’aide de l’approche que vous préférez.

PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin

J’ai tendance à utiliser la propriété par point quand je la traite comme une propriété. Ce sont généralement des choses que j’ai définies statiquement dans mon code et je les connais en haut de ma tête. Si j’ai besoin de parcourir la liste ou d’accéder par programme aux clés, j’utilise les crochets pour fournir le nom de la clé.

foreach($name in $people.Keys)
{
    $person = $people[$name]
    '{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}

La possibilité d’imbriquer des tables de hachage vous offre beaucoup de flexibilité et d’options.

Examiner les tables de hachage imbriquées

Dès que vous commencez à imbriquer des tables de hachage, vous aurez besoin d’un moyen simple de les examiner à partir du terminal console. Si je prends cette dernière table de hachage, j’obtiens une sortie qui ressemble à ceci et cela ne va que jusqu'à une certaine profondeur.

PS> $people
Name                           Value
----                           -----
Kevin                          {age, city}
Alex                           {age, city}

Ma commande préférée pour examiner ces éléments est ConvertTo-Json parce que c’est très propre et j’utilise fréquemment JSON pour d'autres besoins.

PS> $people | ConvertTo-Json
{
    "Kevin":  {
                "age":  36,
                "city":  "Austin"
            },
    "Alex":  {
                "age":  9,
                "city":  "Austin"
            }
}

Même si vous ne connaissez pas JSON, vous devriez être en mesure de voir ce que vous recherchez. Il existe une Format-Custom commande pour les données structurées comme celle-ci, mais j’aime toujours mieux la vue JSON.

Création d'objets

Parfois, vous avez juste besoin d'un objet, et utiliser une table de hachage pour contenir les propriétés ne suffit pas pour accomplir la tâche. Le plus souvent, vous souhaitez voir les clés sous forme de noms de colonnes. pscustomobject facilite cela.

$person = [pscustomobject]@{
    name = 'Kevin'
    age  = 36
}

$person

name  age
----  ---
Kevin  36

Même si vous ne le créez pas initialement comme un pscustomobject, vous pouvez toujours le convertir ultérieurement si nécessaire.

$person = @{
    name = 'Kevin'
    age  = 36
}

[pscustomobject]$person

name  age
----  ---
Kevin  36

J'ai déjà une explication détaillée de pscustomobject que vous devriez lire après avoir lu celui-ci. Il s’appuie sur beaucoup de choses apprises ici.

Lecture et écriture de tables de hachage dans un fichier

Enregistrement au format CSV

La difficulté à faire enregistrer une table de hachage dans un fichier CSV est l’une des difficultés auxquelles je faisais référence ci-dessus. Convertissez votre table de hachage en un pscustomobject et l’enregistrera correctement au format CSV. Cela vous aide si vous commencez par pscustomobject afin que l’ordre des colonnes soit conservé. Mais vous pouvez le convertir en un pscustomobject inline si nécessaire.

$person | ForEach-Object{ [pscustomobject]$_ } | Export-Csv -Path $path

Là encore, consultez mon écriture sur l’utilisation d’un pscustomobject.

Enregistrement d’une table de hachage imbriquée dans un fichier

Si j’ai besoin d’enregistrer une table de hachage imbriquée dans un fichier, puis de la lire à nouveau, j’utilise les applets de commande JSON pour le faire.

$people | ConvertTo-Json | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-Json

Il existe deux points importants à propos de cette méthode. Tout d’abord, le code JSON est écrit en plusieurs lignes. J’ai donc besoin d’utiliser l’option -Raw pour la lire dans une seule chaîne. La seconde est que l’objet importé n’est plus un [hashtable]. Il s’agit maintenant d’un [pscustomobject], ce qui peut poser des problèmes si vous ne vous y attendez pas.

Surveillez les tables de hachage profondément imbriquées. Lorsque vous le convertissez en JSON, vous risquez de ne pas obtenir les résultats attendus.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json

{
  "a": {
    "b": {
      "c": "System.Collections.Hashtable"
    }
  }
}

Utilisez le paramètre Depth pour vous assurer que vous avez étendu toutes les tables de hachage imbriquées.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3

{
  "a": {
    "b": {
      "c": {
        "d": "e"
      }
    }
  }
}

Si vous avez besoin que ce soit un [hashtable] lors de l'importation, alors vous devez utiliser les commandes Export-CliXml et Import-CliXml.

Conversion de JSON en table de hachage

Si vous avez besoin de convertir JSON en un [hashtable], il existe une façon dont je sais de le faire avec le JavaScriptSerializer dans .NET.

[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')

À compter de PowerShell v6, la prise en charge de JSON utilise la bibliothèque JSON.NET de NewtonSoft et inclut la compatibilité avec les tables de hachage.

'{ "a": "b" }' | ConvertFrom-Json -AsHashtable

Name      Value
----      -----
a         b

PowerShell 6.2 a ajouté le paramètre Depth à ConvertFrom-Json. La profondeur par défaut est 1024.

Lecture directe à partir d’un fichier

Si vous avez un fichier qui contient une table de hachage à l’aide de la syntaxe PowerShell, il existe un moyen de l’importer directement.

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )

Il importe le contenu du fichier dans un scriptblockfichier, puis vérifie qu’il n’a pas d’autres commandes PowerShell avant de l’exécuter.

À ce sujet, saviez-vous qu’un manifeste de module (le .psd1 fichier) n’est qu’une table de hachage ?

Les clés peuvent être n’importe quel objet

La plupart du temps, les clés sont simplement des chaînes. Nous pouvons donc mettre des guillemets autour de quoi que ce soit et en faire une clé.

$person = @{
    'full name' = 'Kevin Marquette'
    '#' = 3978
}
$person['full name']

Vous pouvez faire des choses étranges que vous n’avez peut-être pas réalisé que vous pourriez faire.

$person.'full name'

$key = 'full name'
$person.$key

Juste parce que vous pouvez faire quelque chose, cela ne signifie pas que vous devriez. Ce dernier ressemble simplement à un bogue en attente de se produire et serait facilement mal compris par toute personne qui lit votre code.

Techniquement, votre clé n’a pas besoin d’être une chaîne, mais elle est plus facile à penser si vous utilisez uniquement des chaînes. Toutefois, l’indexation ne fonctionne pas correctement avec les clés complexes.

$ht = @{ @(1,2,3) = "a" }
$ht

Name                           Value
----                           -----
{1, 2, 3}                      a

L’accès à une valeur dans la table de hachage par sa clé ne fonctionne pas toujours. Par exemple:

$key = $ht.Keys[0]
$ht.$($key)
a
$ht[$key]
a

Lorsque la clé est un tableau, vous devez encapsuler la $key variable dans une sous-expression afin qu’elle puisse être utilisée avec la notation d’accès aux membres (.). Vous pouvez également utiliser la notation d’index de tableau ([]).

Utilisation dans les variables automatiques

$PSBoundParameters

$PSBoundParameters est une variable automatique qui existe uniquement dans le contexte d’une fonction. Elle contient tous les paramètres avec lesquels la fonction a été appelée. Ce n’est pas exactement une table de hachage, mais assez proche pour que vous puissiez la traiter comme telle.

Cela inclut la suppression des clés et l'utilisation du splatting dans d'autres fonctions. Si vous trouvez que vous écrivez des fonctions proxy, examinez de plus près celle-ci.

Pour plus d’informations, consultez about_Automatic_Variables .

PSBoundParameters : piège à éviter

Une chose importante à retenir est que cela inclut uniquement les valeurs transmises en tant que paramètres. Si vous avez également des paramètres avec des valeurs par défaut, mais qu’ils ne sont pas passés par l’appelant, $PSBoundParameters ne contiennent pas ces valeurs. Ceci est généralement négligé.

$PSDefaultParameterValues

Cette variable automatique vous permet d’affecter des valeurs par défaut à n’importe quelle applet de commande sans modifier l’applet de commande. Examinez cet exemple.

$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"

Cela ajoute une entrée à la $PSDefaultParameterValues table de hachage qui définit UTF8 comme valeur par défaut pour le Out-File -Encoding paramètre. Ceci est spécifique à la session, donc vous devriez le placer dans votre $PROFILE.

Je l’utilise souvent pour pré-attribuer des valeurs que je tape assez souvent.

$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'

Cela accepte également les caractères génériques, vous permettant de définir des valeurs en bloc. Voici quelques façons d’utiliser ce qui suit :

$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential

Pour obtenir une répartition plus approfondie, consultez cet excellent article sur les valeurs par défaut automatiques par Michael Sorens.

Regex $Matches

Lorsque vous utilisez l’opérateur -match, une variable automatique appelée $Matches est créée avec les résultats de la comparaison. Si vous avez des sous-expressions dans votre expression régulière, ces sous-correspondances sont également répertoriées.

$message = 'My SSN is 123-45-6789.'

$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]

Correspondances nommées

C’est l’une de mes fonctionnalités préférées que la plupart des gens ne connaissent pas. Si vous utilisez une correspondance regex nommée, vous pouvez accéder à cette correspondance par son nom dans les résultats.

$message = 'My Name is Kevin and my SSN is 123-45-6789.'

if($message -match 'My Name is (?<Name>.+) and my SSN is (?<SSN>.+)\.')
{
    $Matches.Name
    $Matches.SSN
}

Dans l’exemple ci-dessus, (?<Name>.*) est une sous-expression nommée. Cette valeur est ensuite placée dans la $Matches.Name propriété.

Group-Object -AsHashtable

Une fonctionnalité peu connue de Group-Object est sa capacité à transformer certains jeux de données en table de hachage pour vous.

Import-Csv $Path | Group-Object -AsHashtable -Property Email

Cette opération ajoute chaque ligne à une table de hachage et utilise la propriété spécifiée comme clé pour y accéder.

Copier des tables de hachage

Il est important de savoir que les tables de hachage sont des objets. Et chaque variable est simplement une référence à un objet. Cela signifie qu’il faut plus de travail pour créer une copie valide d’une table de hachage.

Affectation de types de référence

Lorsque vous avez une table de hachage et que vous l’affectez à une deuxième variable, les deux variables pointent vers la même table de hachage.

PS> $orig = @{name='orig'}
PS> $copy = $orig
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [copy]

Cela met en évidence qu’ils sont identiques, car la modification des valeurs dans l’une d’elles modifie également les valeurs dans l’autre. Cela s’applique également lors de la transmission de tables de hachage dans d’autres fonctions. Si ces fonctions modifient cette table de hachage, votre original est également modifié.

Copies superficielles, niveau unique

Si nous avons une table de hachage simple comme notre exemple ci-dessus, nous pouvons utiliser Clone() pour effectuer une copie superficielle.

PS> $orig = @{name='orig'}
PS> $copy = $orig.Clone()
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [orig]

Cela nous permettra d’apporter des modifications de base à l'une sans affecter l'autre.

Copies peu profondes et imbriquées

La raison pour laquelle elle est appelée copie superficielle est parce qu’elle copie uniquement les propriétés de niveau de base. Si l’une de ces propriétés est un type de référence (comme une autre table de hachage), ces objets imbriqués continueront à pointer les uns vers les autres.

PS> $orig = @{
        person=@{
            name='orig'
        }
    }
PS> $copy = $orig.Clone()
PS> $copy.person.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.person.name
PS> 'Orig: [{0}]' -f $orig.person.name

Copy: [copy]
Orig: [copy]

Vous pouvez donc voir que même si j’ai cloné la table de hachage, la référence à person n’a pas été clonée. Nous devons faire une copie profonde pour vraiment avoir une deuxième table de hachage qui n'est pas liée à la première.

Copie profonde

Il existe quelques façons de créer une copie en profondeur d’une table de hachage (et de la conserver en tant que table de hachage). Voici une fonction utilisant PowerShell pour créer de manière récursive une copie approfondie :

function Get-DeepClone
{
    [CmdletBinding()]
    param(
        $InputObject
    )
    process
    {
        if($InputObject -is [hashtable]) {
            $clone = @{}
            foreach($key in $InputObject.Keys)
            {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            return $clone
        } else {
            return $InputObject
        }
    }
}

Il ne gère pas d’autres types de référence ou tableaux, mais il s’agit d’un bon point de départ.

Une autre façon consiste à utiliser .NET pour le désérialiser à l’aide de CliXml comme dans cette fonction :

function Get-DeepClone
{
    param(
        $InputObject
    )
    $TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}

Pour les tables de hachage extrêmement volumineuses, la fonction de désérialisation est plus rapide à mesure qu'elle s'étend. Toutefois, il y a certaines considérations à prendre en compte lors de l’utilisation de cette méthode. Comme il utilise CliXml, il est gourmand en mémoire et si vous clonez d’énormes tables de hachage, cela peut être un problème. Une autre limitation de CliXml est qu’il existe une limitation de profondeur de 48. Cela signifie que si vous disposez d’une table de hachage avec 48 couches de tables de hachage imbriquées, le clonage échoue et aucune table de hachage n’est générée du tout.

Autre chose?

J'ai progressé rapidement. J'espère que vous appreniez quelque chose de nouveau ou que vous compreniez mieux à chaque lecture. Parce que j’ai abordé le spectre complet de cette fonctionnalité, il y a des aspects qui peuvent ne pas s’appliquer à vous pour l’instant. C'est tout à fait normal et c'est plutôt attendu en fonction de votre utilisation de PowerShell.