Condividi tramite


Tutto quello che volevi sapere sulle tabelle hash

Voglio fare un passo indietro e parlare di tabelle hash. Li uso tutti i tempi ora. Stavo insegnando a qualcuno riguardo a essi dopo la riunione del nostro gruppo di utenti ieri sera e mi sono reso conto che avevo la stessa confusione su di essi proprio come lui. Le tabelle hash sono davvero importanti in PowerShell, quindi è consigliabile avere una conoscenza approfondita delle tabelle.

Nota

La versione originale di questo articolo è apparsa nel blog scritto da @KevinMarquette. Il team di PowerShell ringrazia Kevin per aver condiviso questo contenuto con noi. Consultare il suo blog all'indirizzo PowerShellExplained.com.

Hashtable come raccolta di elementi

Voglio che tu prima veda un Hashtable come una raccolta nella definizione tradizionale di una tabella hash. Questa definizione offre una conoscenza fondamentale del modo in cui funzionano quando vengono usati per roba più avanzata in un secondo momento. Ignorare questa comprensione è spesso una fonte di confusione.

Che cos'è una matrice?

Prima di passare alla hashtable di, è necessario menzionare prima matrici. Ai fini di questa discussione, una matrice è un elenco o una raccolta di valori o oggetti.

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

Dopo aver creato gli elementi in una matrice, è possibile usare foreach per scorrere l'elenco o usare un indice per accedere a singoli elementi nella matrice.

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

Write-Output $array[3]

È anche possibile aggiornare i valori usando un indice nello stesso modo.

$array[2] = 13

Ho appena iniziato a esplorare le matrici, però questo dovrebbe metterle nel contesto giusto mentre mi sposto sui tabelloni hash.

Che cos'è una tabella hash?

Inizierò con una descrizione tecnica di base delle tabelle hash, in generale, prima di passare agli altri modi in cui PowerShell li usa.

Una tabella hash è una struttura di dati, molto simile a una matrice, ad eccezione dell'archiviazione di ogni valore (oggetto) usando una chiave. Si tratta di un archivio chiave/valore di base. Prima di tutto, viene creata una tabella hash vuota.

$ageList = @{}

Si noti che le parentesi graffe, anziché le parentesi, vengono usate per l'utilizzo di una tabella hash. Aggiungere quindi un elemento usando una chiave simile alla seguente:

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

$ageList.Add( 'Alex', 9 )

Il nome della persona è la chiave e la loro età è il valore che voglio salvare.

Uso delle parentesi quadre per l'accesso

Dopo aver aggiunto i valori alla tabella hash, è possibile recuperarli utilizzando la stessa chiave invece di utilizzare un indice numerico come si farebbe per un array.

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

Quando voglio l'età di Kevin, uso il suo nome per accedervi. È possibile usare questo approccio per aggiungere o aggiornare i valori anche nella tabella hash. Questo è proprio come usare il metodo Add() precedente.

$ageList = @{}

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

$ageList['Alex'] = 9

Esiste un'altra sintassi che è possibile usare per accedere e aggiornare i valori illustrati in una sezione successiva. Se si arriva a PowerShell da un altro linguaggio, questi esempi dovrebbero rientrare nel modo in cui è possibile usare le tabelle hash in precedenza.

Creazione di tabelle hash con valori

Finora ho creato una tabella hash vuota per questi esempi. È possibile precompilare le chiavi e i valori quando vengono creati.

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

Come tabella di ricerca

Il valore reale di questo tipo di tabella hash è che è possibile usarli come tabella di ricerca. Ecco un semplice esempio.

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

$server = $environments[$env]

In questo esempio si specifica un ambiente per la variabile $env e verrà selezionato il server corretto. È possibile usare un switch($env){...} per una selezione come questa, ma una tabella hash è un'opzione interessante.

Ciò migliora ulteriormente quando si compila dinamicamente la tabella di ricerca per usarla in un secondo momento. Considerare quindi l'uso di questo approccio quando è necessario fare riferimento incrociato a qualcosa. Penso che vedremmo questo ancora di più se PowerShell non fosse così bravo a filtrare sulla pipe con Where-Object. Se si è in una situazione in cui le prestazioni sono importanti, questo approccio deve essere considerato.

Non dico che è più veloce, ma rientra nella regola di Se le prestazioni sono importanti, testarlo.

