Allt du ville veta om hashtables
Jag vill ta ett steg tillbaka och prata om hashtables. Jag använder dem hela tiden nu. Jag lärde 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.
Anteckning
Den ursprungliga versionen av den här artikeln visades på bloggen skriven av @KevinMarquette. PowerShell-teamet tackar Kevin för att han har 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 med 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 hashtabell ä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 hashtabell. Sedan lägger vi till ett objekt med en nyckel som denna:
$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 lägger 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 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 uppslagstabell
Det verkliga värdet för den här typen av 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 bra 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 säger inte 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 den som ett objekt.
PS> $ageList | Measure-Object
count : 1
Även om egenskapen anger hur många värden den .count innehåller.
PS> $ageList.count
2
Du kommer runt det här problemet genom att använda .values egenskapen om allt du behöver är bara 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 hash-tabellen 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 för iterering GetEnumerator() ö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 för 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 nycklarna om de inte redan finns i hashtabellen. Beroende på hur du har definierat dina nycklar och dina värden är detta antingen lite konstigt eller en perfekt passform. Exemplet med å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'
Plötsligt börjar denna hashtable kännas och fungera 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 gäller 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 funktionen .Remove() .
$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 hash-tabell är att bara initiera den till en tom hash-tabell.
$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 hashtabeller
Som standard sorteras inte hashtabeller (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 stanna kvar 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 hashtabeller
När du definierar en hash-tabell på en rad kan du separera nyckel/värde-paren med 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 hashtabeller 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 }
}
name är vad cmdleten skulle märka 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 i. Jag är mer benägna att skapa en ny hash-tabell 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 ett 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 -Parameter @{ 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 hashtabeller
Om du har en lista över hashtabeller som du vill sortera ser du att Sort-Object inte behandlar dina nycklar som egenskaper. Vi kan få en avrunda 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 på cmdletar
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 hash-tabellen 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. Den rullar antingen bort från skärmen eller omsluter var den än känns. 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 en stund för att uppskatta hur enkelt 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ång. Jag definierar för länge så att fönstret rullas å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 hash-tabell med vanliga parametrar. Sedan lägger jag till $Credential om den finns.
Eftersom jag använder splatting här, behöver jag bara ha samtalet 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 kommandon som du anropar.
Flera splats
Du kan splat flera hashtables till samma cmdlet. Om vi går tillbaka till vårt ursprungliga plattningsexempel:
$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 gör koden renare.
$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 hashtabeller
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 hashtabeller
Vi kan använda hashtables som värden i en hash-tabell.
$person = @{
name = 'Kevin'
age = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'
Jag började med en grundläggande hashtabell som innehåller två nycklar. Jag har lagt till en nyckel med namnet location med en tom hash-tabell. Sedan lade jag till de två sista objekten i hash-tabellen location . Vi kan göra allt detta infogat också.
$person = @{
name = 'Kevin'
age = 36
location = @{
city = 'Austin'
state = 'TX'
}
}
Detta skapar samma hash-tabell 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 hash-tabell.
$people = @{
Kevin = @{
age = 36
city = 'Austin'
}
Alex = @{
age = 9
city = 'Austin'
}
}
Detta blandar begreppet att använda hashtabeller 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å 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
}
Om du har möjlighet att kapsla hashtabeller får du stor flexibilitet och många alternativ.
Titta på kapslade hashtabeller
Så fort du börjar kapsla hashtabeller behöver du ett enkelt sätt att titta på dem från konsolen. Om jag tar den sista hashtabellen får jag en utdata som ser ut så här och den 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 hash-tabell för att lagra egenskaper får bara inte jobbet gjort. Oftast vill du se nycklarna som kolumnnamn. En 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 konvertera den senare när det behövs.
$person = @{
name = 'Kevin'
age = 36
}
[pscustomobject]$person
name age
---- ---
Kevin 36
Jag har redan detaljerade skriva upp för pscustomobject att du ska gå läsa efter den här. Den bygger på många av de saker som har lärts här.
Läsa och skriva hashtabeller till filen
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 write-up om att använda en pscustomobject.
Spara en kapslad hash-tabell till en fil
Om jag behöver spara en kapslad hashtabell 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ör det första skrivs JSON ut i flera ledningar så jag måste använda -Raw alternativet för att läsa tillbaka det till en enda sträng. Det andra är att det importerade objektet inte längre är en [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 hashtabeller. 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 hashtabeller.
@{ 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-stödet 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 Depth i ConvertFrom-Json. Standarddjupet är 1024.
Läsa direkt från en fil
Om du har en fil som innehåller en hash-tabell med 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 hash-tabell?
Nycklar kan vara valfritt objekt
För det mesta är nycklarna bara strängar. Så vi kan sätta citat 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 din nyckel 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
Det fungerar inte alltid att komma åt ett värde i hash-tabellen med dess nyckel. Exempel:
$key = $ht.keys[0]
$ht.$($key)
a
$ht[$key]
a
När nyckeln är en matris måste du omsluta variabeln $key i ett underuttryck så att den kan användas med medlemsåtkomstnotation (.). Eller så kan du använda matrisindexnotation ([]).
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 hash-tabell men tillräckligt nära för att du kan behandla den som en.
Det inkluderar att ta bort nycklar och stänka 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 $PSDefaultParameterValues hash-tabellen 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 grupp. Här är några sätt som du kan använda:
$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential
En mer detaljerad analys 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 matchningens resultat. Om du har några underuttryck i ditt 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 regex-matchning 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 ett namngivet (?<Name>.*) underuttryck. Det här värdet placeras sedan i $Matches.Name egenskapen .
Group-Object -AsHashtable
En liten känd funktion Group-Object i är att den kan göra vissa datauppsättningar till en hash-tabell åt dig.
Import-CSV $Path | Group-Object -AsHashtable -Property email
Varje rad läggs till i en hash-tabell och den angivna egenskapen används som nyckel för att komma åt den.
Kopiera hashtabeller
En viktig sak att veta är att hashtabeller ä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 hash-tabell.
Tilldela referenstyper
När du har en hash-tabell och tilldelar den till en andra variabel pekar båda variablerna på samma hash-tabell.
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 samma eftersom om du ändrar värdena i en kommer även värdena i den andra att ändras. Detta gäller även när hashtabeller skickas till andra funktioner. Om dessa funktioner gör ändringar i hashtabellen ändras även originalet.
Grunda kopior, enkel 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.
Grunda 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 hashtabell) 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 hashtabellen, klonades inte referensen till person . Vi måste göra en djup kopia för att verkligen ha en andra hash-tabell 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 hash-tabell (och behålla den som en hash-tabell). Här är en funktion som använder PowerShell för att rekursivt skapa en djupkopia:
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 inte några 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 hashtabeller är deserialiseringsfunktionen snabbare när den skalar ut. Det finns dock vissa 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 enorma hashtabeller kan det vara ett problem. En annan begränsning i CliXml är att det finns en djupbegränsning på 48. Om du har en hash-tabell med 48 lager kapslade hashtabeller 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.