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. Gisteravond, na de vergadering van onze gebruikersgroep, legde ik iemand iets over hen uit en ik besefte dat ik dezelfde verwarring over hen had als hij. 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 de blog 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 Hashtable- als een verzameling in de traditionele definitie van een hashtabel ziet. 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 array?

Voordat ik aan de slag ga met wat een Hashtable is, moet ik eerst arrays noemen. 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 foreach gebruiken om de lijst te herhalen of een index 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 oppervlakkig naar arrays gekeken, maar dat zou ze in de juiste context moeten plaatsen terwijl ik overstap naar hashtables.

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 basis opslag 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']

Wanneer ik Kevins leeftijd wil weten, gebruik ik zijn naam om deze op te vragen. 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 bovenstaande Add() methode.

$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 een 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 variabele $env en kiest u de juiste server. U kunt een switch($env){...} gebruiken voor een selectie zoals deze, 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 met de pijplijn 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 de prestaties belangrijk zijn, test u 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 eigenschap Count aangeeft hoeveel waarden deze bevat.

PS> $ageList.Count
2

U kunt dit probleem omzeilen door de eigenschap Values 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 is 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 doorlopen elke sleutel in de hashtabel en gebruiken deze sleutel vervolgens om toegang te krijgen tot de waarde. Dit is een veelvoorkomend patroon bij het werken met hashtables als verzameling.

GetEnumerator()

Dat brengt ons naar GetEnumerator() voor het doorlopen van onze hashtabel.

$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. Bedankt aan Mark Kraus voor het herinneren aan dit.

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

Notitie

U kunt een hashtabel met één sleutel niet klonen. PowerShell genereert een fout. In plaats daarvan converteert u de eigenschap Sleutels naar een array en doorloopt u de array.

@($environments.Keys) | 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
}

We kunnen kenmerken toevoegen en benaderen op de $person als volgt.

$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 $false of nul was, zou die instructie onverwacht $false retourneren.

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

Dit omzeilt dat probleem voor nulwaarden, maar niet $null tegenover niet-bestaande sleutels. Meestal hoeft u dat onderscheid niet te maken, maar er zijn methoden voor wanneer u dat doet.

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

We hebben ook een ContainsValue() voor de 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 methode Remove().

$person.Remove('age')

Als u ze een $null waarde toewijst, blijft u over met een sleutel met een $null waarde.

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

$person = @{}

Probeer in plaats daarvan de methode Clear() te gebruiken.

$person.Clear()

Dit is een van deze exemplaren waarbij met behulp van de methode zelfdocumenterende code wordt gemaakt en de intenties 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. Dit ziet u 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 is de naam die de cmdlet aan die kolom zou geven. Het Expression is een scriptblok dat wordt uitgevoerd waarbij $_ 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 Name inkorpen tot n en Expression om te e terwijl je eraan werkt.

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

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

Aangepaste sorteerexpressie

U kunt een verzameling eenvoudig sorteren als de objecten de gegevens bevatten waarop u wilt sorteren. U kunt de gegevens aan het object toevoegen 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, komt u erachter dat de Sort-Object uw sleutels niet als attributen behandelt. We kunnen dat omzeilen 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 aspecten van hashtables die veel mensen niet al 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 het gebruik van splattingmoeten 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 de $ is wat de splat-bewerking activeert.

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

Ik gebruik 'splatting' als de opdracht te lang wordt. Ik definieer 'te lang' als waardoor mijn venster naar rechts moet scrollen. 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 die een Get-CimInstance-aanroep verpakt met een optioneel $Credential argument.

$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. Vervolgens voeg ik de $Credential toe als deze bestaat. Omdat ik hier splatting gebruik, hoef ik alleen maar één keer de aanroep naar Get-CimInstance in mijn code te hebben. Dit ontwerppatroon is zeer schoon en kan eenvoudig veel optionele parameters verwerken.

Om eerlijk te zijn, kunt u uw opdrachten schrijven om $null-waarden van parameters toe te staan. 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 voorbeeld van "splatting":

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

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

Add-DhcpServerv4Scope @DHCPScope @Common

