Allt du ville veta om hashtables

Jag vill ta ett steg tillbaka och prata om hashtables. Jag använder dem hela tiden nu. Jag undervisade någon om dem efter vårt användargruppsmöte igår kväll och jag insåg att jag hade samma förvirring om dem som han hade. Hashtables är verkligen viktiga i PowerShell så det är bra att ha en gedigen förståelse för dem.

Kommentar

Den ursprungliga versionen av den här artikeln visades på bloggen skriven av @KevinMarquette. PowerShell-teamet tackar Kevin för att ha delat det här innehållet med oss. Kolla in hans blogg på PowerShellExplained.com.

Hashtable som en samling saker

Jag vill att du först ska se en Hashtable som en samling i den traditionella definitionen av en hashtable. Den här definitionen ger dig en grundläggande förståelse för hur de fungerar när de används för mer avancerade saker senare. Att hoppa över den här förståelsen är ofta en källa till förvirring.

Vad är en matris?

Innan jag hoppar in i vad en Hashtable är, måste jag nämna matriser först. I den här diskussionen är en matris en lista eller samling värden eller objekt.

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

När du har dina objekt i en matris kan du antingen använda foreach för att iterera över listan eller använda ett index för att komma åt enskilda element i matrisen.

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

Write-Output $array[3]

Du kan också uppdatera värden med hjälp av ett index på samma sätt.

$array[2] = 13

Jag skrapade bara ytan på matriser men det borde sätta dem i rätt kontext när jag går vidare till hashtables.

Vad är en hashtable?

Jag ska börja med en grundläggande teknisk beskrivning av vad hashtables är, i allmän mening, innan jag övergår till andra sätt som PowerShell använder dem.

En hashtable är en datastruktur, ungefär som en matris, förutom att du lagrar varje värde (objekt) med hjälp av en nyckel. Det är ett grundläggande nyckel-/värdearkiv. Först skapar vi en tom hash-tabell.

$ageList = @{}

Observera att klammerparenteser i stället för parenteser används för att definiera en hashtable. Sedan lägger vi till ett objekt med en nyckel som den här:

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

$ageList.add( 'Alex', 9 )

Personens namn är nyckeln och deras ålder är det värde som jag vill spara.

Använda hakparenteserna för åtkomst

När du har lagt till dina värden i hashtabellen kan du dra tillbaka dem med samma nyckel (i stället för att använda ett numeriskt index som du skulle ha gjort för en matris).

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

När jag vill ha Kevins ålder använder jag hans namn för att komma åt det. Vi kan använda den här metoden för att lägga till eller uppdatera värden i hashtable också. Det här är precis som att add() använda funktionen ovan.

$ageList = @{}

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

$ageList['Alex'] = 9

Det finns en annan syntax som du kan använda för att komma åt och uppdatera värden som jag tar upp i ett senare avsnitt. Om du kommer till PowerShell från ett annat språk bör de här exemplen passa in i hur du kan ha använt hashtables tidigare.

Skapa hashtables med värden

Hittills har jag skapat en tom hashtable för dessa exempel. Du kan fylla i nycklarna och värdena i förväg när du skapar dem.

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

Som en uppslagstabell

Det verkliga värdet för den här typen av en hashtable är att du kan använda dem som en uppslagstabell. Här är ett enkelt exempel.

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

$server = $environments[$env]

I det här exemplet anger du en miljö för variabeln $env och väljer rätt server. Du kan använda en switch($env){...} för ett val som detta, men en hashtable är ett trevligt alternativ.

Detta blir ännu bättre när du dynamiskt skapar uppslagstabellen för att använda den senare. Så tänk på att använda den här metoden när du behöver korsreferens något. Jag tror att vi skulle se detta ännu mer om PowerShell inte var så bra på att filtrera på röret med Where-Object. Om du någonsin befinner dig i en situation där prestanda är viktigt måste den här metoden övervägas.

Jag kommer inte att säga att det är snabbare, men det passar in i regeln om prestanda spelar roll, testa det.

Flerval

