The foreach loop in PowerShell allows you to iterate over every item in a collection and execute a block of code for each element. For instance, you can use a foreach loop to compile a list of files in a directory for an audit, display all running processes on the system, or transfer old files to an archive.
This article covers the syntax of the PowerShell foreach loop, dives into typical use cases, and includes example scripts. It also addresses how to avoid common mistakes and provides best practices for using foreach loops efficiently.
Getting Started with PowerShell Foreach Loops
Basic Syntax and Execution
Below is the basic syntax of the foreach statement:
foreach ($item in $collection) {
# Code to execute for each item
}
The processing of this foreach loop works as follows:
- PowerShell loads the collection $collection into memory and calculates the number of objects it contains.
- The initial object from $collection is stored in the $item variable, and the corresponding code block is executed for this object.
- Subsequently, the value of $item is updated to the next object in $collection, and the code block is executed again for this new object. This cycle repeats until every object in $collection has been processed.
Advanced Syntax
The foreach method provides a more compact way to iterate over collections and may offer improved performance. The syntax is as follows:
$collection.ForEach({ <expression> })
Comparison of Foreach Keyword, Foreach-Object, and Foreach Method
The foreach keyword, foreach-object cmdlet, and foreach method serve a similar purpose, but each has unique use cases and behaviors.
ForEach Keyword
The foreach keyword is used to iterate over a collection of items, loading all items into memory and processing them simultaneously. This makes it an ideal choice for in-memory collections where speed is essential, as it processes all elements at once.
ForEach-Object Cmdlet
In contrast, the foreach-object cmdlet is pipeline-oriented, designed to process each object that passes through the pipeline one at a time. It is more efficient in terms of memory consumption, making it a better fit for scenarios where loading the entire collection into memory is not feasible. While it may be slightly slower than the foreach keyword due to pipeline overhead, it excels in conserving memory during script execution.
ForEach Method
Introduced in PowerShell 5, the foreach method is tailored for certain collection types, such as arrays and lists. It offers a more concise syntax for performing actions on each element. Object-oriented, the foreach method is ideal when chaining methods, and it tends to be more efficient than the foreach-object cmdlet when working with in-memory collections.
In the following example, we demonstrate how to retrieve processes consuming more than 400 MB of memory using the foreach keyword, foreach-object cmdlet, and foreach method.
# Get all processes consuming more than 400MB of memory
$processes = Get-Process | Where-Object { $_.WorkingSet64 -gt 400MB }
# Using foreach keyword (Table Format)
Write-Host "`n=== Using foreach Keyword (Table Format) ===" -ForegroundColor Cyan
$output = foreach ($proc in $processes) {
[PSCustomObject]@{
"Process Name" = $proc.ProcessName
"PID" = $proc.Id
"Memory (MB)" = [math]::Round($proc.WorkingSet64 / 1MB, 2)
}
}
$output | Format-Table -AutoSize
# Using Foreach-Object (List Format)
Write-Host "`n=== Using Foreach-Object Cmdlet (List Format) ===" -ForegroundColor Green
$processes | ForEach-Object {
Write-Output "Process Name : $($_.ProcessName)"
Write-Output "PID : $($_.Id)"
Write-Output "Memory Usage : $([math]::Round($_.WorkingSet64 / 1MB, 2)) MB"
Write-Output "-----------------------------------"
}
# Using .ForEach() Method (CSV-like Output)
Write-Host "`n=== Using .ForEach() Method (CSV-like Output) ===" -ForegroundColor Yellow
$processes.ForEach({
"$($_.ProcessName),$($_.Id),$([math]::Round($_.WorkingSet64 / 1MB, 2)) MB"
}) | ForEach-Object { Write-Output $_ }

Common Use Cases
Displaying All Numbers in an Array
The script below will iterate through an array of numbers, printing each number to the console:
# Define an array of numbers
$numbers = 1..5
# Loop through the array
foreach ($number in $numbers) {
Write-Host "Number (foreach keyword): $number"
}

Listing the Names of All Files in a Directory
The following PowerShell foreach example utilizes the Get-ChildItem cmdlet to retrieve files from a specified directory and display each file’s name:
foreach ($file in Get-ChildItem -Path " D:\Office") {
Write-Output $file.Name
}

Manipulating Each Object in a Collection
This script retrieves all processes with names starting with either “C” or “F”, checks if each process is running, and adds a custom property, isActive, to document the status of each process:
# Get all processes whose names start with 'C' or 'F'
$processes = Get-Process | Where-Object { $_.Name -like "C*" -or $_.Name -like "F*" }
foreach ($process in $processes) {
$isActive = $process.Responding
$process | Add-Member -NotePropertyName "IsActive" -NotePropertyValue $isActive
}
$processes | Format-Table -Property Name, Id, IsActive