Ik gebruik deze methode wanneer ik een gemeenschappelijke set parameters heb die ik doorgeef 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 beheren via splatting

Splatting werkt ook op sommige uitvoerbare bestanden die een /param:value syntaxis gebruiken. Robocopy.exeheeft bijvoorbeeld enkele parameters als 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 met de naam location toegevoegd met een lege hashtabel. Toen heb ik de laatste twee items aan die location hashtabel toegevoegd. We kunnen dit allemaal ook in één lijn 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.

Het bekijken van geneste hashtabellen

Zodra je hashtabellen gaat nesten, heb je 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 favoriete opdracht voor het bekijken van deze dingen is ConvertTo-Json, omdat het erg overzichtelijk is en ik vaak JSON gebruik voor andere zaken.

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 heb je gewoon een object nodig en een hashtabel gebruiken om eigenschappen vast te houden werkt gewoon niet. 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 het in eerste instantie niet als een pscustomobject 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 een uitgebreide beschrijving van pscustomobject die u 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 opslaan van een hashtabel in een CSV is een van de moeilijkheden waarnaar ik hierboven verwijs. Converteer uw hashtabel naar een pscustomobject en deze wordt correct opgeslagen in CSV. Dit helpt als u begint met een pscustomobject zodat de kolomvolgorde behouden blijft. Maar u kunt deze indien nodig naar een pscustomobject inline casten.

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

Bekijk nogmaals mijn artikel 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]meer is. 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 parameter Depth 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 wilt dat het een [hashtable] is bij het importeren, moet u de opdrachten Export-CliXml en Import-CliXml gebruiken.

JSON converteren naar Hashtable

Als u JSON moet converteren naar een [hashtable], is er een manier die ik ken om dit te 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 parameter Depth toegevoegd aan ConvertFrom-Json. De standaard Depth 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 om alles zetten en het tot 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.

In feite hoeft uw sleutel geen tekenreeks te zijn, maar is het gemakkelijker om ze te begrijpen 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 array is, moet u de variabele $key in een subexpressie verpakken, zodat deze kan worden gebruikt met gegevenstoegang (.) notatie. U kunt ook de matrixindex ([]) notatie gebruiken.

Gebruik van 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 spreiden naar andere functies. Als u merkt dat u proxyfuncties schrijft, kijk dan eens wat beter naar deze.

Zie about_Automatic_Variables voor meer informatie.

PSBoundParameters onverwacht probleem

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, bevat $PSBoundParameters 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 waarmee UTF8 wordt ingesteld als de standaardwaarde voor de parameter Out-File -Encoding. Dit is sessiespecifiek, zodat u deze in uw $PROFILEmoet plaatsen.

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

Raadpleeg dit geweldige artikel over Automatische standaardinstellingen door Michael Sorensvoor een meer gedetailleerde uiteenzetting.

Regex $Matches

Wanneer u de operator -match gebruikt, wordt er een automatische variabele met de naam $Matches gemaakt met de resultaten van de match. Als u subexpressies in uw regex hebt, worden alle deelmatches 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 match gebruikt, hebt u toegang tot die match op naam bij de matches.

$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 eigenschap $Matches.Name geplaatst.

Group-Object -AsHashtable

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

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 Clone() gebruiken 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 aan de ene zonder invloed 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 heb gekloond, de verwijzing naar person niet is gekloond. 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 deserialisatiefunctie sneller naarmate deze wordt uitgeschaald. Er zijn echter enkele dingen die u moet overwegen als u deze methode gebruikt. Omdat het CliXmlgebruikt, is het geheugenintensief en als u enorme hashtabellen kloont, kan dat een probleem zijn. Een andere beperking van de CliXml- is dat er een maximale diepte van 48 is. Dit betekent dat als je een hashtabel met 48 lagen geneste hashtabellen hebt, het klonen zal mislukken en er helemaal geen hashtabel zal worden uitgevoerd.

Nog iets?

Ik heb snel veel vooruitgang geboekt. Mijn hoop is dat je weggaat met het leren van iets nieuws of het beter begrijpen elke keer dat 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.