I allmänhet ser du en hashtable som ett nyckel/värde-par, där du anger en nyckel och får ett värde. Med PowerShell kan du ange en matris med nycklar för att hämta flera värden.

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

I det här exemplet använder jag samma uppslagshashtabell ovanifrån och tillhandahåller tre olika matrisformat för att hämta matchningarna. Det här är en dold pärla i PowerShell som de flesta inte känner till.

Iterera hashtables

Eftersom en hashtable är en samling nyckel/värde-par itererar du över den på ett annat sätt än för en matris eller en normal lista med objekt.

Det första att märka är att om du rör din hashtable behandlar röret det som ett objekt.

PS> $ageList | Measure-Object
count : 1

Även om egenskapen .count anger hur många värden den innehåller.

PS> $ageList.count
2

Du kommer runt det här problemet genom att .values använda egenskapen om allt du behöver bara är värdena.

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

Det är ofta mer användbart att räkna upp nycklarna och använda dem för att komma åt värdena.

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

Här är samma exempel med en foreach(){...} loop.

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

Vi går igenom varje nyckel i hashtabellen och använder den sedan för att komma åt värdet. Det här är ett vanligt mönster när du arbetar med hashtables som en samling.

GetEnumerator()

Det leder oss till GetEnumerator() för iterering över vår hashtable.

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

Uppräknaren ger dig varje nyckel/värde-par en efter en. Den har utformats specifikt för det här användningsfallet. Tack till Mark Kraus för att du påminde mig om den här.

BadEnumeration

En viktig detalj är att du inte kan ändra en hashtable när den räknas upp. Om vi börjar med vårt grundläggande $environments exempel:

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

Och det går inte att ange varje nyckel till samma servervärde.

$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

Detta kommer också att misslyckas även om det verkar som om det också bör vara bra:

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

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

Tricket med den här situationen är att klona nycklarna innan du gör uppräkningen.

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

Hashtable som en samling egenskaper

Hittills har den typ av objekt som vi placerade i vår hashtable alla samma typ av objekt. Jag använde åldrar i alla dessa exempel och nyckeln var personens namn. Det här är ett bra sätt att titta på det när alla objektsamlingen har ett namn. Ett annat vanligt sätt att använda hashtables i PowerShell är att lagra en samling egenskaper där nyckeln är namnet på egenskapen. Jag går in på den idén i nästa exempel.

Egenskapsbaserad åtkomst

Användningen av egenskapsbaserad åtkomst ändrar dynamiken i hashtables och hur du kan använda dem i PowerShell. Här är vårt vanliga exempel ovan som behandlar nycklarna som egenskaper.

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

Precis som exemplen ovan lägger det här exemplet till dessa nycklar om de inte redan finns i hashtabellen. Beroende på hur du har definierat dina nycklar och vad dina värden är, är detta antingen lite konstigt eller en perfekt passform. Exemplet på ålderslistan har fungerat bra fram tills nu. Vi behöver ett nytt exempel för att detta ska kännas rätt framöver.

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

Och vi kan lägga till och komma åt attribut på det här viset $person .

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

Helt plötsligt börjar denna hashtable kännas och bete sig som ett objekt. Det är fortfarande en samling saker, så alla exempel ovan gäller fortfarande. Vi närmar oss det bara ur en annan synvinkel.

Söka efter nycklar och värden

I de flesta fall kan du bara testa värdet med något liknande:

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

Det är enkelt men har varit källan till många buggar för mig eftersom jag förbiser en viktig detalj i min logik. Jag började använda den för att testa om det fanns en nyckel. När värdet var $false eller noll skulle instruktionen returneras $false oväntat.

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

Det här fungerar runt problemet för nollvärden men inte $null jämfört med obefintlig nyckel. För det mesta behöver du inte göra den skillnaden, men det finns funktioner för när du gör det.

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

Vi har också en ContainsValue() för den situation där du behöver testa för ett värde utan att känna till nyckeln eller iterera hela samlingen.

Ta bort och rensa nycklar

Du kan ta bort nycklar med .Remove() funktionen .