Selezione multipla

In genere, si pensa a una tabella hash come una coppia chiave/valore, in cui si specifica una chiave e si ottiene un valore. PowerShell consente di fornire una matrice di chiavi per ottenere più valori.

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

In questo esempio, utilizzo la stessa tabella hash di ricerca di prima e fornisco tre diversi stili di array per ottenere le corrispondenze. Questa è una gemma nascosta in PowerShell che la maggior parte delle persone non è a conoscenza.

Iterazione delle tabelle hash

Poiché una tabella hash è una raccolta di coppie chiave/valore, è possibile eseguire l'iterazione in modo diverso rispetto a una matrice o a un normale elenco di elementi.

La prima cosa da notare è che se si passa la tabella hash attraverso la pipe, essa viene trattata come un unico oggetto.

PS> $ageList | Measure-Object
count : 1

Anche se la proprietà Count indica quanti valori contiene.

PS> $ageList.Count
2

Per risolvere questo problema, usare la proprietà Values se sono necessari solo i valori.

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

Spesso è più utile enumerare le chiavi e usarle per accedere ai valori.

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

Di seguito è riportato lo stesso esempio con un ciclo foreach(){...}.

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

Stiamo percorrendo ogni chiave nella tabella hash e poi viene utilizzata per accedere al valore. Si tratta di uno schema comune quando si utilizzano tabelle hash come raccolta.

GetEnumerator()

Questo ci porta a GetEnumerator() per iterare sulla hash table.

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

L'enumeratore fornisce ogni coppia chiave/valore una dopo l'altra. È stato progettato appositamente per questo caso d'uso. Grazie a Mark Kraus per avermi ricordato questo.

Enumerazione Errata

Un dettaglio importante è che non è possibile modificare una tabella hash durante l'enumerazione. Se si inizia con l'esempio di base $environments:

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

E il tentativo di impostare ogni chiave sullo stesso valore del server ha esito negativo.

$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

Anche questo fallirà, anche se sembra che dovrebbe funzionare.

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

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

Il trucco per questa situazione consiste nel clonare le chiavi prima di eseguire l'enumerazione.

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

Hashtable come raccolta di proprietà

Finora il tipo di oggetti inseriti nella tabella hash era lo stesso tipo di oggetto. Ho usato età in tutti questi esempi e la chiave era il nome della persona. Questo è un ottimo modo per esaminarlo quando nella tua raccolta di oggetti ciascuno ha un nome. Un altro modo comune per usare tabelle hash in PowerShell consiste nel contenere una raccolta di proprietà in cui la chiave è il nome della proprietà. Questa idea verrà descritta in questo esempio successivo.

Accesso basato su proprietà

L'uso dell'accesso basato su proprietà modifica le dinamiche delle tabelle hash e il modo in cui è possibile usarle in PowerShell. Di seguito è riportato il nostro esempio consueto, considerando le chiavi come proprietà.

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

Proprio come negli esempi precedenti, questo esempio aggiunge tali chiavi se non esistono già nella tabella hash. A seconda della modalità di definizione delle chiavi e dei valori, questo è un po' strano o perfetto. L'esempio di elenco di età ha funzionato molto fino a questo punto. Abbiamo bisogno di un nuovo esempio affinché questo sembri giusto andando avanti.

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

E possiamo aggiungere e accedere agli attributi sul $person in questo modo.

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

All'improvviso questa tabella hash inizia a somigliare e comportarsi come un oggetto. È ancora una raccolta di cose, quindi tutti gli esempi precedenti si applicano ancora. Ci avviciniamo solo da un punto di vista diverso.

Controllo delle chiavi e dei valori

Nella maggior parte dei casi, è sufficiente testare il valore con un risultato simile al seguente:

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

È semplice ma è stata la fonte di molti bug per me perché stavo ignorando un dettaglio importante nella mia logica. Ho iniziato a usarlo per verificare se era presente una chiave. Quando il valore è stato $false o zero, tale istruzione restituirà $false in modo imprevisto.

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

Questo risolve il problema per i valori zero, ma non per $null rispetto alle chiavi inesistenti. Nella maggior parte dei casi non è necessario fare questa distinzione, ma esistono metodi per quando si esegue l'operazione.

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

È anche disponibile un ContainsValue() per la situazione in cui è necessario testare un valore senza conoscere la chiave o eseguire l'iterazione dell'intera raccolta.

