PowerShell ForEach Loops: Types en Beste Praktijken

Wanneer je voor het eerst begint met het schrijven van PowerShell-scripts, kom je onvermijdelijk op een punt waar je meerdere items uit een verzameling moet verwerken. Het is dan dat je moet duiken in PowerShell foreach-lussen en moet leren waar ze allemaal over gaan.

Bijna alle programmeertalen hebben een constructie genaamd lussen; PowerShell is niet anders. Een van de meest populaire soorten lussen in PowerShell is de foreach-lus. In de meest basale vorm leest een foreach-lus een volledige verzameling items en voert voor elk item een soort code uit.

Een van de meest verwarrende aspecten van de PowerShell foreach-lus voor beginners is alle opties die je hebt. Er is niet slechts één manier om elk item in een verzameling te verwerken; er zijn er drie!

In dit artikel ga je leren hoe elk type foreach-lus werkt en wanneer je de ene boven de andere moet gebruiken. Tegen de tijd dat je klaar bent met dit artikel, heb je een goed begrip van elk type foreach-lus.

Basics van de PowerShell ForEach-lus

Een van de meest voorkomende soorten lussen die je in PowerShell zult gebruiken, is het type foreach-lus. Een foreach-lus leest een set objecten (itereert) en wordt voltooid wanneer hij klaar is met de laatste. De verzameling objecten die worden gelezen, wordt meestal voorgesteld door een array of een hashtable.

LET OP: De term ‘iteratie’ is een programmeerterm die verwijst naar elke uitvoering van een lus. Elke keer dat een lus een cyclus voltooit, wordt dit een iteratie genoemd. Het uitvoeren van een lus over een set objecten wordt vaak aangeduid als itereren over de set.

Misschien moet je een tekstbestand maken over verschillende mappen verspreid over een bestandssysteem. Laten we zeggen dat de map-paden C:\Folder, C:\Program Files\Folder2 en C:\Folder3 zijn. Zonder een lus zouden we de Add-Content cmdlet drie keer moeten refereren.

Add-Content -Path 'C:\Folder\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Program Files\Folder2\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Folder2\textfile.txt' -Value 'This is the content of the file'

Wat is het enige verschil tussen elk van deze commandoreferenties? Het is de waarde van Path. De waarde voor Path is het enige wat verandert tussen elk van deze.

Je dupliceert veel code. Je verspilt tijd met typen en je opent jezelf voor problemen in de toekomst. In plaats daarvan moet je een “set” maken die alleen de items bevat die veranderen. Voor dit voorbeeld gebruiken we een array.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

Je hebt nu elk van deze paden opgeslagen in een enkele “set” of array. Je bent nu klaar om een lus te gebruiken om over elk van deze elementen te itereren. Voordat je dat doet, is het echter een goed moment om een onderwerp te noemen dat meestal struikelt bij degenen die nieuw zijn in PowerShell. In tegenstelling tot andere soorten lussen zijn foreach-lussen niet hetzelfde.

Er zijn technisch gezien drie soorten foreach-lussen in PowerShell. Hoewel ze elk op dezelfde manier worden gebruikt, is het belangrijk om het verschil te begrijpen.

De foreach-verklaring

Het eerste type foreach-lus is een verklaring. foreach is een interne PowerShell-sleutelwoord dat geen cmdlet of functie is. De foreach-verklaring wordt altijd gebruikt in de vorm: foreach ($i in $array).

Gebruikmakend van het bovenstaande voorbeeld, vertegenwoordigt de variabele $i de iterator of de waarde van elk item in $path terwijl het over elk element in de array itereert.

Merk op dat de iterator variabele niet per se $i hoeft te zijn. De variabelenaam kan van alles zijn.

In het onderstaande voorbeeld kunt u dezelfde taak volbrengen als het herhalen van de referentie Add-Content door dit te doen:

# Maak een array van mappen aan
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