$person.remove('age')

När du tilldelar dem ett $null värde får du bara en nyckel som har ett $null värde.

Ett vanligt sätt att rensa en hashtable är att bara initiera den till en tom hashtable.

$person = @{}

Även om det fungerar kan du försöka använda clear() funktionen i stället.

$person.clear()

Det här är en av de instanser där funktionen skapar självdokumenterande kod och gör kodens avsikter mycket rena.

Alla roliga saker

Ordnade hashtables

Som standard sorteras inte hashtables (eller sorteras). I den traditionella kontexten spelar ordningen ingen roll när du alltid använder en nyckel för att komma åt värden. Du kanske upptäcker att du vill att egenskaperna ska förbli i den ordning som du definierar dem. Tack och lov finns det ett sätt att göra det med nyckelordet ordered .

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

Nu när du räknar upp nycklar och värden förblir de i den ordningen.

Infogade hashtables

När du definierar en hashtabell på en rad kan du separera nyckel/värde-paren med ett semikolon.

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

Detta kommer att vara praktiskt om du skapar dem på röret.

Anpassade uttryck i vanliga pipelinekommandon

Det finns några cmdletar som stöder användning av hashtables för att skapa anpassade eller beräknade egenskaper. Du ser ofta detta med Select-Object och Format-Table. Hashtabellerna har en särskild syntax som ser ut så här när den är helt expanderad.

$property = @{
    name = 'totalSpaceGB'
    expression = { ($_.used + $_.free) / 1GB }
}

Är name vad cmdleten skulle märka den kolumnen. expression är ett skriptblock som körs där $_ är värdet för objektet på röret. Här är skriptet i praktiken:

$drives = Get-PSDrive | Where Used
$drives | Select-Object -Property name, $property

Name     totalSpaceGB
----     ------------
C    238.472652435303

Jag placerade det i en variabel men det kan enkelt definieras infogat och du kan förkorta name till n och expression till e medan du är på det.

$drives | Select-Object -property name, @{n='totalSpaceGB';e={($_.used + $_.free) / 1GB}}

Personligen gillar jag inte hur länge det gör kommandon och det främjar ofta några dåliga beteenden som jag inte kommer in på. Jag är mer benägna att skapa en ny hashtable eller pscustomobject med alla fält och egenskaper som jag vill använda i stället för att använda den här metoden i skript. Men det finns mycket kod där ute som gör detta så jag ville att du skulle vara medveten om det. Jag pratar om att skapa en pscustomobject senare.

Anpassat sorteringsuttryck

Det är enkelt att sortera en samling om objekten har de data som du vill sortera efter. Du kan antingen lägga till data i objektet innan du sorterar dem eller skapa ett anpassat uttryck för Sort-Object.

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

I det här exemplet tar jag en lista över användare och använder en anpassad cmdlet för att få ytterligare information bara för sorteringen.

Sortera en lista med hashtables

Om du har en lista över hashtables som du vill sortera ser du att Sort-Object inte behandlar dina nycklar som egenskaper. Vi kan få en runda som med hjälp av ett anpassat sorteringsuttryck.

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

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

Splatting hashtables at cmdlets

Detta är en av mina favorit saker om hashtables som många människor inte upptäcker tidigt. Tanken är att du i stället för att tillhandahålla alla egenskaper till en cmdlet på en rad i stället kan packa dem i en hashtable först. Sedan kan du ge hashtable till funktionen på ett speciellt sätt. Här är ett exempel på hur du skapar ett DHCP-omfång på det normala sättet.

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"

Utan att använda splatting måste alla dessa saker definieras på en enda rad. Det rullar antingen bort från skärmen eller kommer att omslutas där det någonsin känns som. Jämför nu det med ett kommando som använder 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

Användningen av @ tecknet i stället för $ är det som anropar splat-åtgärden.

Ta bara en stund att uppskatta hur lätt det exemplet är att läsa. De är exakt samma kommando med samma värden. Den andra är lättare att förstå och underhålla framöver.