Rimozione e cancellazione delle chiavi

È possibile rimuovere le chiavi con il metodo Remove().

$person.Remove('age')

Assegnando loro un valore $null è sufficiente lasciare una chiave con un valore $null.

Un modo comune per cancellare una tabella hash consiste nell'inizializzarlo semplicemente in una tabella hash vuota.

$person = @{}

Anche se funziona, provare a usare invece il metodo Clear().

$person.Clear()

Si tratta di una di queste istanze in cui l'uso del metodo crea codice autodocumentato e rende le intenzioni del codice molto pulito.

Tutte le cose divertenti

Tabelle hash ordinate

Per impostazione predefinita, le tabelle hash non sono organizzate (o ordinate). Nel contesto tradizionale, l'ordine non è importante quando si usa sempre una chiave per accedere ai valori. È possibile che si voglia che le proprietà rimangano nell'ordine in cui vengono definite. Fortunatamente, c'è un modo per farlo con la parola chiave ordered.

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

Ora, quando si enumerano le chiavi e i valori, rimangono in questo ordine.

Tabelle hash inline

Quando si definisce una tabella hash in una riga, è possibile separare le coppie chiave/valore con un punto e virgola.

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

Questo sarà utile se li stai creando sulla pipeline.

Espressioni personalizzate nei comuni comandi della pipeline

Esistono alcuni cmdlet che supportano l'uso di tabelle hash per creare proprietà personalizzate o calcolate. Comunemente lo si vede con Select-Object e Format-Table. Le tabelle hash hanno una sintassi speciale simile alla seguente quando vengono espanse completamente.

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

Il Name è ciò che il cmdlet etichetterebbe come quella colonna. Il Expression è un blocco di script che viene eseguito quando $_ è il valore dell'oggetto sul pipe. Ecco lo script in azione:

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

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

L'ho inserito in una variabile, ma potrebbe essere facilmente definito in linea e puoi ridurre Name a n e Expression a e mentre ci sei.

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

Personalmente non mi piace quanto tempo richiedano i comandi e spesso promuove alcuni comportamenti scorretti che non affronterò. È più probabile creare una nuova tabella hash o pscustomobject con tutti i campi e le proprietà desiderati anziché usare questo approccio negli script. Ma c'è un sacco di codice là fuori che lo fa, quindi volevo che tu ne sia a conoscenza. Parlerò di creare un pscustomobject più avanti.

Espressione di ordinamento personalizzata

È facile ordinare una raccolta se gli oggetti hanno i dati su cui vuoi ordinare. È possibile aggiungere i dati all'oggetto prima di ordinarlo o creare un'espressione personalizzata per Sort-Object.

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

In questo esempio si prende un elenco di utenti e si utilizza un cmdlet personalizzato per ottenere informazioni aggiuntive per il solo scopo di ordinare.

Ordinare un elenco di tabelle Hashtable

Se disponi di un elenco di tabelle hash da ordinare, scoprirai che il comando Sort-Object non tratta le chiavi come proprietà. Possiamo aggirare questo usando un'espressione di ordinamento personalizzata.

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

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

Utilizzo di hashtable con cmdlet

Questo è uno dei miei aspetti preferiti delle tabelle hash che molte persone non scoprono nella fase iniziale. L'idea è che invece di fornire tutte le proprietà a un cmdlet su una sola riga, è invece possibile comprimerle in una tabella hash. È quindi possibile assegnare la tabella hash alla funzione in modo speciale. Di seguito è riportato un esempio di creazione di un ambito DHCP nel modo 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"

Senza usare splatting, è necessario definire tutti gli elementi in una singola riga. Scorre fuori dallo schermo oppure va a capo dove vuole. Ora confronta ciò con un comando che usa lo 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'uso del segno @ invece del $ è ciò che richiama l'operazione splat.

Basta dedicare un attimo a apprezzare quanto sia facile leggere quell'esempio. Sono esattamente lo stesso comando con tutti gli stessi valori. Il secondo è più facile da comprendere e mantenere in futuro.

Uso la tecnica dello "splatting" ogni volta che il comando diventa troppo lungo. Definisco troppo lungo quando la mia finestra scorre verso destra. Se incontro tre proprietà per una funzione, è probabile che la riscriverò usando una tabella hash 'splattata'.

