Delen via


Alles wat u wilde weten over hashtables

Ik wil een stap terug doen en praten over hashtables. Ik gebruik ze de hele tijd. Ik leerde iemand over hen na onze vergadering van onze gebruikersgroep gisteravond en ik besefte dat ik dezelfde verwarring had over hen als hij had. Hashtables zijn echt belangrijk in PowerShell, dus het is goed om er een solide kennis van te hebben.

Notitie

De oorspronkelijke versie van dit artikel verscheen op het blog dat is geschreven door @KevinMarquette. Het PowerShell-team bedankt Kevin voor het delen van deze inhoud met ons. Bekijk zijn blog op PowerShellExplained.com.

Hashtable als een verzameling dingen

Ik wil dat u eerst een hashtabel als een verzameling ziet in de traditionele definitie van een hashtabel. Deze definitie geeft u een fundamenteel inzicht in hoe ze werken wanneer ze later worden gebruikt voor geavanceerdere dingen. Het overslaan van dit begrip is vaak een bron van verwarring.

Wat is een matrix?

Voordat ik aan de slag ga met wat een Hashtable is, moet ik eerst matrices vermelden. Voor deze discussie is een matrix een lijst of verzameling waarden of objecten.

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

Zodra u uw items in een matrix hebt opgenomen, kunt u deze gebruiken foreach om de lijst te herhalen of een index te gebruiken om toegang te krijgen tot afzonderlijke elementen in de matrix.

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

Write-Output $array[3]

U kunt waarden ook op dezelfde manier bijwerken met behulp van een index.

$array[2] = 13

Ik heb net het oppervlak op matrices gekrabd, maar dat zou ze in de juiste context moeten plaatsen terwijl ik naar hashtables ga.

Wat is een hashtabel?

Ik begin met een eenvoudige technische beschrijving van wat hashtables zijn, in de algemene zin, voordat ik naar de andere manieren ga waarop PowerShell ze gebruikt.

Een hashtabel is een gegevensstructuur, vergelijkbaar met een matrix, behalve dat u elke waarde (object) opslaat met behulp van een sleutel. Het is een basisarchief voor sleutel/waarde. Eerst maken we een lege hashtabel.

$ageList = @{}

U ziet dat accolades, in plaats van haakjes, worden gebruikt om een hashtabel te definiëren. Vervolgens voegen we een item toe met behulp van een sleutel als volgt:

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

$ageList.add( 'Alex', 9 )

De naam van de persoon is de sleutel en de leeftijd is de waarde die ik wil opslaan.

De haken gebruiken voor toegang

Zodra u uw waarden aan de hashtabel hebt toegevoegd, kunt u ze weer ophalen met dezelfde sleutel (in plaats van een numerieke index te gebruiken zoals u voor een matrix zou hebben).

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

Als ik kevins leeftijd wil, gebruik ik zijn naam om het te openen. We kunnen deze methode ook gebruiken om waarden toe te voegen aan of bij te werken in de hashtabel. Dit is net zoals het gebruik van de add() bovenstaande functie.

$ageList = @{}

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

$ageList['Alex'] = 9

Er is nog een syntaxis die u kunt gebruiken voor het openen en bijwerken van waarden die ik in een latere sectie zal behandelen. Als u vanuit een andere taal naar PowerShell komt, moeten deze voorbeelden passen in de wijze waarop u hashtabellen eerder hebt gebruikt.

Hashtables maken met waarden

Tot nu toe heb ik een lege hashtabel gemaakt voor deze voorbeelden. U kunt de sleutels en waarden vooraf invullen wanneer u ze maakt.

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

Als opzoektabel

De werkelijke waarde van dit type hashtabel is dat u deze kunt gebruiken als opzoektabel. Hier volgt een eenvoudig voorbeeld.

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

$server = $environments[$env]

In dit voorbeeld geeft u een omgeving op voor de $env variabele en kiest u de juiste server. U kunt een switch($env){...} voor een selectie als deze gebruiken, maar een hashtabel is een handige optie.