Jag använder splatting när kommandot blir för långt. Jag definierar för länge som gör att mitt fönster rullar åt höger. Om jag träffar tre egenskaper för en funktion är oddsen att jag skriver om den med en splatted hashtable.

Splatting för valfria parametrar

Ett av de vanligaste sätten jag använder splatting på är att hantera valfria parametrar som kommer från någon annan plats i mitt skript. Anta att jag har en funktion som omsluter ett Get-CIMInstance anrop som har ett valfritt $Credential argument.

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

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

Get-CIMInstance @CIMParams

Jag börjar med att skapa min hashtable med vanliga parametrar. Sedan lägger jag till $Credential om den finns. Eftersom jag använder splatting här behöver jag bara ha anropet till Get-CIMInstance i min kod en gång. Det här designmönstret är mycket rent och kan enkelt hantera många valfria parametrar.

För att vara rättvis kan du skriva dina kommandon för att tillåta $null värden för parametrar. Du har bara inte alltid kontroll över de andra kommandona som du anropar.

Flera splats

Du kan splat flera hashtables till samma cmdlet. Om vi går tillbaka till vårt ursprungliga splatting-exempel:

$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

Jag använder den här metoden när jag har en gemensam uppsättning parametrar som jag skickar till många kommandon.

Splatting för ren kod

Det är inget fel med att splatta en enda parameter om du blir kodrensare.

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

Splatting körbara filer

Splatting fungerar också på vissa körbara filer som använder en /param:value syntax. Robocopy.exe, till exempel, har vissa parametrar som detta.

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

Jag vet inte att det här är så användbart, men jag tyckte att det var intressant.

Lägga till hashtables

Hashtables stöder additionsoperatorn för att kombinera två hashtables.

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

Detta fungerar bara om de två hashtabellerna inte delar en nyckel.

Kapslade hashtables

Vi kan använda hashtables som värden i en hashtable.

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

Jag började med en grundläggande hashtable som innehåller två nycklar. Jag har lagt till en nyckel som heter location med en tom hashtable. Sedan lade jag till de två sista objekten i den location hashtabellen. Vi kan göra allt detta internt också.

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

Detta skapar samma hashtable som vi såg ovan och kan komma åt egenskaperna på samma sätt.

$person.location.city
Austin

Det finns många sätt att närma sig strukturen för dina objekt. Här är ett andra sätt att titta på en kapslad hashtable.

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

Detta blandar begreppet att använda hashtables som en samling objekt och en samling egenskaper. Värdena är fortfarande enkla att komma åt även när de är kapslade med vilken metod du vill.

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

Jag brukar använda dot-egenskapen när jag behandlar den som en egenskap. Det är i allmänhet saker som jag har definierat statiskt i min kod och jag känner dem från toppen av mitt huvud. Om jag behöver gå i listan eller programmatiskt komma åt nycklarna använder jag hakparenteserna för att ange nyckelnamnet.

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

Att ha möjlighet att kapsla hashtables ger dig mycket flexibilitet och alternativ.

Titta på kapslade hashtables

Så fort du börjar kapsla hashtables behöver du ett enkelt sätt att titta på dem från konsolen. Om jag tar den sista hashtable, får jag en utdata som ser ut så här och det går bara så djupt:

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

Min gå till kommandot för att titta på dessa saker beror ConvertTo-JSON på att det är mycket ren och jag använder ofta JSON på andra saker.

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

Även om du inte känner till JSON bör du kunna se vad du letar efter. Det finns ett Format-Custom kommando för strukturerade data som denna, men jag gillar fortfarande JSON-vyn bättre.

Skapa objekt

Ibland behöver du bara ha ett objekt och att använda en hashtable för att lagra egenskaper är bara inte att få jobbet gjort. Oftast vill du se nycklarna som kolumnnamn. A pscustomobject gör det enkelt.

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

$person

name  age
----  ---
Kevin  36

Även om du inte skapar den som en pscustomobject initial kan du alltid casta den senare när det behövs.

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

[pscustomobject]$person

name  age
----  ---
Kevin  36