Splatting per i parametri facoltativi

Uno dei modi più comuni in cui uso lo splatting è gestire i parametri facoltativi provenienti da un'altra parte nel mio script. Supponiamo di avere una funzione che wrappa una chiamata Get-CimInstance che include un argomento $Credential opzionale.

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

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

Get-CimInstance @CIMParams

Per iniziare, creare la tabella hash con parametri comuni. Quindi aggiungo il $Credential se esistente. Poiché in questo caso si usa lo splatting, è sufficiente avere la chiamata a Get-CimInstance nel codice una sola volta. Questo modello di progettazione è molto pulito e può gestire facilmente molti parametri facoltativi.

A dire il vero, è possibile scrivere i comandi per consentire valori $null per i parametri. Non si ha sempre il controllo sugli altri comandi che si invocano.

Schizzi multipli

È possibile eseguire lo splating di più tabelle hash nello stesso cmdlet. Se riconsideriamo il nostro esempio originale di 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

Questo metodo verrà usato quando si dispone di un set comune di parametri che sto passando a un sacco di comandi.

Splatting per il codice pulito

Non c'è niente di sbagliato nell'usare lo "splatting" di un singolo parametro se questo rende il tuo codice più pulito.

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

Eseguibili splatting

La funzione di splatting funziona anche su alcuni eseguibili che usano una sintassi di tipo /param:value. Robocopy.exe, ad esempio, include alcuni parametri come questo.

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

Non so che questo è tutto ciò utile, ma l'ho trovato interessante.

Aggiunta di tabelle hash

Le tabelle hash supportano l'operatore di addizione per combinare due tabelle hash.

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

Questa operazione funziona solo se le due tabelle hash non condividono una chiave.

Tabelle hash annidate

È possibile usare le tabelle hash come valori all'interno di una tabella hash.

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

Ho iniziato con una tabella hash di base contenente due chiavi. È stata aggiunta una chiave denominata location con una tabella hash vuota. Quindi ho aggiunto gli ultimi due elementi alla tabella hash location. Possiamo farlo anche direttamente nel testo.

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

In questo modo viene creata la stessa tabella hash illustrata in precedenza e è possibile accedere alle proprietà nello stesso modo.

$person.location.city
Austin

Esistono molti modi per avvicinarsi alla struttura degli oggetti. Ecco un secondo modo per esaminare una tabella hash annidata.

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

Questo combina il concetto di utilizzo di tabelle hash come raccolta di oggetti e una raccolta di proprietà. I valori sono ancora facili da accedere anche quando sono annidati usando qualsiasi approccio preferito.

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

Tendo a usare la proprietà punto quando la considero come una proprietà. Queste sono in genere cose che ho definito in modo statico nel mio codice e le conosco a memoria. Se è necessario esaminare l'elenco o accedere a livello di codice alle chiavi, usare le parentesi quadre per specificare il nome della chiave.

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

La possibilità di annidare tabelle hash offre una grande flessibilità e molte opzioni.

Esaminare le tabelle hash annidate

Non appena si inizia ad annidare le tabelle hash, avrai bisogno di un modo semplice per esaminarle dalla console. Se prendo l'ultima tabella hash, ottengo un output che ha un aspetto come questo e arriva solo fino a questo punto.

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

Il mio comando preferito per analizzare queste cose è ConvertTo-Json perché è molto pulito e utilizzo spesso JSON con altre cose.

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

Anche se non si conosce JSON, si dovrebbe essere in grado di vedere cosa si sta cercando. Esiste un comando Format-Custom per i dati strutturati come questo, ma mi piace ancora meglio la visualizzazione JSON.

Creazione di oggetti

A volte hai bisogno di un oggetto e utilizzare una tabella hash per contenere le proprietà non risulta efficace. Più comunemente si vogliono visualizzare le chiavi come nomi di colonna. Un pscustomobject semplifica questa operazione.

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

$person

name  age
----  ---
Kevin  36

Anche se inizialmente non lo crei come pscustomobject, puoi sempre fare il cast in seguito quando necessario.

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

[pscustomobject]$person

name  age
----  ---
Kevin  36

Ho già una descrizione dettagliata per *pscustomobject* che dovresti assolutamente leggere dopo questo. Si basa su molte delle cose apprese qui.

Lettura e scrittura di tabelle hash nel file