Dit wordt nog beter wanneer u de opzoektabel dynamisch bouwt om deze later te gebruiken. Denk dus na over het gebruik van deze benadering wanneer u iets kruislings moet raadplegen. Ik denk dat we dit nog meer zouden zien als PowerShell niet zo goed was in het filteren op de pijp met Where-Object. Als u zich ooit in een situatie bevindt waarin de prestaties van belang zijn, moet deze aanpak worden overwogen.

Ik zal niet zeggen dat het sneller is, maar het past wel in de regel van Als prestaties belangrijk zijn, test het.

Meervoudige selectie

Over het algemeen beschouwt u een hashtabel als een sleutel/waardepaar, waarbij u één sleutel opgeeft en één waarde opgeeft. Met PowerShell kunt u een matrix met sleutels opgeven om meerdere waarden op te halen.

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

In dit voorbeeld gebruik ik dezelfde opzoekhashtabel van hierboven en geef ik drie verschillende matrixstijlen op om de overeenkomsten op te halen. Dit is een verborgen juweeltje in PowerShell waarvan de meeste mensen zich niet bewust zijn.

Hashtables herhalen

Omdat een hashtabel een verzameling sleutel-waardeparen is, kunt u deze anders herhalen dan voor een matrix of een normale lijst met items.

Het eerste wat u moet zien, is dat als u uw hashtable doorsluist, de pijp het als één object behandelt.

PS> $ageList | Measure-Object
count : 1

Hoewel de .count eigenschap aangeeft hoeveel waarden deze bevat.

PS> $ageList.count
2

U kunt dit probleem omzeilen door de .values eigenschap te gebruiken als u alleen de waarden nodig hebt.

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

Het is vaak handiger om de sleutels op te sommen en deze te gebruiken om toegang te krijgen tot de waarden.

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

Hier volgt hetzelfde voorbeeld met een foreach(){...} lus.

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

We lopen elke sleutel in de hashtabel en gebruiken deze vervolgens om toegang te krijgen tot de waarde. Dit is een veelvoorkomend patroon bij het werken met hashtables als een verzameling.

GetEnumerator()

Dat brengt ons ertoe om GetEnumerator() onze hashtabel te herhalen.

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

De enumerator geeft u elk sleutel-/waardepaar na elkaar. Het is speciaal ontworpen voor deze use case. Dank u aan Mark Kraus om me eraan te herinneren.

BadEnumeration

Een belangrijk detail is dat u een hashtabel niet kunt wijzigen terwijl deze wordt geïnventariseerd. Als we beginnen met ons basisvoorbeeld $environments :

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

En het instellen van elke sleutel op dezelfde serverwaarde mislukt.

$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

Dit mislukt ook, ook al lijkt het erop dat het ook goed zou moeten zijn:

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

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

De truc voor deze situatie is om de sleutels te klonen voordat u de opsomming uitvoert.

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

Hashtabel als een verzameling eigenschappen

Tot nu toe waren het type objecten dat we in onze hashtabel hebben geplaatst, allemaal hetzelfde type object. Ik gebruikte leeftijden in al die voorbeelden en de sleutel was de naam van de persoon. Dit is een uitstekende manier om het te bekijken wanneer uw verzameling objecten elk een naam heeft. Een andere veelgebruikte manier om hashtables in PowerShell te gebruiken, is door een verzameling eigenschappen te bewaren waarbij de sleutel de naam van de eigenschap is. In dit volgende voorbeeld stap ik in dat idee.

Op eigenschappen gebaseerde toegang

Het gebruik van op eigenschappen gebaseerde toegang verandert de dynamiek van hashtables en hoe u deze kunt gebruiken in PowerShell. Hier volgt ons gebruikelijke voorbeeld van hierboven, waarbij de sleutels als eigenschappen worden behandeld.

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

