Hoe Windows PowerShell je leven makkelijker kan maken
05 juli 2023
Op het eerste gezicht lijkt het niet altijd het meest eenvoudig of gebruiksvriendelijk. Toch is de kans groot dat je, als je iets doet met IT, het wel eens hebt moeten gebruiken: Windows PowerShell. Deze tool die standaard op elke Windows computer aanwezig zijn, is een command line interface (CLI) of kortweg een ‘shell’. Dat wil zeggen, in plaats van een interactieve gebruikersinterface met knoppen, kun je commands intypen om acties uit te voeren op je computer.
Zelf had ik nog heel weinig ervaring met deze tool, totdat ik aan mijn opdracht begon als technisch applicatiebeheerder bij Waterleidingenmaatschappij Drenthe en Waterbedrijf Groningen. Hier werd Powershell veel gebruikt voor het monitoren en onderhouden van alle systemen.
Ik besloot daarom een cursus te volgen om de basics van Powershell te leren. Al gauw had ik de smaak te pakken, en vond ik het zo leuk om te scripten met PowerShell dat het zo’n beetje mijn favoriete bezigheid werd op de opdracht. Ik kwam erachter dat je ongeveer alles wat je op een computer kan doen ook met PowerShell gedaan kan worden, plus nog een stuk meer.
Het grote voordeel van PowerShell is dat je acties geheel automatisch kunt uitvoeren, en ook op meerdere computers tegelijk als je dat zou willen. Het kan je leven als IT’er een stuk gemakkelijker maken en dat is precies de reden waarom Microsoft de tool in 2006 is gaan ontwikkelen. In dit artikel neem ik je mee in hoe je PowerShell gebruikt en wat je er zoal mee kunt.
Zoals ik al zei is PowerShell standaard op elke Windows computer geïnstalleerd. Ook op Linux of iOS machines is het te downloaden en te gebruiken, maar daarvoor moet je wel .NET geïnstalleerd hebben op de computer. Dat is namelijk het platform dat wordt gebruikt om de code uit te voeren op je computer, net als bij programmeertalen van Microsoft als C# en Visual Basic.
Als je Windows PowerShell opstart op je computer verschijnt er een locatie, gevolgd door het knipperende balkje waar je commando’s (of kortweg ‘cmdlets’) kunt typen. In PowerShell hebben de cmdlets over het algemeen de volgende structuur: Werkwoord-Zelfstandig naamwoord. Het eerste cmdlet dat je kunt uitproberen is:
Get-ChildItem
Je typt dit in en drukt op Enter om het uit te voeren. Get-ChildItem verkrijgt de bestanden in de locatie. Als je naar een andere locatie wilt navigeren, kun je dat doen met Set-Location. Aan het cmdlet Set-Location moet je tevens een parameter meegeven, namelijk de locatie waar je naartoe wilt:
Set-Location -Path ‘Downloads’
Nu zit je in je downloads map, en wanneer je nog eens Get-ChildItem uitvoert krijg je als het goed is de bestanden in deze locatie. Wil je toch weer een mapje terug? Gebruik dan simpelweg:
Set-Location -Path ..
(1 punt betekent altijd je huidige locatie, twee punten de locatie erboven).
Een ander veelgebruikt cmdlet is Get-Process, deze geeft je informatie over alle processen die op dit moment bezig zijn op je computer. Probeer maar eens!
Zoeken in de lange lijst van processen doe je heel eenvoudig, met de parameter -Name.
Get-Process -Name ‘powershell’
Bij het zoeken kun je tevens de wildcard * gebruiken, wat staat voor: dit kan alles zijn.
Get-Process -Name ‘powersh*’
Dit is een korte illustratie van hoe PowerShell-cmdlets er in de basis uitzien. Er zijn er werkelijk duizenden, en verderop in dit artikel zal ik nog verschillende anderen introduceren. Als je ergens naar op zoek bent kun je er het beste gewoon op googlen 😉. Voor documentatie over hoe een PowerShell cmdlet werkt, kun je Get-Help gebruiken. Zie bijvoorbeeld:
Get-Help Get-Process –ShowWindow
Zoals je ziet kun je verschillende parameters meegeven aan Get-Process. Onderaan de pagina staat de uitleg van wat deze parameters zijn en wat je er eventueel aan mee kunt geven. Wanneer de parameter tussen vierkante haakjes staat [ ] betekent dat je deze niet uit hoeft te typen.
Get-Process ‘powersh*’
was dus voldoende geweest.
Daarnaast hebben veel cmdlets een alias, dat wil zeggen, een afkorting. In het geval van Get-Process is dat gps, in het geval van Set-Location cd, en Get-ChildItem gci of dir.
gps ‘powersh*’
Je hebt nu de basis van cmdlets te pakken. Zoals je in de laatste paar voorbeelden hebt gezien krijg je niet 1 gegeven, maar een tabelletje met informatie terug over hetgene dat je opvraagt. Wat je daadwerkelijk terugkrijgt is een object, of een lijst van objecten, en die hebben meerdere properties. Het tabelletje laat vaak niet eens alle properties zien.
Stel nu dat je 1 specifieke property zoekt, bijvoorbeeld de starttijd van het proces. Er zijn dan twee manieren om deze te verkrijgen.
De eerste is door eerst je object te selecteren, en dan van dat object de eigenschap te selecteren met Eigenschap. Dit kun je doen door het object eerst op te slaan in een variabele. Een variabele declareer je met $:
$powershell_process = gps ‘powershell’
Vervolgens selecteer je de eigenschap StartTime:
$powershell_process.StartTime
De tweede manier is om de pipe | te gebruiken. Met de pipe kun je een object doorgeven. Je kunt dan vervolgens nog een cmdlet uitvoeren op de input van de pipe. Zo kun je dus allerlei handige combinaties maken. In dit geval kunnen we het cmdlet Select-Object gebruiken om een property te selecteren van ons object (met de parameter -Property):
gps ‘powershell’ | Select-Object -Property StartTime
Als je wilt weten welke eigenschappen een object allemaal heeft, kan dat door het object door te geven aan Get-Member:
Get-Process | Get-Member
Met de pipe kun je dus ontzettend veel combinaties maken. Hier een paar handige voorbeelden:
1. Hoe gebruik je Windows PowerShell (en cmdlets)?
2. Naar een specifieke property zoeken
2.1. Gebruiken van variabele en/of de pipe
2.2. Toepassen van de pipe
4. Meer voorbeelden van wat je kunt doen met PowerShell
4.1. PowerShell-one-liners
Cmdlet | Omschrijving | Voorbeeld(en) |
Select-Object (alias: select) |
Selecteer (bepaalde properties van) een object | Get-Process ‘powershell’ | Select-Object StartTime
Get-Process | Select-Object -First 3
Maak een property door een expressie te bouwen. Dit doe je met curly brackets { }, waarin $_ het doorgegeven object is:
Geef je property een naam: Get-Volume -DriveLetter C | select @{name=’Space Used (%)’; expression={100 – ($_.SizeRemaining / $_.Size * 100)}}
Met de parameter -ExpandProperty pak je puur de geselecteerde property in plaats van het object met de property: Write-Output ”Computer laatst herstart op $last_reboot” |
Sort-Object (alias: sort) |
Sorteer een lijst met objecten | Get-ChildItem | Sort-Object LastWriteTime -Descending |
Where-Object (alias: where / ?) |
Filter een lijst met objecten* | Get-ChildItem | Where-Object Name -like ‘*.txt’
Get-EventLog -LogName Application | ? {$_.EntryType -eq ‘Error’ -and $_.TimeGenerated -gt (Get-Date).AddDays(-15)} |
ForEach-Object (alias: foreach / %) |
Voor een actie uit voor elk van de objecten | Get-ChildItem ‘*.txt’ | foreach {Write-Output ”Text file name: $($_.Name)”
$array = @(‘John’, ‘Jack’, ‘James’) $array | % {Add-Content ‘names.txt’ $_ } |
Format-List (alias: fl) |
Format het resultaat als lijst (tegenhanger van Format-Table (standaardinstelling)) | gps powershell | select * | fl |
* voor een overzicht van de comparison operators in PowerShell, zie https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators
PowerShell onderscheidt zich van andere command-line tools in dat je er naast losse commando’s ook gehele scripts mee kan schrijven. Hiervoor kun je Windows PowerShell ISE gebruiken (deze staat ook standaard op je computer). ISE staat voor Integrated Scripting Environment. In scripts kun je logica gebruiken zoals je dat in andere programmeertalen ook kan: if-else statements, loops, try-catch statements, enzovoorts. Net als bij cmdlets kun je je script parameters laten gebruiken. Met een script kun je dus wat meer logica uitvoeren dan met een los cmdlet. Daarnaast kun je je script opslaan (een PowerShell script is een .ps1-bestand), en deze automatisch / herhaaldelijk uit laten voeren.
Zoals ik al noemde kun je ongeveer alles wat je gewoonlijk op je computer doet ook met Windows PowerShell. Als afsluiting van dit artikel geef ik je een paar voorbeelden, die misschien van pas kunnen komen in je werk. Gebruik hiervoor PowerShell ISE.
1. Een API call doen
$headers = @{
‘Key’ = ‘jouw key (https://www.weatherapi.com/signup.aspx)’
‘Content-Type’ = ‘json’
}
$response = Invoke-WebRequest “https://api.weatherapi.com/v1/current.json?q=Utrecht&aqi=no” -Headers $headers
$weather = $response | ConvertFrom-Json
$weather.location.name
“$($weather.current.temp_c) C°”
$weather.current.condition.text
Output (voorbeeld):
Utrecht
11.0 C°
Clear
2. Een SQL-query uitvoeren
$connectionString = “Server=server; database=database; Trusted_Connection=True; User ID=username;”
$query = @”
Jouw query
“@
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $connectionString
$sqlConnection.Open()
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.Connection = $SqlConnection
$sqlCmd.CommandText = $query
$table = New-Object System.Data.DataTable
$reader = $sqlCmd.ExecuteReader()
$table.Load($reader)
Write-Output $table
$sqlConnection.Close()
}
catch
{
Write-Output “Kan niet verbinden met de database:`n$_”
}
Output is het resultaat van je SQL-query
3. Check of er Windows updates klaarstaan
$objSession = [activator]::CreateInstance([type]::GetTypeFromProgID(“Microsoft.Update.Session”))
$searchResult = $objSession.CreateUpdateSearcher().Search(“IsInstalled=0″)
$updatesAmt = $searchResult.Updates.Count
$maxSeverity = ”
$totalSize = 0
$updates = for ($i = 0; $i -lt $searchResult.Updates.Count; $i++) {
$totalSize += $searchResult.Updates.Item($i).MaxDownloadSize
$severity = “$($searchResult.Updates.Item($i).MsrcSeverity)”
if ($maxSeverity -eq ”)
{
$maxSeverity = $severity
}
elseif ($maxSeverity -eq ‘Low’ -and ‘Moderate’,’High’,’Critical’ -contains $severity)
{
$maxSeverity = $severity
}
elseif ($maxSeverity -eq ‘Moderate’ -and ‘High’,’Critical’ -contains $severity)
{
$maxSeverity = $severity
}
elseif ($maxSeverity -eq ‘High’ -and $severity -eq ‘Critical’)
{
$maxSeverity = $severity
}
}
$size = ”
if ($totalSize -gt 0) {
$size = [String]([System.Math]::Round($totalSize/1MB, 2)) + ” MB”
}
New-Object PSObject –Property @{
LastUpdated = $lastUpdated
AvailableUpdates = $updatesAmt
Severity = $maxSeverity
Size = $size
}
Output (voorbeeld):
LastUpdated AvailableUpdates Severity Size
———– —————- ——– —-
4/30/2023 2:04:36 PM 3 747.34 MB
4. Welke processen gebruiken het meeste geheugen?
$RAM = Get-CimInstance Win32_PhysicalMemory | Measure -Property Capacity -Sum | %{$_.sum/1Mb}
Get-CimInstance Win32_PerfFormattedData_PerfProc_Process | Select Name, @{name=’Memory(%)’; expression={[math]::Round(($_.WorkingSetPrivate / 1Mb) / $RAM * 100, 2)}} | Sort ‘Memory(%)’ -Descending | select -first 12
Output (voorbeeld):
Name Memory(%)
—- ———
_Total 20.46
Memory Compression 4.34
MsMpEng 1.24
SearchApp 1.2
chrome 0.92
chrome#2 0.89
powershell_ise 0.78
WINWORD 0.59
Code#4 0.53
chrome#18 0.51
Code#2 0.43
chrome#6 0.42
Dan is er nog één ander verschil om rekening mee te houden: de PowerShell-one-liner, dat is één doorlopende pijplijn. Maar niet persé een opdracht die zich op 1 fysieke regel bevindt. Want niet alle opdrachten die zich op één fysieke regel bevinden, zijn een one-liner.
Bestaat een uit één onafgebroken pipeline worden . Dat worden one-liners genoemd. Hieronder vind je voorbeelden van Powershell one-liners.
1. Sorteren van informatie uit een csv bestand
We gaan uit van het volgende voorbeeld; stel, we hebben een csv bestand ‘management.csv’:
$martijn = New-Object PSObject –Property @{
Naam = ‘Martijn Ockers’
Functie = ‘Algemeen Directeur’
Startdatum = ’01-08-2013′
}
$stijn = New-Object PSObject –Property @{
Naam = ‘Stijn Smolders’
Functie = ‘Commercieel Directeur’
Startdatum = ’01-04-2014′
}
$feroz = New-Object PSObject –Property @{
Naam = ‘Feroz Fernandes’
Functie = ‘Manager IT Services’
Startdatum = ’01-11-2016′
}
$management = @($martijn, $stijn, $feroz)
$management | Export-Csv -Path ‘management.csv’ -NoTypeInformation
Gebruik deze cmdlets:
import-csv management.csv | select Naam, @{name=’Startdatum_dt’; expression={[Datetime]::ParseExact($_.Startdatum, ‘dd-MM-yyyy’, $null)}} | Sort-Object Startdatum_dt
En dan krijg je deze output:
Naam Startdatum_dt
—- —————-
Feroz Fernandes 01/11/2016 12:00:00 AM
Stijn Smolders 01/04/2014 12:00:00 AM
Martijn Ockers 01/08/2013 12:00:00 AM
2. .csv converteren naar .json
(Je kunt ongeveer alle datatypen eenvoudig naar elkaar converteren met PowerShell)
Import-Csv ‘management.csv’ | ConvertTo-Json | Out-File ‘management.json’
Als output krijg je naast het bestand management.csv, welke we aan het begin hadden opgemaakt, een nieuwe bestand opgeslagen genaamd management.json.
3. Maak een html-tabel met alle services op je computer
Get-Service | Select Name, Displayname, Status, StartType | ConvertTo-Html -Head ‘<
style>
table {border-collapse: collapse; } td, th { border: 1px solid black; text-align: left; padding: 5px; } <
style>
‘ | Out-file ‘services.html’
Output: het bestand services.html is opgeslagen in je huidige locatie, en kun je openen met elke moderne browser.
4. Hoeveel bestanden heb ik in mijn Downloads-map sinds dit jaar?
(Get-ChildItem -Path .Downloads | ? LastWriteTime -gt (Get-Date -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0)).Count
Output (voorbeeld):
14
5. Vergelijk 2 bestanden of objecten op verschillen
Compare-Object -ReferenceObject (Get-Content -Path Testfile1.txt) -DifferenceObject (Get-Content -Path Testfile2.txt)
Output (voorbeeld):
Input Object SideIndicator
———– ————-
cat =>
racoon =>
dog <=
squirrel <=
6. Scan je computer op de Log4j vulnerability
Get-ChildItem -Path C: -Recurse | ? Name -match ‘log4j’
Output (voorbeeld):
Directory: C:UsersBramPrins.m2repositorylog4jlog4j1.2.12
Mode LastWriteTime Length Name
—- ————- —— —-
-a—- 9/2/2021 5:03 PM 358085 log4j-1.2.12.jar
-a—- 9/2/2021 5:03 PM 40 log4j-1.2.12.jar.sha1
-a—- 9/2/2021 2:55 PM 145 log4j-1.2.12.pom
-a—- 9/2/2021 2:55 PM 136 log4j-1.2.12.pom.sha1
7. Scan meerdere computers in je domein op de Log4j vulnerability
Invoke-Command -ComputerName computers -ScriptBlock { Get-ChildItem -Path C: -Recurse | ? Name -match ‘log4j’ }