1 The Pipeline
The pipeline operator | sends the output of one command into the next:
Get-Service | Where-Object Status -eq "Running"
Meaning: get all services → send them to Where-Object → only return the ones whose status is Running. A more elaborate chain:
Get-Process |
Where-Object CPU -gt 100 |
Sort-Object CPU -Descending
This finds processes that have consumed significant CPU time and sorts them by usage. Select-Object picks specific properties (Get-Service | Select-Object Name, Status); Where-Object filters (Get-Service | Where-Object {$_.Status -eq "Stopped"}). The special variable $_ means "the current object moving through the pipeline" — you'll see it constantly in real scripts, since it's how each command in a pipeline refers to whatever the previous command just handed it.
2 Variables
PowerShell variables begin with $: $name = "Alice", then Write-Host $name returns Alice.
$Username = "jsmith"$Retries = 3$Enabled = $true or $Enabled = $false$Servers = @("SERVER01", "SERVER02", "SERVER03") — access one element with $Servers[0], which returns SERVER01.$User = @{ Username = "jsmith"; Department = "Finance"; Location = "London" }. Retrieve a value with $User["Department"] or $User.Department.PowerShell can also read operating-system environment variables — $env:USERNAME, $env:COMPUTERNAME, $env:PATH — extremely useful for scripts that need to adapt to whichever user or computer they're actually running on. Useful built-in variables include $PSVersionTable (and specifically $PSVersionTable.PSVersion to check which PowerShell you're running — relevant given Section 1 of the hub's Windows PowerShell vs PowerShell 7 distinction), plus $HOME, $PWD, $LASTEXITCODE, $Error, and $?.
3 String Handling
Combining strings: $FirstName = "Alice"; $LastName = "Smith"; $FullName = "$FirstName $LastName" produces Alice Smith.
🔮 Predict first
$name = "Alice". What does Write-Host "Hello $name" print, versus Write-Host 'Hello $name'?
Reveal the answer
Double quotes print Hello Alice — they expand variables. Single quotes print Hello $name literally — they normally don't. This distinction prevents a genuinely common class of scripting mistake, especially in longer strings where a variable silently fails to expand because someone used single quotes without realising it.
4 Conditional Logic
Automation constantly needs to make decisions, using if/elseif/else:
$Service = Get-Service -Name Spooler
if ($Service.Status -eq "Running") {
Write-Host "Service is running"
}
else {
Write-Host "Service is stopped"
}
An enterprise example — monitoring an authentication service:
$Service = Get-Service -Name "AuthenticationService"
if ($Service.Status -ne "Running") {
Write-Host "WARNING: Authentication service is not running"
}
A mature version of this might then write an event, create an alert, restart the service, send telemetry, or open a ticket. Comparison operators: -eq equal, -ne not equal, -gt/-lt greater/less than, -ge/-le greater/less-or-equal, -like wildcard matching, -match regular expression matching. Logical operators: -and, -or, -not — e.g. if (($Department -eq "Finance") -and ($Enabled -eq $true)) { Write-Host "Active Finance user" }.
5 Loops
Loops repeat an operation — this is where automation starts getting genuinely powerful. Told to disable 500 contractor accounts, doing it manually could take hours; a loop can do it in seconds.
foreach is the most common loop:
$Servers = @("SERVER01", "SERVER02", "SERVER03")
foreach ($Server in $Servers) {
Write-Host "Checking $Server"
}
A practical version tests connectivity to real servers:
$Servers = @("DC01", "WEB01", "SQL01")
foreach ($Server in $Servers) {
Test-Connection $Server -Count 1
}
ForEach-Object is the pipeline equivalent: Get-Service | ForEach-Object { Write-Host $_.Name } — again using $_ for the current object. A traditional for loop also exists: for ($i = 1; $i -le 10; $i++) { Write-Host "Iteration $i" }, as does while: $Counter = 1; while ($Counter -le 5) { Write-Host $Counter; $Counter++ }. Be careful — a badly written loop can become infinite, and an infinite loop in a script that's also making changes is a genuinely dangerous combination.
6 Functions
Functions package reusable code. Without them, scripts quickly become repetitive:
function Test-Server {
param(
$ComputerName
)
Test-Connection $ComputerName -Count 1
}
Test-Server -ComputerName "SERVER01"
Enterprise scripts should use typed, mandatory parameters where appropriate:
function Test-Server {
param(
[Parameter(Mandatory)]
[string]$ComputerName
)
Test-Connection -ComputerName $ComputerName -Count 1
}
This makes scripts easier to understand, easier to maintain, harder to misuse, and more suitable for larger teams. A slightly richer example:
function Get-ServiceHealth {
param(
[string]$ServiceName
)
$Service = Get-Service -Name $ServiceName
if ($Service.Status -eq "Running") {
Write-Output "$ServiceName is healthy"
}
else {
Write-Output "$ServiceName is not running"
}
}
Get-ServiceHealth -ServiceName "Spooler"
Imagine a cybersecurity team repeatedly checking an API, an authentication service, a certificate, an event log, disk space, and network connectivity. Instead of duplicating those commands throughout a huge script, functions like Test-API, Test-Service, Test-Certificate, Test-EventLog, Test-DiskSpace and Test-Network keep the automation far easier to maintain — and this is exactly the pattern Lesson 6's toolkit-building lab asks you to build for real.
Lesson Outcome
You should now be able to build a pipeline chain with Where-Object/Select-Object and understand $_, create and use strings/integers/booleans/arrays/hash tables, explain the double-vs-single-quote distinction, write if/elseif/else logic with comparison and logical operators, write foreach/for/while loops, and package reusable logic into functions with typed parameters. Lesson 2 puts these to work on real data — files, CSV, and JSON.