Understanding loops for processing bulk data: the difference between foreach and ForEach-Object and the $_ automatic variable in the pipeline, for loops with a counter, while and do-until for conditions, Break and Continue control, plus performance considerations between pipeline and foreach.

In episode 5 you built scripts that make decisions: if, switch, and friends. But decisions only matter if they're applied to lots of data. In the real world, you won't write commands one by one for a hundred files — you need to repeat. This episode covers loops: foreach, for, while, and do — the engines that process collections in bulk.
If if is a gate that selects a path, a loop is a conveyor belt that passes items one by one through that gate. Combine the two, and you have a script that checks, modifies, and filters hundreds of objects in an instant.
There are two syntaxes that look similar but behave differently. First, the foreach statement, which processes an entire collection:
foreach ($file in Get-ChildItem /data) {
Write-Host "Memproses $($file.Name)"
}Second, the ForEach-Object cmdlet used in the pipeline:
Get-ChildItem /data | ForEach-Object {
Write-Host "Memproses $($_.Name)"
}The difference is fundamental: foreach loads the whole collection into memory then iterates; ForEach-Object processes each item as it flows through the pipeline. For small collections you won't feel the difference, but for millions of items this difference decides.
Inside a ForEach-Object block, the $_ variable (or $PSItem) refers to the item currently being processed. This is an automatic variable populated by the pipeline — you don't need to (and shouldn't) set it yourself.
Get-Service | ForEach-Object {
Write-Host "$($_.Name) sedang $($_.Status)"
}You also met $_ in the catch block in error handling contexts — there it points to the error record. Here it points to the pipeline object. Same concept: one automatic variable whose context is determined by its environment.
ForEach-Object accepts three blocks at once: -Begin runs once before the first item, -Process runs for each item, and -End runs once after the last item.
Get-Process pwsh | ForEach-Object `
-Begin { $total = 0 } `
-Process { $total += $_.WS } `
-End { Write-Host "Total memori: $total bytes" }This pattern is useful for aggregation — calculating totals, collecting lists, or opening-closing resources. -Begin prepares the accumulator, -Process weighs it, -End reports the result.
When the number of iterations is already known, use for — the classic loop with a counter:
for ($i = 0; $i -lt 5; $i++) {
Write-Host "Iterasi ke-$i"
}The three parts inside the parentheses: initialization $i = 0, condition $i -lt 5, and increment $i++. Execution order: check the condition, run the block, increment the counter, repeat. Great for processing arrays by index:
$nama = @("Ayu", "Budi", "Citra")
for ($i = 0; $i -lt $nama.Count; $i++) {
Write-Host "$($i + 1). $($nama[$i])"
}while repeats as long as the condition is true, without knowing in advance when it will stop:
$antrian = 5
while ($antrian -gt 0) {
Write-Host "Sisa antrian: $antrian"
$antrian--
}The condition is checked before the block runs. If the condition is already false at the start, the block never runs. while suits things with indefinite duration: waiting for a service to start, waiting for a file to appear, or polling status.
These two siblings of while reverse the order: the block runs first, the condition is checked after. This means the block is guaranteed to run at least once.
do {
Write-Host "Mencoba koneksi..."
Start-Sleep -Seconds 2
$siap = Test-NetConnection -ComputerName server -Port 443 -InformationLevel Quiet
} while (-not $siap)do-until is its mirror: it repeats until the condition becomes true.
$i = 0
do {
$i++
} until ($i -ge 10)
Write-Host "Selesai di iterasi ke-$i"Note the difference from while: do-while stops when the condition is false, do-until stops when the condition is true.
Three keywords control the flow inside loops:
break — exits the loop entirely.continue — jumps to the next iteration, skipping the rest of the block.return — exits the function or script containing the loop.for ($i = 1; $i -le 10; $i++) {
if ($i -eq 3) { continue }
if ($i -eq 8) { break }
Write-Host $i
}The result: 1, 2, 4, 5, 6, 7. Number 3 is skipped by continue; when $i reaches 8, break stops the loop entirely.
A common question: which is faster, foreach or ForEach-Object?
$data = 1..100000
$start = Get-Date
foreach ($x in $data) { $null = $x }
"foreach: $( (Get-Date) - $start )"
$start = Get-Date
$data | ForEach-Object { $null = $_ }
"ForEach-Object: $( (Get-Date) - $start )"In many cases foreach is faster because it doesn't go through pipeline overhead and doesn't create per-item objects. However ForEach-Object is memory-efficient because it processes streaming — it doesn't need to hold the entire collection in memory.
Tip
Rule of thumb: for small-to-medium collections already in variables, use foreach. For data flowing from another cmdlet in a pipeline, use ForEach-Object — and in episode 8 you'll learn -Parallel, which runs iterations concurrently in PowerShell 7.
Combine everything: a script that summarizes large files in a folder.
$folder = "/data"
$batasMb = 100
$files = Get-ChildItem $folder -File -ErrorAction SilentlyContinue
if (-not $files) {
Write-Host "Tidak ada file di $folder" -ForegroundColor Yellow
exit 1
}
$total = 0
$besar = foreach ($f in $files) {
$total += $f.Length
if ($f.Length -gt $batasMb * 1MB) { $f }
}
Write-Host "Jumlah file: $($files.Count)"
Write-Host "Total ukuran: $([math]::Round($total / 1MB, 2)) MB"
Write-Host "File di atas $batasMb MB: $($besar.Count)"This script uses all of this episode's concepts at once: foreach to collect large files, total accumulation, and handling the empty state via if from episode 5.
Episode 6 gives you the repetition engine: foreach for whole collections, ForEach-Object for the pipeline with the $_ variable, -Begin/-Process/-End blocks for aggregation, for with a counter for indices, while for indefinite durations, do-while and do-until guaranteed to run once, plus break, continue, and return to control flow.
Key takeaways:
foreach loads all data, ForEach-Object processes streaming — choose based on data size.$_ is the item being processed; don't set it manually.do-while and do-until are guaranteed to execute the block at least once.break stops the loop, continue jumps to the next iteration.In episode 7 we package the loop and decision logic into functions & script blocks — named code blocks that can be called, given parameters, and reused without copying code. See you in episode 7!