# Voer iteratie uit om hetzelfde bestand in elke map aan te maken
foreach ($i in $folders) {
    Add-Content -Path "$i\SampleFile.txt" -Value "This is the content of the file"
}

De foreach-verklaring staat bekend als een sneller alternatief dan het gebruik van de ForEach-Object-cmdlet.

De ForEach-Object CmdLet

Als foreach een verklaring is en alleen op één manier kan worden gebruikt, is ForEach-Object een cmdlet met parameters die op veel verschillende manieren kunnen worden gebruikt. Net als de foreach-verklaring kan de ForEach-Object-cmdlet over een set objecten itereren. Alleen dit keer geeft het die set objecten door en de actie die op elk object moet worden uitgevoerd als een parameter zoals hieronder wordt getoond.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders | ForEach-Object (Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file")

Opmerking: Om het verwarrend te maken, heeft de ForEach-Object-cmdlet een alias genaamd foreach. Afhankelijk van hoe de term “foreach” wordt aangeroepen, wordt de foreach-verklaring uitgevoerd, of wordt de ForEach-Object uitgevoerd.

A good way to differentiate these situations is to notice if the term “foreach” is followed by ($someVariable in $someSet). Otherwise, in any other context, the code author is probably using the alias for ForEach-Object.

De foreach()-methode

Een van de nieuwste foreach loops werd geïntroduceerd in PowerShell v4 en heet een foreach() methode. Deze methode bestaat op een array of collectie object. De foreach() methode heeft een standaard scriptblok parameter dat de acties bevat die uitgevoerd moeten worden bij elke iteratie, net als de anderen.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders.ForEach({
	Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file"
})

Het belangrijkste verschil met de foreach() methode is hoe het intern werkt.

Het gebruik van de foreach() methode is aanzienlijk sneller en dit is merkbaar bij grote sets. Het wordt aanbevolen om deze methode te gebruiken als dat mogelijk is.

Mini-project: Itereren over een set servernamen

Een van de meest voorkomende toepassingen voor een loop in PowerShell is het lezen van een set servers uit een bepaalde bron en een actie uitvoeren op elke server. Voor ons eerste mini-project, laten we wat code schrijven waarmee we servernamen (één per regel) uit een tekstbestand kunnen lezen en elke server kunnen pingen om te bepalen of ze online zijn of niet.

List of server names in a text file

Voor deze workflow, welk type loop denk je dat het beste werkt?

Let op dat ik het woord “set” noemde, zoals in “set van servers”. Dat is al een hint. Je hebt al een specifiek aantal servernamen en je wilt een actie uitvoeren op elk van hen. Dit klinkt als een perfecte kans om de meest gebruikte PowerShell loop te proberen; de foreach loop.

De eerste taak is het maken van een set servernamen in code. De meest gebruikelijke manier om dat te doen is door het maken van een array. Gelukkig voor ons retourneert Get-Content, standaard, een array waarbij elk element in de array wordt vertegenwoordigd door een enkele regel in het tekstbestand.

Eerst, maak een array van al mijn servernamen en noem het $servers.

$servers = Get-Content .\servers.txt

Nu je de array hebt gemaakt, moet je nu bevestigen of elke server online is of niet. Een geweldig cmdlet om de verbinding met een server te testen is genaamd Test-Connection. Dit cmdlet voert een paar verbindingstests uit op een computer om te zien of deze online is of niet.

Door Test-Connection uit te voeren binnen een foreach-lus om elke regel in het bestand te lezen, kun je elke servernaam vertegenwoordigd door de variabele $server doorgeven aan Test-Connection. Dit is hoe PowerShell door een tekstbestand kan lussen. Je kunt hieronder een voorbeeld zien van hoe dit werkt.

foreach ($server in $servers) {
	try {
		$null = Test-Connection -ComputerName $server -Count 1 -ErrorAction STOP
		Write-Output "$server - OK"
	}
	catch {
		Write-Output "$server - $($_.Exception.Message)"
	}
}

Wanneer de foreach-lus wordt uitgevoerd, zal het er dan ongeveer zo uitzien:

Looping Test-Connection through a list of server names

Je hebt nu succesvol de verbinding getest van een tekstbestand vol met servernamen! Op dit punt kun je nu servernamen toevoegen of verwijderen in het tekstbestand naar believen zonder de code te wijzigen.

Je hebt succesvol in de praktijk gebracht wat we hebben gepreekt, de DRY-methode.

Voorbeelden van PowerShell VoorElkascalen

Laten we eens kijken naar een paar voorbeelden van hoe de ForEach-lus kan worden gebruikt. Deze voorbeelden zijn gebaseerd op praktijkgevallen die het concept tonen, dat je kunt aanpassen aan je eigen eisen.

Voorbeeld 1: Het maken van een bestand in elke submap in een map met de ForEach-verklaring

Dit voorbeeld toont het veelvoorkomende gebruik van de PowerShell-foreach voor mappen in een directory.

Stel dat er tien submappen zijn binnen de map C:\ARCHIVE_VOLUMES. Elke submap vertegenwoordigt een archiefvolume dat dagelijks wordt geback-upt. Na elke back-up wordt een bestand met de naam BackupState.txt gemaakt in elke map met de datum waarop het is geback-upt.

Je kunt hieronder een voorbeeld zien van hoe dit eruit zou kunnen zien.

Sub-directories under C:\ARCHIVE_VOLUMES

Het onderstaande script voert drie acties uit:

  • 1. Haalt de lijst op van alle submappen binnen C:\ARCHIVE_VOLUMES
  • 2. Doorloopt elke map
  • 3. Maakt een tekstbestand met de naam BackupState.txt met daarin de huidige datum en tijd als waarde

Het onderstaande voorbeeld gebruikt de foreach-verklaring.

# Definieer de TOP-niveau map
$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"

# Haal alle submappen recursief op
$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }

