Live Cyber Attack Lab 🎯 Watch our IR team detect & respond to a rogue insider trying to steal data! Choose a Session

X

PowerShell ForEach: Loop and Object Basics

PowerShell

illustration of device using PowerShell ForEach

PowerShell is a ubiquitous tool for system administrators and can be used to automate many common tasks across your Windows environment. Like most other programming languages, PowerShell makes use of a concept known as “looping” or “iteration”, in which the same action is performed on a number of different pieces of data.

As you work through our PowerShell tutorial, you’ll come across the “ForEach” command. This is one of the most fundamental and popular ways that PowerShell implements looping, and a good knowledge of the command is important for anyone working with PowerShell.

Get the Free PowerShell and Active Directory Essentials Video Course

I'd recommend this for both new and advanced PowerShell users. Building an AD tool is a great learning experience.

In this article, we’ll take a deeper look at the ForEach command in PowerShell. We’ll explain how this command works, how you can use it to automate tasks and give you some detailed examples of it in action. We’ll also explain some of the considerations you should have in mind while working with it.

What is PowerShell ForEach Loop?

illustration of a conveyer belt reading a command depicting PowerShell ForEach Loop

Nearly all programming languages have a way of “looping” commands. These commands allow you to perform the same action on a number of different pieces of data without having to type the same command out each time.

You can loop in PowerShell in many different ways, but one of the most common is the ForEach loop. The ForEach command reads a number of objects – typically represented by an array or hashtable – and performs a specified action on each. It will then stop when it has worked through all of the objects you have given it.

To see why the ForEach loop is so useful, let’s take a basic example. Say that you need to read all the files in a directory. To get a list of child objects (folders and files) in a directory, you would normally use the Get-ChildItem PowerShell cmdlet. This is the most popular file system cmdlet, and it has several aliases (gci, dir, ls).

The Get-ChildItem cmdlet displays a list of child (nested) objects on a specified drive or directory. The path to the directory is specified through the –Path attribute. For example, to list the files in the C:PS directory, run the command:

Get-ChildItem -Path ‘C:PS’

This command will display a list of objects located in the root of the specified directory, but sometimes you will want to also display the contents of child directories. To do this, you can use the –Recurse parameter:

Get-ChildItem -Path ‘C:PS’ –Recurse

The same outcome can be achieved, though, using the ForEach command, with the added bonus that this will let you perform an action on each item in turn. For instance:

Get-ChildItem –Path "C:PS" |

Foreach-Object {

#Do something with $_.FullName

}

For example, you can delete all files with the *.log extension in the specified directory and all subfolders. Here, we won’t really delete the files from disk by adding the parameter WhatIF, but if you remove this then the command will be live:

Get-ChildItem –Path "C:PS" -Recurse -Filter *.log

|

Foreach-Object {

Remove-Item $_.FullName -WhatIF

}

There are many actions you can perform using this kind of structure. Say, for instance, that you want to make a text file in several different places at once using the Add-Content command. Let’s call these different locations C:\Folder, C:\Program Files\Folder2 and C:\Folder3.

Without a loop, you’d have to type the same command three times, each with a different path to tell PowerShell where to put these files. Like this:

Add-Content -Path 'C:\Folder\textfile.txt' -Value ‘Some text’

Add-Content -Path 'C:\Program Files\Folder2\textfile.txt' -Value ‘Some text’

Add-Content -Path 'C:\Folder2\textfile.txt' -Value ‘Some text’

Even in this small example, you are wasting a lot of time typing. Worse, as the number of commands grows larger, it is easy to make mistakes that can be costly.

Achieving the same goal through a ForEach loop is much easier, less prone to error, and much simpler. To do that, we would normally store the file paths in our example above in an array:

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

This will make a $folders array that can now be worked with by another command in PowerShell. To use the ForEach command to add a file to each of our folders, we can now use the command:

foreach ($t in $folders) {

Add-Content -Path "$t\textfile.txt" -Value "Some text"

}

This is a much quicker way to achieve the same goal. In the remainder of this article, we’ll look at how you can use the ForEach command to speed up many more tasks in PowerShell.

PowerShell ForEach Loop Considerations

Before we get into the details, a word of warning. The ForEach command is a powerful tool, and you need to be careful when using it. A poorly designed loop, or one that contains mistakes, can cause a lot of damage very quickly.

Before going through the rest of this guide, therefore, make sure that you are well versed in the basics of using PowerShell. You should read our guide on PowerShell inputs, for example, and also make sure you are aware of the cybersecurity implications of using PowerShell.

What is PowerShell ForEach and ForEach-Object?

code examples of the ForEach statement, the ForEach-Object Cmdlet and the ForEach () Method