Salvataggio in csv

La difficoltà di ottenere una tabella hash per salvare in un file CSV è una delle difficoltà a cui mi riferivo in precedenza. Converti la tua tabella hash in un pscustomobject e verrà salvata correttamente in CSV. Consente di iniziare con un pscustomobject in modo che l'ordine delle colonne venga mantenuto. Ma puoi effettuare il cast a un pscustomobject inline, se necessario.

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

Ancora una volta, consultare il mio articolo su come utilizzare un pscustomobject.

Salvataggio di una tabella hash annidata in un file

Se è necessario salvare una tabella hash annidata in un file e quindi leggerla nuovamente, usare i cmdlet JSON per farlo.

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

Ci sono due punti importanti su questo metodo. In primo luogo, il codice JSON viene scritto su più righe, quindi è necessario usare l'opzione -Raw per leggerla in una singola stringa. Il secondo è che l'oggetto importato non è più un [hashtable]. Attualmente è un [pscustomobject] e questo può causare problemi se non ci si aspetta.

Prestare attenzione alle tabelle hash profondamente annidate. Quando lo si converte in JSON, è possibile che non si ottengano i risultati previsti.

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

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

Usare il parametro Depth per assicurarsi di aver espanso tutte le tabelle hash annidate.

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

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

Se è necessario un [hashtable] all'importazione, è necessario usare i comandi Export-CliXml e Import-CliXml.

Conversione di JSON in hashtable

Se è necessario convertire JSON in un [hashtable], esiste un modo per farlo con il JavaScriptSerializer in .NET.

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

A partire da PowerShell v6, il supporto JSON usa Newtonsoft JSON.NET e aggiunge il supporto hashtable.

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

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

PowerShell 6.2 ha aggiunto il parametro Depth a ConvertFrom-Json. Il valore predefinito Depth è 1024.

Lettura diretta da un file

Se si dispone di un file che contiene una tabella hash usando la sintassi di PowerShell, è possibile importarla direttamente.

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

Importa il contenuto del file in un scriptblock, quindi verifica di non avere altri comandi di PowerShell prima di eseguirlo.

A proposito, sapevi che un manifesto del modulo (il file .psd1) è semplicemente una tabella hash?

Le chiavi possono essere qualsiasi oggetto

Nella maggior parte dei casi, le chiavi sono solo stringhe. Quindi possiamo mettere le virgolette intorno a qualsiasi cosa e trasformarla in una chiave.

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

Puoi fare alcune cose strane che potresti non aver capito che potresti fare.

$person.'full name'

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

Solo perché puoi fare qualcosa, non significa che dovresti. L'ultimo aspetto sembra un bug in attesa di verificarsi e sarebbe facilmente frainteso da chiunque legga il codice.

Tecnicamente la tua chiave non deve essere una stringa, ma è più facile da capire se si usano solo stringhe. Tuttavia, l'indicizzazione non funziona correttamente con le chiavi complesse.

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

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

L'accesso a un valore nella tabella hash tramite la relativa chiave non funziona sempre. Per esempio:

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

Quando la chiave è un array, è necessario inserire la variabile $key in una sottoespressione affinché possa essere usata con la notazione di accesso ai membri (.). In alternativa, è possibile usare la notazione dell'indice di matrice ([]).

Uso nelle variabili automatiche

$PSBoundParameters

$PSBoundParameters è una variabile automatica che esiste solo all'interno del contesto di una funzione. Contiene tutti i parametri con cui è stata chiamata la funzione. Non si tratta esattamente di una tabella hash, ma è abbastanza simile da poterla trattare come tale.

Ciò include la rimozione di chiavi e il passaggio di parametri ad altre funzioni. Se ti trovi a scrivere funzioni proxy, esamina più da vicino questa.

Per altri dettagli, vedere about_Automatic_Variables.

PSBoundParameters gotcha

Un aspetto importante da ricordare è che include solo i valori passati come parametri. Se si hanno anche parametri con valori predefiniti ma non vengono passati dal chiamante, $PSBoundParameters non contiene tali valori. Questo è comunemente trascurato.

$PSDefaultParameterValues

Questa variabile automatica consente di assegnare valori predefiniti a qualsiasi cmdlet senza modificare il cmdlet. Esaminare questo esempio.

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

