Windows Powershell – The Basics 6
Als je Powershell scripts maakt dan is het essentieel om te weten hoe Powershell loops werken. Een loop is de herhaling van een stuk code tot een bepaalde conditie behaald is. In deze post gaan we kennis maken met de meest voorkomende loops namelijk de While, For, ForEach, Do-While en Do-Until loop. Vervolgens kijken we naar wat andere loop functies en eindigen we met een kleine intro m.b.t. Powershell Scripting. Laten we gaan loopen!
Zoals al gezegd is een loop een herhaling van een stuk code tot een bepaalde conditie behaald is. Een loop noemen we in het Nederlands ook weleens een “iteratie” of een “lus”. We kennen verschillende soorten loops en daarom leggen we eerst kort de funtie uit van de verschillende loops:
While Loop
Een “While Loop” is een stuk code welke herhaald wordt tot een bepaalde conditie “false” is. Zolang de conditie “true” is zal het stuk code onophoudelijk herhaald worden. We noemen dit ook wel een “pre-test loop” omdat de conditie gecontroleerd wordt alvorens de loop wordt uitgevoerd. Het kan dus zijn dat een While Loop de code in de loop niet uitvoert omdat de conditie al gehaald is alvorens de loop wordt uitgevoerd welke in dat geval overgeslagen wordt.
Do-While Loop
Een “Do-While” loop heeft heel veel overeenkomsten met de hiervoor genoemde “While Loop”. Ook hierbij wordt een blok code herhaald tot de gegeven conditie “false” is. De conditie wordt echter gecontroleerd nadat de loop is afgelopen. Het blok met code wordt dus altijd minimaal 1x uitgevoerd omdat pas daarna de conditie gecontroleerd wordt. De Do-While Loop noemen we dus ook weleens een “post-test loop”.
Do-Until Loop
De “Do-Until Loop” is super vergelijkbaar met de “Do-While” Loop. Er is echter 1 groot verschil. De Do-While Loop herhaalt zichzelf zolang de conditie “true” is en de Do-Until Loop herhaalt zichzelf tot de conditie “false” is. Met andere woorden. De Do-While Loop zal loopen zolang de conditie “false” is en de Do-Until Loop zal loopen zolang de conditie “true” is.
For Loop
De For Loop zorgt er ook voor dat een blok code meerdere malen herhaald kan worden maar in tegenstelling tot een While Loop is oneindige repetitie vaak niet mogelijk (uitzonderingen daargelaten zoals bij Java). Een “For Loop” werkt met een counter (teller) en de conditie bij een “For Loop” is vaak in de trend van “bevind de teller zich tussen X1 en X2, voer dan de loop uit”. De beginwaarde (X1) is meestal 0 en de eindwaarde (X2) kan gedefinieerd worden. De conditie van een “For Loop” bestaat dus uit 3 dingen:
- De beginwaarde van de teller
- De controle (ofwel, bevindt de teller zich tussen de toegestane waardes om de loop nogmaals uit te voeren)
- De actie + loop. Dus tel X op bij de teller en voer de loop uit
ForEach Loop
De “ForEach Loop” is essentieel anders dan de hiervoor genoemde “For” loop. Met een ForEach Loop doorzoek je een verzameling. Elke waarde in de verzameling die aan de conditie van de loop voldoet zal “door de loop” gaan. De code in de loop zal dus toegepast worden op elke waarde in de verzameling. ForEach Loops werken meestal niet met een counter welke de code makkelijker maakt om te lezen en zorgt er ook voor dat “off-by-one” errors niet voor kunnen komen in tegenstelling tot de gewone “For Loops”.
Nu we de verschillen tussen bovenstaande loops beschreven hebben gaan we de loops toepassen in een Powershell omgeving. We beginnen met de “While Loop”:
Powershell While Loop
Een simpel voorbeeld van de Powershell While Loop is:
$i = 1 While($i -lt 10) {Write-Host $i; $i++} |
Wat hierboven gebeurt is als volgt.
$i = 1 |
We definiëren hier de variabele “i” en deze zetten we op 1.
While($i -lt 10) |
Hier zeggen we “zolang variabele “I” lager is dan 10″
{Write-Host $i; $i++} |
Tel 1 op bij de variabele.
Daarna controleren we weer of de variabele lager is dan 10. Zo ja dan wordt er weer 1 bij opgeteld en herhaald het proces zich weer. Zo nee (false) dan zal de loop overgeslagen worden en wordt er verder gegaan met de code.
Een wat grotere While-Loop ziet er als volgt uit:
while(($inp = Read-Host -Prompt "Selecteer een commando") -ne "Q"){ switch($inp){ D {"Bestand wordt verwijderd"} V {"Bestand wordt getoond"} P {"Bestand wordt beschermd tegen overschrijven"} Q {"Einde"} default {"Foutieve keuze"} } } |
Wat we hier doen is het maken van een klein (niet functioneel) menu. De While Loop wordt gestart en we definieren een $inp (input) variabele. Deze variabele mag niet gelijk aan (-ne) Q zijn. Dit is dus meteen de conditie in de While Loop. Q zou de conditie “true” maken en de loop afbreken (zoals je ook kunt zien als je deze loop zou testen). Vervolgens schrijven we op het scherm “Selecteer een commando”. De gebruiker kan nu diverse commando’s intypen. Iets anders dan een D,V,P of Q zal resulteren in de tekst “Foutieve keuze”. Als D,V of P wordt gebruikt zal de gedefinieerde tekst achter de letters getoond worden en zoals al gezegd zal Q de loop beëindigen. Bij elke andere keuze start de loop weer vanaf het begin.
Powershell Do-While Loop
Zoals je weet lijkt de “Do-While” loop heel veel op de “While Loop” met als enige uitzondering dat de Do-While loop de conditie aan het einde van de code controleert. Een voorbeeld:
$i = 1 do {Write-Host $i; $i++} while ($i -lt 10) |
Wat hier gebeurt is als volgt:
$i = 1 |
We definiëren variabele “i” en zetten deze op 1.
do {Write-Host $i; $i++} |
We hogen de variabele op met 1.
while ($i -lt 10) |
We controleren de variabele. Als deze nog lager is dan 10 wordt de loop nogmaals uitgevoerd.
Merk je het subtiele verschil met de gewone “While Loop”? Hier voeren we de actie uit op de variabele en controleren we daarna of we de loop nog uit moeten voeren en bij een gewone While Loop controleren we of we de loop nog uit moeten voeren en pas als dit zo is dan wordt de actie uitgevoerd.
Even een groter voorbeeld:
Clear-Host $strPassword ="Jarno" $strQuit = "Probeer opnieuw" Do { $Guess = Read-Host "`n Raad het wachtwoord" if($Guess -eq $StrPassword) {" Goede gok"; $strQuit ="n"} else{ $strQuit = Read-Host " Fout! `n Wil je nog een keer raden? (Y/N)" } } # End of 'Do' While ($strQuit -ne "N") |
Wat gebeurt hier? Allereerst maken we het scherm schoon “Clear-Host” en defileren we een 2-tal variabelen die we ik de loops gebruiken:
Clear-Host $strPassword ="Jarno" $strQuit = "Probeer opnieuw" |
Dan openen we de Do Loop. We starten met de vraag om het wachtwoord te raden. De input slaan we op in de $Guess variabele. Als deze overeenkomt met het opgegeven wachtwoord (Jarno) dan zie je de tekst “Goede gok” en wordt de “strQuit” variabele op “n” gezet. Als het wachtwoord niet goed geraden is dan wordt de “else” loop gestart met de vraag of je nogmaals wilt raden. De strQuit variabele wordt dan door de gebruiker geverifieerd.
Do { $Guess = Read-Host "`n Raad het wachtwoord" if($Guess -eq $StrPassword) {" Goede gok"; $strQuit ="n"} else{ $strQuit = Read-Host " Fout! `n Wil je nog een keer raden? (Y/N)" } } # End of 'Do' |
De laatste regel valideert de strQuit variabele. Dit gebeurt dus op het einde van de loop. Zolang deze niet “N” is zal de loop nogmaals starten.
While ($strQuit -ne "N") |
Powershell Do-Until Loop
Zoals de “Do-While” loop op de “While Loop” lijkt zo lijkt de “Do-Until Loop” op de “Do-While Loop”. Een Do-Until Loop zou er als volgt uit kunnen zien:
$i = 1 do {Write-Host $i; $i++} until ($i -gt 10) |
Als we de loop ontleden gebeurt hier het volgende:
$i = 1 |
We definiëren variabele “i” en zetten deze op 1.
do {Write-Host $i; $i++} |
We hogen de variabele op met 1.
until ($i -gt 10) |
Voer deze loop uit tot variabele “i” groter is dan 10 (dus t/m 10 en dus herhalen zolang de conditie false is)
Zoals jullie weten is de Do-Until Loop hetzelfde als de Do-While Loop met als uitzondering dat deze loop doorgaat tot de waarde “true” is. Laten we dat eens bekijken met hetzelfde voorbeeld als bij de De-While loop:
Clear-Host $strPassword ="Jarno" $strQuit = "Probeer opnieuw" Do { $Guess = Read-Host "`n Raad het wachtwoord" if($Guess -eq $StrPassword) {" Goede gok"; $strQuit ="n"} else{ $strQuit = Read-Host " Fout! `n Wil je nog een keer raden? (Y/N)" } } # End of 'Do' Until ($strQuit -eq "N") |
Het enige wat anders is aan deze hele loop is de laatste regel, namelijk:
Until ($strQuit -eq "N") |
I.p.v. “While” staat hier nu “Until”. Dus zolang de “strQuit” variabele niet gelijk is aan “N” zal de loop doorgaan en opnieuw starten. Pas als de waarde “true” is wordt de loop afgebroken. Als de “strQuit” variabele dus gelijk is aan (-eq) “N” dan zal de loop stoppen.
Powershell For Loop
Zoals al gezegd werkt de “For Loop” met een couter (teller). Een simpel voorbeeld van een “For Loop”:
for ($i=1; $i -le 10; $i++) { Write-Host "Item" $i; } |
De teller wordt hier gedefinieerd als “$i”. Er 3 zaken waar een conditie in een “For” loop naar kijkt. Laten we deze eens invullen voor deze simpele loop:
- De beginwaarde van de teller = 1
- De controle (ofwel, bevindt de teller zich tussen de toegestane waardes om de loop nogmaals uit te voeren) = “-le 10” (dus is minder dan 10 of gelijk aan 10)
- De actie + loop. Dus tel X op bij de teller en voer de loop uit = “$i++” dus tel 1 op bij de counter
Nu we deze zaken weten kunnen we bekijken wat deze loop doet. De loop begint bij 1. Vervolgens schrijft deze naar het scherm de tekst “Item” + de huidige variabele. Dus “Item 1”. Daarna draait de loop opnieuw waarna gekeken wordt naar de 2e conditie. Als dit counter minder of gelijk aan 10 is dan zal deze de teller ophogen met 1 (waardoor deze 2 is). Als deze hoger is dan 10 dan zal de loop stoppen en doorgaan met de vervolgcode. De output naar het scherm is dan:
Item 1 Item 2 Item 3 Item 4 Item 5 Item 6 Item 7 Item 8 Item 9 Item 10 |
For loops worden vaak gebruikt om door een array (gestructureerde lijst met elementen) te gaan om zodoende op ieder object in de array een actie toe te passen. We kunnen een array als volgt bekijken:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") For ($i=0; $i -lt $fruit.Length; $i++) { $fruit[$i] } |
We definiëren eerst een array genaamd “fruit”. Om dit te doen specifieren we de variabele fruit en openen we de waardes van de variabele met een “@” om aan te geven dat het een array is en dus een gestructureerde lijst waarbij ieder item op een eigen “regel” staat.
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") |
Daarna openen we de For Loop:
For ($i=0; $i -lt $fruit.Length; $i++) { $colors[$i] } |
In de For Loop zetten we de teller ($i) op 0 (wat overeenkomt met het eerste item in de array omdat arrays bij 0 beginnen en niet met 1). Vervolgens zeggen we zolang de teller lager is dan het aantal posities in de array dan hogen we deze op met 1 “$i -lt $fruit.Length; $i++)”. Daarna tonen we de waarde uit de array die overeenkomt met de counter “$colors[$i]”. Op deze manier loopen we dus door een array heen om de inhoud van de array te bekijken.
Of tonen we een alternatieve output waarbij we de waardes in de array gebruiken:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") For ($i=0; $i -lt $fruit.Length; $i++) { $fruit[$i] + " is lekker fruit" } |
Powershell ForEach Loop
Als we bovenstaande voorbeeld van de For Loop in een ForEach loop zetten zien we hoeveel simpeler en effectiever een ForEach Loop is:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") ForEach ($element in $fruit) { $element + " is lekker fruit" } |
ForEach staat dus voor “ieder element”. Allereerst wordt de array gedefinieerd en vervolgens wordt de ForEach Loop gestart waar de variabele $element gevuld wordt met de overeenkomstige positie in de array $fruit. Vervolgens wordt de variabele getoond incl. de toevoeging “is lekker fruit”.
Deze code is veel simpeler om te lezen omdat we niet met counters werken en simpelweg door de array loopen.
Een ander voorbeeld met een ForEach loop is de mogelijkheid om output te “pipen” naar de ForEach Loop. Bij grote datasets zorgt deze methode voor meer overhead maar de ForEach Loop wordt hiermee nog iets makkelijker. Bijvoorbeeld:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit | ForEach { $_ } |
Hier vragen we de $fruit array op welke we pipen naar de ForEach Loop. Elk item in de loop wordt nu getoond met een speciale variabele “$_”.
Loop Break en Continue
De mogelijkheid bestaat om een loop vroegtijdig te beëindigen wanneer bepaalde condities behaald zijn. Het voortzetten van het looping proces zou een verspilling van processorkracht zijn. Om loops vroegtijdig te beëindigen kunnen we het “Break” commando gebruiken. Als voorbeeld gebruiken we de volgende (eerder gebruikte) For Loop:
for ($i=1; $i -le 10; $i++) { Write-Host "Item" $i; } |
Bovenstaande For Loop zal 10 maal draaien. Maar stel voor dat we deze al willen laten stoppen bij 5 keer. Om dit te doen plaatsen we een “break” statement met een nieuwe conditie welke we beperken tot slechts 5:
for ($i=1; $i -le 10; $i++) { if ($i -gt 5) { break; } Write-Host "Item" $i; } |
Het Break commando kan gebruikt worden in For en ForEach Loops. Zo kunnen we b.v. door een array loopen maar stoppen wanneer een bepaalde waarde gevonden wordt:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") ForEach ($element in $fruit) { if ($element -eq "Banaan") { break; } $element + " is lekker fruit" } |
De tegenhanger van “Break” is het “Continue” commando. Het Continue commando zorgt ervoor dat de volgende code tussen brackets niet wordt uitgevoerd maar dat de loop wel doorgaat. Het volgende voorbeeld is dus bijna hetzelfde als voorgaande voorbeeld. Alleen de Break is veranderd in een Continue:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") ForEach ($element in $fruit) { if ($element -eq "Banaan") { continue; } $element + " is lekker fruit" } |
Wat hier gebeurt is als volgt: wanneer in de loop de waarde “banaan” gevonden wordt dan wordt deze (incl. de toevoeging “is lekker fruit”) niet naar de console ge-output. De loop gaat echter gewoon door en dus verschijnt elk fruit op het scherm behalve de banaan.
Loop Arrays
Het principe van de “Array” hebben we in bovenstaande voorbeelden al duidelijk gedemonstreerd. Maar wat is nu exact een array? Een array is een gestructureerde lijst met elementen. Elk element in een array heeft een index. De eerste waarde begint bij 0 en de volgende waarde is 1, etc. Met en array kan ieder element afzonderlijk benaderd worden.
In Powershell lijkt het definiëren van een Array op een variabele. Bij sterkere talen moet een variabele omgezet worden naar een array maar dat is bij Powershell niet nodig. Er zijn echter wel verschillende soorten arrays. Dit wil zeggen dat het type waarden welke opgenomen kunnen worden in de arrays verschillend kunnen zijn.
Zonder verdere declaratie gaat Powershell ervan uit dat een array bestaat uit gevarieerde waardes zoals strings, nummers, datums etc. het default datatype is dan ook “Variant”.
Een array definieer je net zoals een variabele maar dan met de @ toevoeging. Dus zoals we al eerder zagen:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") |
Hoewel ik bovenstaande de meest duidelijke notatie vindt kun je de array ook als volgt definieren (zonder @ symbool):
$fruit = "Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas" |
Mocht je een array willen gebruiken van een specifiek datatype dan kun je dit aangeven voordat je de variabele van de array bepaald. Bijvoorbeeld een array met alleen integers:
[int[]] $fruit = 1,2,3,4,5,6,7,8,9 |
Om later items aan een array toe te voegen gebruiken we simpelweg het plus symbool (+):
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit = $fruit + "Mandarijn" ForEach ($element in $fruit) { $element + " is lekker fruit" } |
We kunnen bovenstaande nog korter noteren met een combined assignment operator:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit += "Mandarijn" |
Op dezelfde manier kun je ook een andere array samenvoegen. Bijvoorbeeld:
$fruit += $groente |
Arrays kunnen niet gemakkelijk verwijderd worden. Je kunt een array verwijderen door een null variabele te definiëren:
$fruit = $null |
Specifieke waardes uit een array verwijderen is net zo lastig. Dit kan gedaan worden met een workaround waarbij de waardes gefilterd worden en opnieuw worden toegewezen aan de array:. In het volgende voorbeeld verwijderen we alle items uit de array en laten we alleen de waarde “Banaan” staan:
$fruit = $fruit | where {$_ -eq "Banaan"} |
In voorgaande voorbeeld hebben we de For en ForEach loops gebruikt om de elementen van een array te bekijken. Dit is echter overbodig. Alleen het aanroepen van de array zorgt ervoor dat de gehele array getoond wordt:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit |
Het weergeven van een specifiek item uit de array kan door het specificeren van het indexnummer. Om de “Appel” uit de array te tonen specificeren we indexnummer 0 (de eerste positie binnen een array):
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit[0] |
De Appel, Peer en Mango kunnen we specificeren in een komma gescheiden lijst:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit[0,1,5] |
Om gewoon waarde 1 t/m 5 weer te geven gebruik je het “van-tot” symbool. Dit zijn 2 puntjes:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit[0 .. 5] |
Een specifiek item zoeken in een array kan ook. Gebruik b.v. de “-Like” flag. Wildcards zijn toegestaan:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit[0 .. 5] -like "Sin*" |
Waardes sorteren van A-Z doen we als volgt:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit = $fruit | Sort $fruit |
Let op, bovenstaande “Sort” verplaatst de items in je array van A-Z. Deze sort functie beperkt zich dus niet tot de output op het scherm. Om alleen de output op het scherm te manipuleren waarbij de items in de arrays op dezelfde locatie blijven staan kun je het “Sort-Object” gebruiken. Hieronder sorteren we de lijst random.
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit = $fruit | Sort-Object {Get-Random} $fruit |
Zonder toevoeging wordt de lijst ook van A-Z gesorteerd. Om de sortering om te keren (Z-A) gebruik je de “-Descending” flag:
$fruit = @("Appel","Peer","Banaan","Kiwi","Sinaasappel","Mango","Ananas") $fruit = $fruit | Sort-Object -Descending $fruit |
Scripts
Nu we geproefd hebben van Loops en Arrays kan ik me voorstellen dat je heel veel toepssingen kunt verzinnen om te automatiseren. Systeembeheer taken, workflow taken etc. Het is nog makkelijke om de code eenmalig te schrijven en uit te laten voeren met een simpele dubbelklik. Uiteraard kan dat ook. Dit doen we met scripts. Een script is een serie met commando’s zoals je deze ook 1-voor-1 zou invoeren op de Command-Line. Al deze commando’s (of loops, imports etc.) plaats je onder elkaar waarna je het bestand opslaat als een *.ps1 bestand.
Download hier een voorbeeld scriptje waarin we informatie vanuit de computer opslaan in een bestandje waardoor we meteen een goed overzicht hebben van de huidige computerconfiguratie. Dit script is een klein onderdeel uit mijn BlackBackup PowerShell script en BashBunny payload.
Echter is het uitvoeren van scripts standaard verboden en moet dit eerst enabled worden. Dit doe je door Powershell uit te voeren als Administrator. Vervolgens voer je het commando:
Set-ExecutionPolicy RemoteSigned |
Vervolgens klik je op Y om scripts toe te staan of op A om alle scripts toe te staan. Deze setting is van toepassing op je computer en zal dus van toepassing zijn op alle Powershell scripts en niet alleen de scripts in je huidige sessie. Om zonder additionele vragen door te gaan gebruik je de “Bypass -Force” optie:
Set-ExecutionPolicy Bypass -Force |
Het is slim om bovenaan elk script ook de volgende toevoeging te plaatsen. Als Powershell als administrator wordt uitgevoerd zal het script gewoon draaien zonder deze setting op gebruikersniveau toe te passen. Domein administrators kunnen overigens de Set-ExecutionPolicy ook pushen d.m.v. Group Policy.
Als je langere scripts gaat schrijven is het aan te raden om een goede editor te gebruiken welke syntax interpertatie ondersteund. Dus waarbij delen van de code “gekleurd” worden om hun functie aan te duiden. Voor Powershell kun je heel goed Notepad++ gebruiken maar er zijn veel meer (nog betere) editors zoals Sublime Tekst en ISE Steroids.
De laatste tip is om bij het schrijven en testen van code in Powershell een “transcript” file te starten. In het transcript bestand worden alle commando’s die je test in Powershell opgeslagen incl. de output van deze commando’s. Op deze manier kun je gemakkelijk terugkijken welke commando’s je gebruikt hebt. Om een transcript file aan te maken gebruik je het volgende commando:
Start-Transcript |
Om het transcript te stoppen gebruik je:
Stop-Transcript |
Tot zover deze PowerShell post over scripting en loops. Ik hoop dat jullie dit interessant vonden. De volgende Powershell post is de laatste post van deze serie. De volgende keer gaan we kijken naar modules. Zodat we de kracht van PowerShell nog efficiënter kunnen inzetten!