Let’s get into a little more detail. The first thing to realize about using ForEach loops in PowerShell is that there are technically three ways of doing this. These are the ForEach statement, the ForEach-Object Cmdlet, and the ForEach() method. Let’s look at each in turn.

The ForEach Statement

The most basic type of ForEach loop is the ForEach statement. ForEach is an internal PowerShell keyword that is neither a cmdlet nor a function. The basic syntax for using the command is this:

foreach ($i in $array)

Here, the $i variable represents the iterator or the value of each item in $path as it iterates over each element in the array. The value doesn’t have to be $i, though. The variable name can be anything you like.

The ForEach statement is the quickest way to make loops in PowerShell, but it has some disadvantages. We’ll come to those shortly.

The ForEach-Object Cmdlet

The second type of ForEach loop makes use of the ForEach-Object cmdlet. Because ForEach-Object is a cmdlet, it is much more flexible than the ForEach statement we’ve shown you above.

You can pass these cmdlet parameters that affect the way in which it works. Like the foreach statement, the ForEach-Object cmdlet can iterate over a set of objects. A major difference, though, is that the cmlet passes that set of objects and the action to take on each object as a parameter. Here is the same example we’ve given above, but this time using the cmdlet:

$folders | ForEach-Object (Add-Content -Path "$_\text.txt" -Value "Some Text")

The cmdlet can be a little confusing, however, because it has an alias called ForEach, which makes it look the same as the statement above. You can differentiate between these situations by checking if the term “foreach” is followed by ($someVariable in $someSet). Otherwise, in any other context, the code author is probably using the alias for ForEach-Object.

The ForEach() Method

The final way in which ForEach loops can be used was introduced in PowerShell v4. This is the ForEach() method. This method can be used on an array or a collection object, just like the other ways of implementing ForEach loops above. Using the same example as above, this is how the ForEach() method would be used:

$folders.ForEach({

Add-Content -Path "$_\textfile.txt" -Value "Some Text"

})

This looks very similar to the ForEach-object cmdlet, but the ForEach() method works slightly differently. Without getting into all of the details, the most important factor is that it is significantly faster than either method above, and especially so over large datasets.

When To Use Each ForEach Method

There are significant differences between each ForEach method above that means that each should be used in particular cases.

For beginners, or where you just need to build a quick, one-use loop, the syntax of the ForEach statement will be sufficient.

ForEach-Object is best used when sending data through a pipeline because it will continue streaming the objects to the next command in the pipeline. You cannot do the same thing with ForEach () {} because it will break the pipeline and throw error messages if you attempt to send that output to another command. This is very important if you plan to use the data in another command through the pipeline.

It is important to note another difference that these options share, which is performance vs. memory consumption. The ForEach statement loads all of the items upfront into a collection before processing them one at a time. ForEach-Object expects the items to be streamed via the pipeline, thus lowering the memory requirements, but at the same time, taking a performance hit.

Finally, the ForEach() method is considerably faster than either of the other two methods,  but is only supported on PowerShell environments after v4. This can cause compatibility issues if you are working with legacy systems, or you are not sure where your code will be used. Though you should use the ForEach() method wherever possible, you will need to know the details of where your code will be used in order to do so.

PowerShell ForEach Loop Basics

Illustration of a looping workflow

Now we’ve shown you how each type of ForEach loop works, you should begin to see why the command is such a powerful one. ForEach can be used with almost any object in PowerShell, and you can use data piping to pass the results of your loop on to any other script or command.

This makes the ForEach loop an extremely powerful and useful tool. We’ll give you lots of examples of the way you can use ForEach below, but before we do so let’s take a brief look at a practical example of a common task that the ForEach command is useful for.

Let’s say, for this example, that you want to fetch and work with some data on Active Directory users. You can use the Get-ADUser PowerShell cmdlet to collect user information from Active Directory, and if you needed to look at the “city” property of each user before taking an action, you could use a ForEach Loop. Let’s say you want to export users whose city property is set to “London”, and then save these results into a CSV file. Here is a PowerShell script that will do that using a ForEach loop:

$CityReport = "C:\Temp\CityReport.CSV"
Remove-Item $CityReport -ErrorAction SilentlyContinue
$STR = "User Name, City"
Add-Content $CityReport $STR
$AllUsersNow = Get-ADUser –Filter * -SearchBase “OU=TestUsers,DC=TechGenix,DC=Com” -Properties *
Foreach ($ThisUser in $AllUsersNow)
{
$CityOfUser = $ThisUser.City
$ThisUserNow = $ThisUser.CN
IF ($CityOfUser -eq "London")
{
$STRNew = $ThisUserNow+","+$CityOfUser
Add-Content $CityReport $STRNew
}
}