# Maak een tekstbestand in elke submap en voeg de huidige datum/tijd toe als waarde.
foreach ($foldername in $Child_Folders.FullName) {
   (get-date -Format G) | Out-File -FilePath "$($foldername)\BackupState.txt" -Force
}

Met behulp van de Get-ChildItem-cmdlet kun je bevestigen dat de bestanden zijn aangemaakt of bijgewerkt in elke submap.

Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include backupstate.txt | Select-Object Fullname,CreationTime,LastWriteTime,Length

De onderstaande schermafbeelding toont de uitvoer van het script waarin alle gevonden BackupState.txt bestanden in elke submap worden weergegeven.

A text file is created in each sub-directory

Voorbeeld 2: Het lezen van de inhoud van elk tekstbestand in submappen

Vervolgens, om het gebruik van PowerShell voor elk bestand in een map te demonstreren, zal het onderstaande script elk BackupState.txt bestand lezen dat is gemaakt in Voorbeeld 1.

  • Zoek recursief alle BackupState.txt bestanden binnen elke submap.
  • Gebruikend de foreach instructie, lees elk tekstbestand om de waarde van de “laatste back-uptijd” te krijgen.
  • Toon het resultaat op het scherm.
## Zoek alle BackupState.txt bestanden in C:\ARCHIVE_VOLUMES
$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName

## Lees de inhoud van elk bestand
foreach ($file in $files) {
    Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))
}

Zodra het script is uitgevoerd in uw PowerShell-sessie, zou u een vergelijkbare uitvoer moeten zien als de schermafbeelding hieronder. Dit toont aan dat PowerShell door bestanden loopt, de inhoud leest en de uitvoer weergeeft.

Using foreach to loop through files and read its contents.

Voorbeeld 3: Services ophalen en starten met behulp van de ForEach-Object CmdLet

Systeembeheerders moeten vaak de status van services ophalen en een handmatige of automatische workflow starten om eventuele mislukte services te herstellen. Laten we het voorbeeldscript bekijken met behulp van de ForEach-Object cmdlet.