Jag har redan detaljerad uppskrivning för pscustomobject att du bör gå läsa efter den här. Det bygger på många av de saker som lärts här.

Läsa och skriva hashtables till fil

Spara till CSV

Kämpar med att få en hashtable att spara till en CSV är en av de svårigheter som jag hänvisade till ovan. Konvertera hashtabellen till en pscustomobject så sparas den korrekt i CSV. Det hjälper om du börjar med en pscustomobject så att kolumnordningen bevaras. Men du kan kasta den till en pscustomobject infogad om det behövs.

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

Återigen, kolla in min uppskrivning om att använda en pscustomobject.

Spara en kapslad hashtable till fil

Om jag behöver spara en kapslad hashtable i en fil och sedan läsa in den igen använder jag JSON-cmdletarna för att göra det.

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

Det finns två viktiga punkter om den här metoden. Först är att JSON skrivs ut flera rad så jag måste använda -Raw alternativet för att läsa den tillbaka till en enda sträng. Det andra är att det importerade objektet inte längre är ett [hashtable]. Det är nu en [pscustomobject] och som kan orsaka problem om du inte förväntar dig det.

Håll utkik efter djupt kapslade hashtables. När du konverterar den till JSON kanske du inte får de resultat du förväntar dig.

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

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

Använd djupparametern för att se till att du har expanderat alla kapslade hashtables.

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

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

Om du vill att det ska vara en [hashtable] vid import måste du använda kommandona Export-CliXml och Import-CliXml .

Konvertera JSON till Hashtable

Om du behöver konvertera JSON till en [hashtable]finns det ett sätt som jag känner till för att göra det med JavaScriptSerializer i .NET.

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

Från och med PowerShell v6 använder JSON-supporten NewtonSoft-JSON.NET och lägger till hashtable-stöd.

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

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

PowerShell 6.2 lade till parametern Djup i ConvertFrom-Json. Standarddjupet är 1024.

Läsa direkt från en fil

Om du har en fil som innehåller en hashtabell med hjälp av PowerShell-syntax finns det ett sätt att importera den direkt.

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

Den importerar innehållet i filen till en scriptblockoch kontrollerar sedan att den inte har några andra PowerShell-kommandon innan den körs.

Visste du att ett modulmanifest (psd1-filen) bara är en hashtable?

Nycklar kan vara valfritt objekt

För det mesta är nycklarna bara strängar. Så vi kan sätta citattecken runt vad som helst och göra det till en nyckel.

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

Du kan göra några udda saker som du kanske inte har insett att du kunde göra.

$person.'full name'

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

Bara för att du kan göra något, betyder det inte att du borde. Den sista ser bara ut som en bugg som väntar på att hända och skulle lätt missförstås av alla som läser din kod.

Tekniskt sett behöver nyckeln inte vara en sträng, men de är lättare att tänka på om du bara använder strängar. Indexering fungerar dock inte bra med de komplexa nycklarna.

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

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

Åtkomsten till ett värde i hashtabellen med dess nyckel fungerar inte alltid. Till exempel:

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

När nyckeln är en matris måste du omsluta variabeln $key i en underuttryck så att den kan användas med medlemsåtkomst (.) notation. Eller så kan du använda matrisindex ([]) notation.

Använda i automatiska variabler

$PSBoundParameters

$PSBoundParameters är en automatisk variabel som bara finns i kontexten för en funktion. Den innehåller alla parametrar som funktionen anropades med. Detta är inte precis en hashtable men tillräckligt nära för att du kan behandla den som en.

Det omfattar att ta bort nycklar och splatta dem till andra funktioner. Om du skriver proxyfunktioner kan du ta en närmare titt på den här.

Mer information finns i about_Automatic_Variables .

PSBoundParameters gotcha

En viktig sak att komma ihåg är att detta endast innehåller de värden som skickas som parametrar. Om du också har parametrar med standardvärden men inte skickas in av anroparen $PSBoundParameters , innehåller inte dessa värden. Detta förbises ofta.

$PSDefaultParameterValues