As you can see, what we are doing here is to make a new variable named $CityReport that stores the path for the report. Then, we collect all users from an organizational unit. Then, we process these data using a ForEach loop. This ForEach Loop function checks the “city” property of the user. Then, it uses an IF condition to see if this property has the “London” value or not for the current user. If the user has “London” as the value in the city property, the current username and city name values are added to the C:\Temp\CityReport.CSV file.

As you can see, this is a much quicker way of making a report than checking each user manually. But this is only the beginning of what ForEach loops can be used for. Let’s take a closer look.

ForEach PowerShell Examples

illustrations of ForEach Powershell examples

In this section, we’ll run through eight examples of ForEach applications. Each example will contain some fundamental elements of using ForEach loops in PowerShell, and after working through all eight you should be quite confident working with this tool.

Example 1. Creating Files in Sub-Folders

One of the most basic tasks you can use ForEach loops for is to make files in subfolders. This can be useful, for instance, if you are backing up files in a number of folders regularly.

For this example, let’s say there are ten sub-folders inside the C:\ARCHIVE_VOLUMES folder. Each sub-folder represents an archive volume that gets backed up daily. After each backup is complete, a file named BackupState.txt is created inside each folder containing the date when it was backed up.

Now, we will write a script that will do three things. It will get a list of all the subfolders inside C:\ARCHIVE_VOLUMES, and then loop through each of these folders. Finally, it will create a text file named BackupState.txt, which will contain the current date and time. Here is the script:

# Define the TOP-level folder

$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"

# Get all sub folders

$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }

# Create a text file in each sub-folder and add the current date/time as value.

foreach ($foldername in $Child_Folders.FullName) {

(get-date -Format G) | Out-File -FilePath "$($foldername)\\BackupState.txt" -Force

}

You can then use the Get-ChildItem cmdlet to check if the script worked:

Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include backupstate.txt | Select-Object Fullname,CreationTime,LastWriteTime,Length

Example 2. Fetching Information from Files in Sub-Folders

In this example, we’ll work with the files we created in example 1, and fetch some information from them. We will write a script that will:

  • Recursively find all BackupState.txt files in our subfolders,
  • Use a ForEach statement to read each file, and find the “last backup time” value,
  • And then display the results on your screen.

Here is the script:

## Find all BackupState.txt files in C:\ARCHIVE_VOLUMES

$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName

## Read the contents of each file

foreach ($file in $files) {

Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))

}

Run this script, and a list of the last backup time for each folder will be displayed in a scrolling output in your console.

Example 3. Starting Services Using the ForEach-Object cmdlet

Now let’s look at a more complicated example. System administrators often need to find the status of particular services, and trigger a workflow that will fix any failed services. In this example, we will use the ForEach-object cmdlet to do that.

The script below will do the following:

  • Fetch a list of services that are available, and configured so they can be started automatically, but that is not currently running,
  • Use the Foreach-object cmdlet to run through each service and attempt to start it,
  • Return a message of either “success” or “failed” for each time the Start-Service command is run.

Here’s the script:

## Get a list of automatic services that are stopped.

$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}

## Pass each service object to the pipeline and process them with the Foreach-Object cmdlet

$services | ForEach-Object {

try {

Write-Host "Attempting to start '$($.DisplayName)'"

Start-Service -Name $.Name -ErrorAction STOP

Write-Host "SUCCESS: '$($.DisplayName)' has been started"

} catch {

Write-output "FAILED: $($.exception.message)"

}

}

This script will then return feedback as it attempts to start each service, so you can remedy any further problems manually.

Example 4. Reading Data from a CSV File Using ForEach()

Most system admins have a similar workflow when it comes to the bulk creation of new users. They will store the data of users that need to be created in a CSV file, which can then be used in PowerShell via the import-CSV command. You can also use the ForEach() method to run through each user in turn, and create an account for them.

For this example, you’ll need a file that is populated with the names of some users to be created. It should have two columns, and look like this:

“Firstname”,”Lastname”

“Grimm”,”Reaper”

“Hell”,”Boy”

“Rick”,”Rude”

Now let’s write a script to create these users. This script will first import the CSV file by passing the content path to the Import-CSV cmdlet. Then, using the foreach() method, it will loop through the names and create the new users in Active Directory:

# Import list of Firstname and Lastname from CSV file

$newUsers = Import-Csv -Path .\Employees.csv

Add-Type -AssemblyName System.Web

# Process the list

$newUsers.foreach(

{

# Generate a random password

$password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3)

$secPw = ConvertTo-SecureString -String $password -AsPlainText -Force

# Formulate a username

$userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName

# Build new user attributes

$NewUserParameters = @{

GivenName       = $_.FirstName

Surname         = $_.LastName

Name            = $userName

AccountPassword = $secPw

}




try {

New-AdUser @NewUserParameters -ErrorAction Stop

Write-Output "User '$($userName)' has been created."

}

catch {

Write-Output $_.Exception.Message

}

}

)

