The foreach loop in PowerShell enables you to iterate through all the items in a collection and execute a block of code for each element. For example, you can use a foreach loop to create a list of all files in a directory for an audit, display all the processes running on the system, or move old files to an archive.
This article details the syntax of the PowerShell foreach loop and explores common use cases, complete with example scripts. Then it explains how to avoid common errors and offers best practices for using foreach loops effectively.
Getting Started with PowerShell Foreach Loops
Basic Syntax and Processing
Here is the basic syntaxoftheforeach statement:
foreach ($item in $collection) {
# Code to execute for each item
}
The processing of this foreach loop proceeds as follows:
- PowerShell loads the collection $collection into memory and determines the number of objects it contains.
- The first object from $collection is assigned to the $item variable, and the code block is executed for that object.
- The value of $item is updated to next object in $collection and the code block is executed for that object. This step is repeated until all objects in $collection have been processed.
Advanced Syntax
The foreach method offers a more concise way to iterate over collections, and it can potentially deliver better performance. The syntax is as follows:
$collection.ForEach({ <expression> })
Comparison of Foreach keyword, Foreach-Object, and Foreach method
Foreach keyword, foreach-object, and Foreach method provide a similar purpose, but they have distinct use cases and behavior.
ForEach Keyword
The foreach keyword is used to iterate over collection of items, loads all the items of collection in memory and process all items at once. Making it an ideal choice when dealing with in-memory collection for speedy processing.
ForEach-Object Cmdlet
Foreach-object cmdlet, on the other hand, is a pipeline-oriented cmdlet designed to process each object streaming through pipeline one at a time. It reduces resource consumption such as memory and is suitable for scenarios where loading the entire collection into memory is not practical. While slightly slower than foreach due to the pipeline overhead, its advantageous in efficient memory utilization during script execution.
ForEach Method
Foreach method was introduced in PowerShell 5, foreach method is useful on certain collection types such as arrays and lists, provides concise syntax for performing actions on each element. Foreach method is object-oriented and used when we want to chain methods and for in-memory collection its more efficient than foreach-object cmdlet.
In the following example, we are getting processes consuming memory more than 400 mb with foreach, foreach-object 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
Display All Numbers in an Array
The following script will loop through an array of numbers and write each one 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"
}
Display the Names of All Files in a Directory
The following PowerShell foreach example uses the Get-ChildItem cmdlet to retrieves the file from a specified directory and displays the name of each file:
foreach ($file in Get-ChildItem -Path " D:\Office") {
Write-Output $file.Name
}
Manipulate Each Object in a Collection
The following script gets all processes whose name starts with either C or F, checks to see if each process is running, and adds the custom property isActive to each process to document its status:
# 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 Differently Based on Your Criteria
You can use If statements in foreach loops to perform different actions based on the characteristics of each object being processed. The following script gets all files in a specified directory but outputs the names of only those larger than 10 kb:
foreach ($file in Get-ChildItem -Path "D:\Office") {
if ($file.Length -gt 10KB) {
Write-Output $file.Name
}
}
This script could easily be modified to take other actions on the large files in a directory instead of merely listing them. For example, you could to copy them to another location, compress them, or delete them based on when they were last accessed.
Using Nesting
Putting one foreach loop inside another is called nesting. Nested foreach loops can be used to iterate through two or more collections and perform operations that involve element from both collections. For instance, nesting can be used to generate combinations of parameters for testing, creating Cartesian products of sets or iterate over multi-dimensional data.
The script below uses nested foreach loops to generate all possible combinations of items from two different arrays, one with three numbers and one with three letters:
$outerArray = 1..3
$innerArray = 'A', 'B', 'C'
foreach ($outer in $outerArray) {
foreach ($inner in $innerArray) {
Write-Output "$outer $inner"
}
}
Handling Errors
If a foreach loop encounters an error, the script will terminate. One common example is when the code tries to divide by zero, which is not mathematically possible. The resulting error message can help you identify the cause of issues.
You can use a Try-Catch block to handle potential issues gracefully and ensure that the script continues to process additional elements in the collection. In the following script, the try block attempts to perform a division problem using each number in a collection. If the operation is successful, the result is written to the host; if not, the catch block displays an error message in red. In either case, the loop proceeds to 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 Mistakes
Here are some mistakes that are often made with foreach loops and how to avoid them.
Using the Same Variable Name in Nested Loops
Using the same variable name in nested loops can lead to unexpected behavior and conflicts. For instance, the following script uses the same variable $item in both the outer and inner loops:
foreach ($item in $collection) {
foreach ($item in $nestedCollection) { # Overwrites outer $item
Write-Host $item
}
}
To avoid problems, use unique variable names for each loop:
foreach ($outerItem in $collection) {
foreach ($innerItem in $nestedCollection) {
Write-Host $innerItem
}
}
Breaking Out of Loops Prematurely
A break statement can be used to exit a foreach loop once a desired condition is met. However, using break statements improperly can prematurely terminate the loop, leading to incomplete processing of the collection.
Accordingly, use break carefully. Consider alternative like continue to skip specific iterations without exiting the loop:
Infinite Loops
If the loop condition never evaluates to false or the collection is modified in an improper way, the loop can continue indefinitely. For example, as the following script iterates over a collection, it keeps adding new items to that collection:
$collection = @("Item1", "Item2", "Item3")
foreach ($item in $collection) {
$collection.Add("NewItem") # Changes collection size dynamically
}
One way to avoid this infinite loop is to make a copy of the original collection and then iterate through the copy, making changes to the original:
# 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 you use foreach loops more effectively.
For pipelined data, use the foreach-object cmdlet.
As we have seen, foreach in PowerShell loads a collection into memory and then processes each items sequentially. Sometimes, however, you need to process objects passed through a pipeline. In those cases, use the foreach-object cmdlet, which also uses less memory. For examples, see this Essential PowerShell Commands guide.
Maximize the readability and maintainability of scripts.
To make your scripts easier to read and maintain, use consistent indentation and spacing for your foreach loops, add meaningful comments, and keep the loop body concise by defining functions or methods, as illustrated 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 a valuable tool for automating tasks that require iterating through a collection and running code on each item, such as managing files and controlling processes. Start by experimenting with the simple examples provided in this article and then move on to more advanced techniques like nested loops and error handling.
FAQ
What is the difference between a foreach loop and the foreach-object cmdlet?
A foreach loop is used to iterate over an array or other collection, which it loads into memory before processing the data. The foreach-object cmdlet is used to process objects passed through a pipeline by another cmdlet.
How do you handle errors in a foreach loop?
Use a try-catch block to handle exceptions gracefully.
Can you break out of a foreach loop in PowerShell?
Yes, you can use a break statement to exit a foreach loop once a desired condition is met. Here, the loop is broken when the number 5 is reached in the collection, so only the numbers 1 through 4 are written to the host:
foreach ($number in 1..10) {
if ($number -eq 5) { break }
Write-Host $number
}
How do you nest foreach loops?
To nest foreach loops, place one loop inside another. To avoid errors, be sure to use unique variable names for different loops. The following script uses nested foreach loops to iterate two collections and display all possible combinations of the items in those collections:
$outerCollection = @(1, 2)
$innerCollection = @(3, 4)
foreach ($outerItem in $outerCollection) {
foreach ($innerItem in $innerCollection) {
Write-Host "Outer: $outerItem, Inner: $innerItem"
}
}