Net als in de bovenstaande voorbeelden voegt dit voorbeeld deze sleutels toe als deze nog niet bestaan in de hashtabel. Afhankelijk van hoe u uw sleutels hebt gedefinieerd en wat uw waarden zijn, is dit een beetje vreemd of een perfecte pasvorm. Het voorbeeld van de leeftijdslijst heeft tot nu toe goed gewerkt. We hebben hiervoor een nieuw voorbeeld nodig om ons in de toekomst goed te voelen.

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

En we kunnen zo $person kenmerken toevoegen en openen.

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

Plotseling begint deze hashtable zich te voelen en te fungeren als een object. Het is nog steeds een verzameling dingen, dus alle bovenstaande voorbeelden zijn nog steeds van toepassing. We benaderen het gewoon vanuit een ander oogpunt.

Controleren op sleutels en waarden

In de meeste gevallen kunt u gewoon testen op de waarde met zoiets als volgt:

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

Het is eenvoudig, maar is de bron geweest van veel bugs voor mij, omdat ik een belangrijk detail in mijn logica over het hoofd had gezien. Ik begon het te gebruiken om te testen of er een sleutel aanwezig was. Wanneer de waarde of nul was $false , zou die instructie onverwacht worden geretourneerd $false .

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

Dit werkt rond dat probleem voor nulwaarden, maar niet $null versus niet-bestaande sleutels. Meestal hoeft u dat onderscheid niet te maken, maar er zijn functies voor wanneer u dat doet.

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

We hebben ook een ContainsValue() situatie waarin u moet testen op een waarde zonder de sleutel te kennen of de hele verzameling te herhalen.

Sleutels verwijderen en wissen

U kunt sleutels verwijderen met de .Remove() functie.

$person.remove('age')

Als u ze een $null waarde toewijst, hoeft u alleen maar een sleutel te gebruiken die een $null waarde heeft.

Een veelgebruikte manier om een hashtabel te wissen, is door deze te initialiseren in een lege hashtabel.

$person = @{}

Probeer in plaats daarvan de clear() functie te gebruiken.

$person.clear()

Dit is een van deze gevallen waarbij met behulp van de functie zelfdocumenterende code wordt gemaakt en de bedoelingen van de code zeer schoon worden gemaakt.

Alle leuke dingen

Geordende hashtabellen

Hashtables worden standaard niet geordend (of gesorteerd). In de traditionele context maakt de volgorde niet uit wanneer u altijd een sleutel gebruikt voor toegang tot waarden. Mogelijk wilt u dat de eigenschappen in de volgorde blijven waarin u ze definieert. Gelukkig is er een manier om dat te doen met het ordered trefwoord.

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

Wanneer u nu de sleutels en waarden opsommen, blijven ze in die volgorde.

Inline-hashtabellen

Wanneer u een hashtabel op één regel definieert, kunt u de sleutel-/waardeparen scheiden met een puntkomma.

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

Dit is handig als u ze in de pijp maakt.

Aangepaste expressies in algemene pijplijnopdrachten

Er zijn enkele cmdlets die ondersteuning bieden voor het gebruik van hashtables om aangepaste of berekende eigenschappen te maken. U ziet dit meestal met Select-Object en Format-Table. De hashtables hebben een speciale syntaxis die er als volgt uitziet wanneer deze volledig is uitgevouwen.

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

De name cmdlet zou die kolom labelen. Het expression is een scriptblok dat wordt uitgevoerd waar $_ de waarde van het object in de pijp is. Dit script in actie:

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

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

Ik heb dat in een variabele geplaatst, maar het kan gemakkelijk inline worden gedefinieerd en je kunt verkorten name tot n en expression met e terwijl je er aan zit.

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

Ik vind het persoonlijk niet leuk hoelang dat opdrachten doet en het bevordert vaak een aantal slechte gedragingen waar ik niet in zal komen. Ik maak waarschijnlijk een nieuwe hashtabel of pscustomobject met alle velden en eigenschappen die ik wil in plaats van deze methode in scripts te gebruiken. Maar er is veel code die dit doet, dus ik wilde dat je er rekening mee moet houden. Ik heb het over het maken van een pscustomobject later.

Aangepaste sorteerexpressie