Med den här automatiska variabeln kan du tilldela standardvärden till valfri cmdlet utan att ändra cmdleten. Ta en titt på det här exemplet.

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

Detta lägger till en post i hashtabellen $PSDefaultParameterValues som anger UTF8 som standardvärde för parametern Out-File -Encoding . Det här är sessionsspecifikt så du bör placera det i din $profile.

Jag använder detta ofta för att förtilldela värden som jag skriver ganska ofta.

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

Detta accepterar även jokertecken så att du kan ange värden i bulk. Här följer några sätt som du kan använda:

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

Mer detaljerad information finns i den här fantastiska artikeln om automatiska standardinställningar av Michael Sorens.

Regex $Matches

När du använder operatorn -match skapas en automatisk variabel med namnet $matches med resultatet av matchningen. Om du har några underuttryck i din regex visas även dessa undermatchningar.

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

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

Namngivna matchningar

Detta är en av mina favoritfunktioner som de flesta inte känner till. Om du använder en namngiven regexmatchning kan du komma åt den matchningen efter namn på matchningarna.

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

I exemplet ovan är är ett namngivet (?<Name>.*) underuttryck. Det här värdet placeras sedan i egenskapen $Matches.Name .

Group-Object -AsHashtable

En liten känd funktion Group-Object i är att det kan göra vissa datauppsättningar till en hashtable åt dig.

Import-CSV $Path | Group-Object -AsHashtable -Property email

Detta lägger till varje rad i en hashtable och använder den angivna egenskapen som nyckel för att komma åt den.

Kopiera hashtables

En viktig sak att veta är att hashtables är objekt. Och varje variabel är bara en referens till ett objekt. Det innebär att det krävs mer arbete för att göra en giltig kopia av en hashtable.

Tilldela referenstyper

När du har en hashtable och tilldelar den till en andra variabel pekar båda variablerna på samma hashtable.

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]

Detta visar att de är desamma eftersom om du ändrar värdena i den ena ändras även värdena i den andra. Detta gäller även vid överföring av hashtables till andra funktioner. Om dessa funktioner gör ändringar i hashtabellen ändras även originalet.

Ytliga kopior på en nivå

Om vi har en enkel hashtabell som vårt exempel ovan kan vi använda .Clone() för att göra en ytlig kopia.

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]

Detta gör att vi kan göra vissa grundläggande ändringar i den ena som inte påverkar den andra.

Ytliga kopior, kapslade

Anledningen till att den kallas för en ytlig kopia är att den bara kopierar basnivåegenskaperna. Om en av dessa egenskaper är en referenstyp (som en annan hashtable) pekar de kapslade objekten fortfarande på varandra.

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]

Så du kan se att även om jag klonade hashtable, var referensen till person inte klonad. Vi måste göra en djup kopia för att verkligen ha en andra hashtable som inte är länkad till den första.

Djupa kopior

Det finns ett par sätt att göra en djup kopia av en hashtable (och behålla den som en hashtable). Här är en funktion som använder PowerShell för att rekursivt skapa en djup kopia:

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

Den hanterar inga andra referenstyper eller matriser, men det är en bra utgångspunkt.

Ett annat sätt är att använda .Net för att deserialisera det med CliXml som i den här funktionen:

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

För extremt stora hashtables är deserialiseringsfunktionen snabbare när den skalar ut. Det finns dock några saker att tänka på när du använder den här metoden. Eftersom det använder CliXml är det minnesintensivt och om du klonar stora hashtables kan det vara ett problem. En annan begränsning i CliXml är att det finns en djupbegränsning på 48. Om du har en hashtable med 48 lager kapslade hashtables misslyckas kloningen och ingen hashtable matas ut alls.

Något mer?

Jag täckte mycket mark snabbt. Min förhoppning är att du går iväg och lutar något nytt eller förstår det bättre varje gång du läser detta. Eftersom jag täckte hela spektrumet av den här funktionen finns det aspekter som kanske inte gäller för dig just nu. Det är helt OK och är ganska förväntat beroende på hur mycket du arbetar med PowerShell.