Voor dit voorbeeld zal het onderstaande script het volgende doen:

  • Krijg een lijst van services die zijn geconfigureerd voor automatisch opstarten maar momenteel niet in een uitgevoerde staat zijn.
  • Volgende, worden de items in de lijst doorgegeven aan de ForEach-Object cmdlet om te proberen elke service te starten.
  • A message of either success or failed is displayed depending on the result of the Start-Service command.
## Krijg een lijst van automatische services die zijn gestopt.
$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}

## Geef elk serviceobject door aan de pijplijn en verwerk ze met de Foreach-Object cmdlet
$services | ForEach-Object {
    try {
        Write-Host "Attempting to start '$($.DisplayName)'"
        Start-Service -Name $.Name -ErrorAction STOP
        Write-Host "SUCCESS: '$($.DisplayName)' has been started"
    } catch {
        Write-output "FAILED: $($.exception.message)"
    }
}

Wanneer het script wordt uitgevoerd, zal het eruitzien als de uitvoer in de onderstaande schermafbeelding. Zoals je kunt zien, kreeg elke service een startopdracht. Sommige zijn succesvol gestart, terwijl anderen niet konden starten.

Using the ForEach-Object loop to start services

Voorbeeld 4: Gegevens lezen uit CSV met de ForEach()-methode

Het gebruik van gegevens uit CSV-bestanden is populair bij systeembeheerders. Het plaatsen van records in een CSV-bestand maakt het eenvoudig om bulkbewerkingen uit te voeren met de combinatie van Import-CSV en ForEach. Deze combinatie wordt vaak gebruikt voor het maken van meerdere gebruikers in Active Directory.

In dit volgende voorbeeld wordt aangenomen dat je een CSV-bestand hebt met twee kolommen – Voornaam en Achternaam. Dit CSV-bestand moet vervolgens worden gevuld met de namen van nieuwe gebruikers die moeten worden aangemaakt. Het CSV-bestand zou er ongeveer zo uitzien.

"Firstname","Lastname"
"Grimm","Reaper"
"Hell","Boy"
"Rick","Rude"

Nu voor het volgende scriptblok. Importeer eerst het CSV-bestand door het inhoudspad door te geven aan het Import-CSV cmdlet. Vervolgens, gebruikmakend van de foreach()-methode, doorloop de namen en maak de nieuwe gebruikers aan in Active Directory.

# Importeer lijst met voornaam en achternaam uit CSV-bestand
$newUsers = Import-Csv -Path .\Employees.csv

Add-Type -AssemblyName System.Web

# Verwerk de lijst
$newUsers.foreach(
    {
        # Genereer een willekeurig wachtwoord
        $password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3)
        $secPw = ConvertTo-SecureString -String $password -AsPlainText -Force

        # Formuleer een gebruikersnaam
        $userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName

        # Bouw nieuwe gebruikersattributen
        $NewUserParameters = @{
            GivenName       = $_.FirstName
            Surname         = $_.LastName
            Name            = $userName
            AccountPassword = $secPw
        }

        try {
            New-AdUser @NewUserParameters -ErrorAction Stop
            Write-Output "User '$($userName)' has been created."
        }
        catch {
            Write-Output $_.Exception.Message
        }
    }
)

Als het wordt uitgevoerd, zou je nu voor elke rij in het CSV-bestand een AD-gebruiker moeten hebben aangemaakt!

Samenvatting

De logica achter de foreach-lus in PowerShell verschilt niet van die van andere programmeertalen. Het verandert alleen met hoe het wordt gebruikt en welke variatie van de foreach-lus wordt gekozen voor een specifieke taak.

In dit artikel heb je de verschillende soorten foreach-lussen geleerd die beschikbaar zijn in PowerShell, en wat te overwegen bij het kiezen van de juiste. Je hebt ook de drie soorten foreach-lussen in actie gezien met behulp van verschillende voorbeeldscenario’s.

Verder lezen

Source:
https://adamtheautomator.com/powershell-foreach/