U kunt een verzameling eenvoudig sorteren als de objecten de gegevens bevatten waarop u wilt sorteren. U kunt de gegevens toevoegen aan het object voordat u het sorteert of een aangepaste expressie voor Sort-Objectmaken.

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

In dit voorbeeld neem ik een lijst met gebruikers en gebruik ik een aangepaste cmdlet om aanvullende informatie op te halen voor de sortering.

Een lijst met hashtabellen sorteren

Als u een lijst met hashtabellen hebt die u wilt sorteren, zult u merken dat uw Sort-Object sleutels niet als eigenschappen worden behandeld. We kunnen dit afronden met behulp van een aangepaste sorteerexpressie.

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

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

Hashtables splatting bij cmdlets

Dit is een van mijn favoriete dingen over hashtables die veel mensen niet vroeg ontdekken. Het idee is dat u in plaats van alle eigenschappen aan een cmdlet op één regel op te geven, ze eerst in een hashtabel kunt inpakken. Vervolgens kunt u de hashtabel op een speciale manier aan de functie geven. Hier volgt een voorbeeld van het maken van een DHCP-bereik op de normale manier.

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"

Zonder splatting te gebruiken, moeten al deze dingen op één regel worden gedefinieerd. Het schuift van het scherm af of loopt terug waar het voelt. Vergelijk dat nu met een opdracht die gebruikmaakt van 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

Het gebruik van het @ teken in plaats van het $ is wat de splat-bewerking aanroept.

Neem even de tijd om te waarderen hoe eenvoudig dat voorbeeld is om te lezen. Ze zijn exact dezelfde opdracht met dezelfde waarden. De tweede is gemakkelijker te begrijpen en te onderhouden in de toekomst.

Ik gebruik splatting wanneer de opdracht te lang wordt. Ik definieer te lang omdat mijn venster naar rechts schuift. Als ik drie eigenschappen voor een functie heb bereikt, is het mogelijk dat ik deze herschrijf met behulp van een geplatte hashtabel.

Splatting voor optionele parameters

Een van de meest voorkomende manieren waarop ik splatting gebruik, is het omgaan met optionele parameters die afkomstig zijn van een andere plaats in mijn script. Stel dat ik een functie heb waarmee een Get-CIMInstance aanroep met een optioneel $Credential argument wordt verpakt.

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

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

Get-CIMInstance @CIMParams

Ik begin met het maken van mijn hashtabel met gemeenschappelijke parameters. Dan voeg ik de $Credential als het bestaat toe. Omdat ik hier splatting gebruik, hoef ik de aanroep maar één keer in mijn code te Get-CIMInstance hebben. Dit ontwerppatroon is zeer schoon en kan eenvoudig veel optionele parameters verwerken.

Om eerlijk te zijn, kunt u uw opdrachten schrijven om waarden voor parameters toe te staan $null . U hebt alleen niet altijd controle over de andere opdrachten die u aanroept.

Meerdere splats

U kunt meerdere hashtables naar dezelfde cmdlet splatten. Als we teruggaan naar ons oorspronkelijke splattingsvoorbeeld:

$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

Ik gebruik deze methode wanneer ik een gemeenschappelijke set parameters heb die ik doorgeeft aan veel opdrachten.

Splatting voor schone code

Er is niets mis met het splatten van één parameter als u code schoner maakt.

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

Uitvoerbare bestanden splatting

Splatting werkt ook op sommige uitvoerbare bestanden die een /param:value syntaxis gebruiken. Robocopy.exeheeft bijvoorbeeld enkele parameters zoals deze.

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

Ik weet niet dat dit zo nuttig is, maar ik vond het interessant.

Hashtables toevoegen

Hashtables ondersteunen de toevoegingsoperator om twee hashtabellen te combineren.

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

Dit werkt alleen als de twee hashtabellen geen sleutel delen.

Geneste hashtabellen

We kunnen hashtables gebruiken als waarden in een hashtabel.

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