Viene aggiunta una voce alla tabella hash $PSDefaultParameterValues che imposta UTF8 come valore predefinito per il parametro Out-File -Encoding. Questa operazione è specifica della sessione, quindi è consigliabile posizionarlo nel tuo $PROFILE.

Lo uso spesso per pre-assegnare valori digitati abbastanza spesso.

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

Accetta anche caratteri jolly, consentendo di impostare i valori collettivamente. Ecco alcuni modi in cui è possibile usare:

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

Per una suddivisione più approfondita, vedere questo ottimo articolo su impostazioni predefinite automatiche di Michael Sorens.

Regex dei $Matches

Quando utilizzi l'operatore -match, viene creata una variabile automatica chiamata $Matches con i risultati della corrispondenza. Se hai delle sotto-espressioni nel tuo regex, vengono elencate anche tali corrispondenze parziali.

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

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

Corrispondenze denominate

Questa è una delle mie caratteristiche preferite che la maggior parte delle persone non conosce. Se si usa una corrispondenza regex denominata, è possibile accedere a tale corrispondenza in base al nome nelle corrispondenze.

$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
}

Nell'esempio precedente, il (?<Name>.*) è una sottoespressione nominata. Questo valore viene quindi inserito nella proprietà $Matches.Name.

Group-Object -AsHashtable

Una funzionalità poco nota di Group-Object è che può trasformare alcuni set di dati in una tabella hash.

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

Verrà aggiunta ogni riga in una tabella hash e verrà usata la proprietà specificata come chiave per accedervi.

Copiare le tabelle hash

Una cosa importante da sapere è che le tabelle hash sono oggetti. Ogni variabile è solo un riferimento a un oggetto . Ciò significa che sono necessari più operazioni per creare una copia valida di una tabella hash.

Assegnazione di tipi di riferimento

Quando si dispone di una tabella hash e la si assegna a una seconda variabile, entrambe le variabili puntano alla stessa tabella hash.

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]

Ciò evidenzia che sono uguali perché la modifica dei valori in uno modificherà anche i valori nell'altro. Questo vale anche quando si passano tabelle hash in altre funzioni. Se tali funzioni apportano modifiche a tale tabella hash, viene modificato anche l'originale.

Copie superficiali, livello unico

Se è disponibile una semplice tabella hash come l'esempio precedente, è possibile usare Clone() per creare una copia superficiale.

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]

Ciò consentirà di apportare alcune modifiche di base a una che non influisce sull'altra.

Copie superficiali, annidate

Il motivo per cui viene chiamato una copia superficiale è perché copia solo le proprietà del livello di base. Se una di queste proprietà è un tipo riferimento (come un'altra tabella hash), gli oggetti annidati punteranno l'uno all'altro.

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]

Quindi puoi vedere che anche se ho clonato la tabella hash, il riferimento a person non è stato clonato. È necessario creare una copia completa per avere effettivamente una seconda tabella hash che non è collegata al primo.

Copie profonde

Esistono due modi per creare una copia profonda di una tabella hash (e mantenerla come tabella hash). Ecco una funzione che usa PowerShell per creare in modo ricorsivo una copia completa:

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
        }
    }
}

Non gestisce altri tipi di riferimento o matrici, ma è un buon punto di partenza.

Un altro modo consiste nell'usare .NET per deserializzarlo usando CliXml come in questa funzione:

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

Per le tabelle hash estremamente grandi, la funzione di deserializzazione è più veloce man mano che si espande. Tuttavia, ci sono alcune cose da considerare quando si usa questo metodo. Poiché usa CliXml, è un uso intensivo della memoria e se si clonano tabelle hash enormi, questo potrebbe essere un problema. Un'altra limitazione della CliXml è una limitazione di profondità pari a 48. Ciò significa che, se si dispone di una tabella hash con 48 livelli di tabelle hash annidate, la clonazione avrà esito negativo e non verrà restituita alcuna tabella hash.

Qualsiasi altra cosa?

Ho fatto molta strada rapidamente. La mia speranza è che tu traia qualcosa di nuovo o lo comprenda meglio ogni volta che leggi questo testo. Poiché ho trattato l'intera gamma di questa funzionalità, ci sono aspetti che potrebbero non essere applicabili a te in questo momento. È assolutamente normale ed è piuttosto prevedibile a seconda dell'uso che si fa di PowerShell.