Advanced Techniques
Processing Items Based on Specific Criteria
By incorporating If statements in a foreach loop, you can execute different actions depending on the characteristics of each object being processed. The script below collects all files from a given directory but only outputs the names of files that exceed 10 KB in size:
foreach ($file in Get-ChildItem -Path "D:\Office") {
if ($file.Length -gt 10KB) {
Write-Output $file.Name
}
}

This script can be easily adapted to perform other actions on the large files in a directory, beyond just listing them. For instance, you could modify it to copy the files to a different location, compress them, or delete them based on their last access time.
Using Nesting
When one foreach loop is placed inside another, it is referred to as nesting. Nested foreach loops allow for iterating over multiple collections and performing actions that involve elements from both collections. For example, nesting is useful for generating parameter combinations for testing, creating Cartesian products of sets, or iterating over multi-dimensional data.
The following script demonstrates nested foreach loops to generate all possible combinations of items from two arrays: one containing three numbers and the other three letters.
$outerArray = 1..3
$innerArray = 'A', 'B', 'C'
foreach ($outer in $outerArray) {
foreach ($inner in $innerArray) {
Write-Output "$outer $inner"
}
}

Error Handling
When a foreach loop encounters an error, the script will stop executing. A common scenario is attempting to divide by zero, which is undefined mathematically. The error message generated will provide valuable information to help pinpoint the issue.
To manage potential errors effectively and ensure the script continues processing the remaining items in the collection, you can use a Try-Catch block. In the script below, the try block attempts to solve a division problem for each number in the collection. If the division is successful, the result is printed to the console; if an error occurs, the catch block will display an error message in red. Regardless of the outcome, the loop continues with the next item in the collection.
# Sample collection of items.
$numbers = @(1, 2, 0, 4, 5)
foreach ($number in $numbers) {
try {
$result = 10 / $number
Write-Host "10 divided by $number is: $result"
} catch {
Write-Host "Error: Cannot divide by zero. Skipping $number." -ForegroundColor Red
}
}

Avoiding Common Pitfalls
Below are some common mistakes often made when working with foreach loops and tips on how to avoid them.
Reusing the Same Variable Name in Nested Loops
Reusing the same variable name in nested loops can cause unpredictable behavior and lead to conflicts. For example, the following script uses the variable $item in both the outer and inner loops:
foreach ($item in $collection) {
foreach ($item in $nestedCollection) { # Overwrites outer $item
Write-Host $item
}
}
To prevent issues, always use distinct variable names for each loop:
foreach ($outerItem in $collection) {
foreach ($innerItem in $nestedCollection) {
Write-Host $innerItem
}
}
Prematurely Breaking Out of Loops
A break statement can be employed to exit a foreach loop as soon as a specified condition is met. However, improper use of break can cause the loop to terminate prematurely, resulting in incomplete processing of the collection.
As such, exercise caution when using break. Consider alternatives like continue, which allows you to skip specific iterations without ending the loop.
Infinite Loops
If the loop condition remains true indefinitely, or if the collection is modified incorrectly, the loop can run endlessly. For example, in the following script, the loop keeps adding new items to the collection while iterating through it:
$collection = @("Item1", "Item2", "Item3")
foreach ($item in $collection) {
$collection.Add("NewItem") # Changes collection size dynamically
}
One effective way to prevent an infinite loop is by creating a copy of the original collection and iterating over the copy while modifying the original collection:
# Original collection
$collection = @("Item1", "Item2", "Item3")
# Iterate over a copy of the collection
foreach ($item in $collection.Clone()) {
Write-Host "Processing $item"
$collection.Add("NewItem") # Modify the original collection safely
}
Write-Host "Final Collection: $collection"
Best Practices
The following best practices can help optimize your use of foreach loops.
For Pipelined Data, Utilize the foreach-object Cmdlet
As previously mentioned, the foreach keyword in PowerShell loads a collection into memory and processes each item sequentially. However, when working with objects passed through a pipeline, it is more efficient to use the foreach-object cmdlet, which consumes less memory.
Enhance Script Readability and Maintainability
To improve the clarity and long-term maintainability of your scripts, use consistent indentation and spacing within foreach loops. Include meaningful comments and ensure the loop body remains concise by defining functions or methods, as demonstrated here:
# Define a collection of file paths
$filePaths = Get-ChildItem -Path "D:\Office" -File
# Process each file in the collection
foreach ($file in $filePaths) {
# Check if the file is larger than 1KB
if ($file.Length -gt 1KB) {
# Log the file name and size
Write-Host "Large file found: $($file.Name) - Size: $($file.Length / 1KB) KB"
} else {
# Skip smaller files
Write-Host "Skipping small file: $($file.Name)"
}
}

Conclusion
The foreach loop in PowerShell is an essential tool for automating tasks that involve iterating over a collection and executing code on each item, such as file management and process control. Begin by exploring the basic examples shared in this article, then progress to more advanced techniques, including nested loops and error handling, to enhance your scripting capabilities.