Ik begon met een eenvoudige hashtabel met twee sleutels. Ik heb een sleutel toegevoegd die is aangeroepen location met een lege hashtabel. Toen heb ik de laatste twee items aan die location hashtabel toegevoegd. We kunnen dit allemaal ook inline doen.

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

Hiermee maakt u dezelfde hashtabel die we hierboven hebben gezien en heeft u op dezelfde manier toegang tot de eigenschappen.

$person.location.city
Austin

Er zijn veel manieren om de structuur van uw objecten te benaderen. Hier volgt een tweede manier om te kijken naar een geneste hashtabel.

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

Dit combineert het concept van het gebruik van hashtables als een verzameling objecten en een verzameling eigenschappen. De waarden zijn nog steeds eenvoudig toegankelijk, zelfs wanneer ze zijn genest met behulp van de gewenste benadering.

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

Ik heb de neiging om de punteigenschap te gebruiken als ik het behandel als een eigenschap. Dit zijn over het algemeen dingen die ik statisch in mijn code heb gedefinieerd en ik ken ze boven in mijn hoofd. Als ik de lijst moet doorlopen of programmatisch toegang wil krijgen tot de sleutels, gebruik ik de haken om de sleutelnaam op te geven.

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

Als u hashtables kunt nesten, hebt u veel flexibiliteit en opties.

Kijken naar geneste hashtabellen

Zodra u hashtabellen gaat nesten, hebt u een eenvoudige manier nodig om ze vanuit de console te bekijken. Als ik die laatste hashtable neem, krijg ik een uitvoer die er als volgt uitziet en het gaat alleen zo diep:

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

Mijn ga naar opdracht om deze dingen te bekijken, is ConvertTo-JSON omdat het erg schoon is en ik vaak JSON op andere dingen gebruik.

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

Zelfs als u JSON niet kent, moet u kunnen zien wat u zoekt. Er is een Format-Custom opdracht voor gestructureerde gegevens zoals deze, maar ik vind de JSON-weergave nog steeds beter.

Objecten maken

Soms hoeft u alleen maar een object te hebben en een hashtabel te gebruiken om eigenschappen vast te houden, wordt de taak niet uitgevoerd. Meestal wilt u de sleutels zien als kolomnamen. Een pscustomobject maakt dat makkelijk.

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

$person

name  age
----  ---
Kevin  36

Zelfs als u deze niet als pscustomobject eerste maakt, kunt u deze altijd later casten wanneer dat nodig is.

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

[pscustomobject]$person

name  age
----  ---
Kevin  36

Ik heb al gedetailleerde schrijfbewerkingen voor pscustomobject die je na deze moet lezen. Het bouwt voort op veel van de dingen die hier zijn geleerd.

Hashtabellen lezen en schrijven naar bestand

Opslaan in CSV

Problemen met het verkrijgen van een hashtabel om op te slaan in een CSV is een van de problemen waarnaar ik hierboven verwijst. Converteer uw hashtabel naar een pscustomobject en wordt correct opgeslagen in CSV. Dit helpt als u begint met een pscustomobject zodanige volgorde dat de kolomvolgorde behouden blijft. Maar u kunt het indien nodig naar een pscustomobject inline casten.

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

Bekijk nogmaals mijn schrijfbewerking over het gebruik van een pscustomobject.

Een geneste hashtabel opslaan in bestand

Als ik een geneste hashtabel moet opslaan in een bestand en het vervolgens opnieuw moet lezen, gebruik ik de JSON-cmdlets om dit te doen.

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

Er zijn twee belangrijke punten over deze methode. Ten eerste is dat de JSON uit meerdere regels is geschreven, dus ik moet de -Raw optie gebruiken om deze terug te lezen in één tekenreeks. De tweede is dat het geïmporteerde object geen [hashtable]. Het is nu een [pscustomobject] en dat kan problemen veroorzaken als u dit niet verwacht.

Let op diep geneste hashtables. Wanneer u deze converteert naar JSON, krijgt u mogelijk niet de resultaten die u verwacht.

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

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