You can also write into this script feedback messages, as per the script in example 3, to confirm that each user has been added successfully.

Example 5. Working With Math

A more abstract example of how ForEach loops can be employed would be to use them to generate times tables. This can be done in various ways, and these give an idea of how flexible the command is. For example, a simple implementation would be:

ForEach ($number in 1,2,3,4,5,6,7,8,9,10) { $number * 13}

Or, using the ForEach() method:

Clear-Host

ForEach ($number in 1..10 ) { $number * 13}

Or with an explicitly defined array:

Clear-Host

$NumArray = (1,2,3,4,5,6,7,8,9,10)

ForEach ($number in $NumArray ) { $number * 13}

Each of these commands will achieve the same outcome and show some of the different ways that you can work with the same ForEach command.

Example 6. To Display Files

In this example, we are going to transition from the explicit ($Item in Collection){Block Statement} to input files using Get-ChildItem, then pipe (|) the output into a {Block Statement}.

Here, Get-ChildItem is just an example cmdlet for us to investigate aspects of the PowerShell ForEach functionality. We’ll also use the root of the C: drive because this will run on most computers, but you can change the file path as you like.

Clear-Host

ForEach ($file in Get-ChildItem C:\)

{

$file.name

}

As with many things in PowerShell, there are lots of ways to achieve this same outcome. For example, you can also use piping to complete the same task as above:

Clear-Host

$Path = "C:\Windows\System32\*.dll"

Get-ChildItem $Path |  ForEach {

Write-Host $_.Name

}

Here, we’ve also introduced a $Path variable so you can store the files you are working with somewhere else than in your root folder. Just make sure you define this variable first.

Example 7. Reading Multiple Data From .txt Files

Building on the previous example, we can also use a ForEach loop to read multiple data points out of a set of .txt files. The following script works in much the same way as those in the previous example, but performs a little more complex data processing on the data it reads:

# PowerShell ForEach loop to display files in C:\Program files

$Path = "C:\Program Files\"

"{0,10} {1,-24} {2,-2}" -f `

" Size", "Last Accessed", "File Name "

ForEach ($file in Get-ChildItem $Path -recurse -force)

{If ($file.extension -eq ".txt")

{

"{0,10} {1,-24} {2,-2}" -f `

$file.length, $file.LastAccessTime, $file.fullname

}

}

Example 8. Looping with ForEach and WMI

PowerShell ForEach loops can also be used to work with WMI objects, using much the same syntax as the examples we’ve covered so far. A basic example of this would be the following script:

$disk= Get-WmiObject Win32_LogicalDisk

ForEach ( $drive in $disk ) { "Drive = " + $drive.Name}

And a more complex example that also performs data processing:

# ForEach example to display the partitions size [GB]

$disk= Get-WmiObject Win32_LogicalDisk

"Drive Ltr: Size [GB]"

ForEach ( $drive in $disk ) { "Drive = " + $drive.Name + `

" Size = " + [int]($drive.Size/1073741824)}

PowerShell Resources

illustration of a user mastering PowerShell

Once you’ve worked through the examples above, you should have a great grasp of how ForEach loops work in PowerShell. These are a fundamental part of the way in which PowerShell works, and will appear in almost every tutorial you read on PowerShell.

Here at Varonis, we’ve produced dozens of guides on how to work with PowerShell, and after teaching yourself how to work with ForEach loops it’s worth taking a look at all of the applications that you can put your new-found skills to.

To help you out, here is a list of all of our guides, to give you some idea of the flexibility and power of PowerShell:

PowerShell Basics

Commands + Scripting

IT Pros

A Final Word

ForEach loops are an extremely useful feature of PowerShell and can be used to automate many tasks that would otherwise consume a lot of time and resources. If you’ve worked with other programming languages, you are likely to be familiar with the concept and will find that PowerShell implements it in a fairly straightforward manner. If PowerShell is the first language you are learning, then working with ForEach loops will give you a great basic grounding in the concept of looping.

After working through the examples above, you should check out the resources we’ve made available on how to use PowerShell in a variety of circumstances. Then, as you begin to use PowerShell more, take a look at the extra tools you can use alongside it, and which can boost your productivity even further.

Jeff Petters

Jeff Petters

Jeff has been working on computers since his Dad brought home an IBM PC 8086 with dual disk drives. Researching and writing about data security is his dream job.

 

Does your cybersecurity start at the heart?

Get a highly customized data risk assessment run by engineers who are obsessed with data security.