The while loop is an entry-condition control flow structure that repeatedly executes a script block as long as a specified conditional expression evaluates to $true. It's the go-to loop construct when the number of iterations isn't known in advance and instead depends on a changing condition.
Because the conditional test occurs before the code block executes, a while loop executes zero times if the condition evaluates to $false at the start.
Basic while Syntax
The while loop consists of an evaluation condition enclosed in parentheses followed by an executable script block enclosed in curly braces.
Syntax Structure
while (<condition>) {
}PowerShell evaluates <condition> as a boolean state. The loop continues to run as long as the expression produces $true (or a truthy value like a non-zero integer or populated object). As soon as the condition evaluates to $false or $null, execution exits the loop and continues with the next statement in the script.
$count = 1
while ($count -le 5) {
Write-Output "Count is: $count"
$count++
}where:
$count = 1initializes the loop control variable.while ($count -le 5)evaluates the condition before each iteration; the loop runs only while this condition is$true.Write-Outputsends the iteration message to the pipeline.$count++operation increments the counter. Without it, the condition would never evaluate tofalse, resulting in an infinite loop.
./while-basic.ps1Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5Common Use Cases
The while loop is best suited for scenarios where the exact number of iterations is unknown in advance and depends on external runtime conditions.
1. Waiting for a Condition to Become True (Polling System States)
while loops are commonly used to poll for a condition, such as waiting for a service to reach a running state:
$service = Get-Service -Name "Spooler"
while ($service.Status -ne "Running") {
Write-Output "Waiting for Spooler service to start..."
Start-Sleep -Seconds 2
$service.Refresh()
}
Write-Output "Spooler service is now running."where:
Start-Sleep -Seconds 2pauses execution briefly between checks, avoiding excessive CPU usage from rapid polling..Refresh()updates the$serviceobject with the service's current live status on each iteration.
2. Reading Input Until a Valid Value Is Given
$input = ""
while ($input -notmatch "^\d+$") {
$input = Read-Host "Enter a number"
if ($input -notmatch "^\d+$") {
Write-Warning "That's not a valid number. Try again."
}
}
Write-Output "You entered: $input"./validate-input.ps1Enter a number: abc
WARNING: That's not a valid number. Try again.
Enter a number: 42
You entered: 423. Processing Items Until a Queue Is Empty
A while loop is the standard pattern for draining collections, such as .NET queues (System.Collections.Generic.Queue[T]) or arrays, until empty:
$queue = [System.Collections.Generic.Queue[string]]::new()
$queue.Enqueue("Task1")
$queue.Enqueue("Task2")
$queue.Enqueue("Task3")
while ($queue.Count -gt 0) {
$task = $queue.Dequeue()
Write-Output "Processing: $task"
}Processing: Task1
Processing: Task2
Processing: Task3where:
$queue.Count -gt 0keeps the loop running as long as items remain..Dequeue()removes and returns the next item, naturally shrinking the queue toward the exit condition.
4. Continuous Background Monitoring (Daemon Pattern)
Scripts designed to run continuously as background services use while ($true) with an internal Start-Sleep pause and explicit exit conditions (break):
while ($true) {
if (Test-Path -Path "C:\Configs\stop.signal") {
Write-Output "Stop signal detected. Shutting down daemon."
break
}
Start-Sleep -Seconds 10
}Practical Example: Resilient Service Polling with Timeout
In production automation, polling an external resource indefinitely can cause scripts to freeze if the target never responds. The following script combines state evaluation with a timeout mechanism using a .NET Stopwatch object to ensure the loop terminates predictably:
param (
[string]$ServiceName = "Spooler",
[int]$TimeoutSeconds = 10
)
$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$ServiceState = (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue).Status
Write-Output "Polling service '$ServiceName' (Timeout: $TimeoutSeconds seconds)..."
while ($ServiceState -ne "Running" -and $Stopwatch.Elapsed.TotalSeconds -lt $TimeoutSeconds) {
Write-Output "Status: $ServiceState. Waiting for service to start..."
Start-Sleep -Seconds 2
$ServiceState = (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue).Status
}
$Stopwatch.Stop()
[PSCustomObject]@{
ServiceName = $ServiceName
FinalStatus = $ServiceState
ElapsedSeconds = [math]::Round($Stopwatch.Elapsed.TotalSeconds, 2)
IsOperational = ($ServiceState -eq "Running")
TimedOut = ($ServiceState -ne "Running" -and $Stopwatch.Elapsed.TotalSeconds -ge $TimeoutSeconds)
}where:
param(...)defines configurable parameters for the target service and timeout limit.[System.Diagnostics.Stopwatch]::StartNew()begins high-resolution time tracking.$ServiceState -ne "Running" -and $Stopwatch.Elapsed.TotalSeconds -lt $TimeoutSecondsforms the compound loop condition. The loop terminates if the service enters theRunningstate or if elapsed time exceeds$TimeoutSeconds.Start-Sleep -Seconds 2pauses execution to avoid pegging the CPU during polling cycles.[PSCustomObject]summarizes the outcome into a structured data object.
Execute the script from the console:
pwsh ./wait-for-service-ready.ps1 -ServiceName "Spooler" -TimeoutSeconds 6Polling service 'Spooler' (Timeout: 10 seconds)...
ServiceName : Spooler
FinalStatus : Running
ElapsedSeconds : 0.02
IsOperational : True
TimedOut : False