Gebruik de parameter Diepte om ervoor te zorgen dat u alle geneste hashtabellen hebt uitgebreid.

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

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

Als u deze [hashtable] wilt importeren, moet u de Export-CliXml en Import-CliXml opdrachten gebruiken.

JSON converteren naar Hashtable

Als u JSON moet converteren naar een [hashtable], is er een manier waarop ik dit moet doen met de JavaScriptSerializer in .NET.

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

Vanaf PowerShell v6 maakt JSON-ondersteuning gebruik van de NewtonSoft-JSON.NET en voegt hashtable-ondersteuning toe.

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

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

PowerShell 6.2 heeft de diepteparameter toegevoegd aan ConvertFrom-Json. De standaarddiepte is 1024.

Rechtstreeks vanuit een bestand lezen

Als u een bestand hebt dat een hashtabel bevat met behulp van de PowerShell-syntaxis, kunt u het rechtstreeks importeren.

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

Het importeert de inhoud van het bestand in een scriptblocken controleert vervolgens of het geen andere PowerShell-opdrachten bevat voordat het wordt uitgevoerd.

Wist u dat een modulemanifest (het psd1-bestand) slechts een hashtabel is?

Sleutels kunnen elk object zijn

Meestal zijn de sleutels alleen tekenreeksen. Dus we kunnen aanhalingstekens plaatsen en het een sleutel maken.

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

Je kunt een aantal vreemde dingen doen die je misschien niet hebt gerealiseerd.

$person.'full name'

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

Alleen omdat je iets kunt doen, betekent het niet dat je moet. Die laatste ziet er gewoon uit als een fout die wacht en kan gemakkelijk verkeerd worden begrepen door iedereen die uw code leest.

Technisch gezien hoeft uw sleutel geen tekenreeks te zijn, maar ze zijn gemakkelijker te bedenken als u alleen tekenreeksen gebruikt. Indexering werkt echter niet goed met de complexe sleutels.

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

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

Het openen van een waarde in de hashtabel met de sleutel werkt niet altijd. Voorbeeld:

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

Wanneer de sleutel een matrix is, moet u de $key variabele in een subexpressie verpakken, zodat deze kan worden gebruikt met lidtoegang (.) notatie. U kunt ook matrixindex-notatie ([]) gebruiken.

Gebruiken in automatische variabelen

$PSBoundParameters

$PSBoundParameters is een automatische variabele die alleen in de context van een functie bestaat. Het bevat alle parameters waarmee de functie is aangeroepen. Dit is niet precies een hashtabel, maar dicht genoeg om het als een te behandelen.

Dit omvat het verwijderen van sleutels en het afplatten naar andere functies. Als u merkt dat u proxyfuncties schrijft, bekijkt u deze.

Zie about_Automatic_Variables voor meer informatie.

PSBoundParameters gotcha

Een belangrijk punt om te onthouden is dat dit alleen de waarden bevat die als parameters worden doorgegeven. Als u ook parameters met standaardwaarden hebt, maar niet wordt doorgegeven door de aanroeper, $PSBoundParameters bevat deze waarden niet. Dit wordt vaak over het hoofd gezien.

$PSDefaultParameterValues

Met deze automatische variabele kunt u standaardwaarden toewijzen aan een cmdlet zonder de cmdlet te wijzigen. Bekijk dit voorbeeld.

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

Hiermee voegt u een vermelding toe aan de $PSDefaultParameterValues hashtabel die wordt ingesteld UTF8 als de standaardwaarde voor de Out-File -Encoding parameter. Dit is sessiespecifiek, dus u moet deze in uw $profile.

Ik gebruik dit vaak om vooraf waarden toe te wijzen die ik vaak typ.

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

Dit accepteert ook jokertekens, zodat u bulksgewijs waarden kunt instellen. Hier volgen enkele manieren waarop u dit kunt gebruiken:

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

Zie dit geweldige artikel over Automatische standaardwaarden van Michael Sorens voor een uitgebreidere uitsplitsing.

Regex-$Matches

Wanneer u de -match operator gebruikt, wordt er een automatische variabele $matches gemaakt met de resultaten van de overeenkomst. Als u subexpressies in uw regex hebt, worden deze subovereenkomsten ook vermeld.

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

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

Benoemde overeenkomsten

Dit is een van mijn favoriete functies die de meeste mensen niet kennen. Als u een benoemde regex-overeenkomst gebruikt, hebt u toegang tot die overeenkomst op naam voor de overeenkomsten.

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

In het bovenstaande voorbeeld is het (?<Name>.*) een benoemde subexpressie. Deze waarde wordt vervolgens in de $Matches.Name eigenschap geplaatst.

Group-Object -AsHashtable

Een weinig bekende functie Group-Object is dat het sommige gegevenssets voor u kan omzetten in een hashtabel.

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

Hiermee voegt u elke rij toe aan een hashtabel en gebruikt u de opgegeven eigenschap als de sleutel om deze te openen.

Hashtables kopiëren

Belangrijk om te weten is dat hashtables objecten zijn. En elke variabele is slechts een verwijzing naar een object. Dit betekent dat er meer werk nodig is om een geldige kopie van een hashtabel te maken.

Verwijzingstypen toewijzen

Wanneer u één hashtabel hebt en deze toewijst aan een tweede variabele, wijzen beide variabelen naar dezelfde hashtabel.

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]

Dit geeft aan dat ze hetzelfde zijn, omdat het wijzigen van de waarden in de ene ook de waarden in de andere wijzigt. Dit geldt ook bij het doorgeven van hashtabellen aan andere functies. Als deze functies wijzigingen aanbrengen in die hashtabel, wordt het origineel ook gewijzigd.

Ondiepe kopieën, één niveau

Als we een eenvoudige hashtabel hebben zoals hierboven, kunnen we gebruiken .Clone() om een ondiepe kopie te maken.

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]

Hierdoor kunnen we enkele basiswijzigingen aanbrengen in een wijziging die niet van invloed is op de andere.

Ondiepe kopieën, genest

De reden waarom het een ondiepe kopie wordt genoemd, is omdat alleen de eigenschappen op basisniveau worden gekopieerd. Als een van deze eigenschappen een verwijzingstype is (zoals een andere hashtabel), verwijzen die geneste objecten nog steeds naar elkaar.

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]

U kunt dus zien dat hoewel ik de hashtabel kloonde, de verwijzing naar person niet gekloond was. We moeten een diepe kopie maken om echt een tweede hashtabel te hebben die niet is gekoppeld aan de eerste.

Diepe kopieën

Er zijn een aantal manieren om een diepe kopie van een hashtabel te maken (en deze als een hashtabel te bewaren). Hier volgt een functie die PowerShell gebruikt om recursief een diepe kopie te maken:

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

Er worden geen andere referentietypen of matrices verwerkt, maar het is een goed uitgangspunt.

Een andere manier is om .Net te gebruiken om het te deserialiseren met behulp van CliXml , zoals in deze functie:

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

Voor extreem grote hashtables is de deserialiserende functie sneller naarmate deze wordt uitgeschaald. Er zijn echter enkele dingen die u moet overwegen bij het gebruik van deze methode. Omdat cliXml wordt gebruikt, is het geheugenintensief en als u enorme hashtabellen kloont, kan dat een probleem zijn. Een andere beperking van cliXml is er een dieptebeperking van 48. Dit betekent dat als u een hashtabel met 48 lagen geneste hashtabellen hebt, het klonen mislukt en wordt helemaal geen hashtabel uitgevoerd.

Nog iets?

Ik heb snel veel grond bedekt. Mijn hoop is dat je wegloopt om iets nieuws te leunen of het beter te begrijpen telkens wanneer je dit leest. Omdat ik het volledige spectrum van deze functie heb behandeld, zijn er aspecten die op dit moment mogelijk niet op u van toepassing zijn. Dat is perfect ok en is een beetje verwacht, afhankelijk van hoeveel u met PowerShell werkt.