In this article, we will take a look at examples of using constructions to loop through the all files and folders on a disk or in a specific directory (using the For-Each and Get-ChildItem statements). You can widely use them in your PowerShell scripts.
Usually, the task of iterating over all file system objects in a directory arises when you need to perform a certain action with all nested objects. For example, you need to delete, copy, move files, add or replace lines in all files in the specific directory according to some criteria.
You can use the Get-ChildItem PowerShell cmdlet to get a list of child objects (folders and files) in a directory. This is the most popular cmdlet for file system management. There are several aliases for ChildItem: 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’
However, it displays a list of objects located in the root of the specified directory. You can also display the contents of child directories (subdirectories) using the -Recurse parameter:
Get-ChildItem -Path ‘C:\PS’ –Recurse
As you can see, the contents of each subdirectory are displayed sequentially.
Now, let’s look at the general structure of the ForEach loop in PowerShell when using the Get-ChildItem cmdlet:
Get-ChildItem –Path "C:\PS" –Recurse | Foreach-Object { Write-Host $_.FullName }
This command prints a complete list of filenames in the target folder (including any subdirectories).
You can replace the Write-Host with another cmdlet and perform any other action against all files.
In this loop, the variable $_ refers to the current element ($PSItem, or this) inside a foreach loop.
Note. Learn how to query Microsoft SQL server with Invoke SqlCmd on PowerShell.
Also, you can use such a loop structure (but we like it less):
foreach($file in Get-ChildItem $SomeFolder) { # Do something }
For example, you can delete all files with the *.log extension in the specified directory and all subfolders (we won’t really delete the files from disk by adding the parameter WhatIF in PowerShell):
Get-ChildItem –Path "C:\PS\" -Recurse -Filter *.log | Foreach-Object { Remove-Item $_.FullName -WhatIF }
The script found 3 files with the *.log extension and indicated that they could be deleted by this script.
Delete files older than xx days using PowerShell
Consider a script that deletes files older than 10 days in a directory (it can be used when you need to clean up the logs folder, or public network folders). We use the PowerShell Where-Object filter to find the old files:
Get-ChildItem C:\ps\ -Recurse | Where-Object { $_.CreationTime -lt ($(Get-Date).AddDays(-10))} | ForEach-Object { remove-Item $_.FullName –whatif }
Renaming files with a ForEach loop in PowerShell
Another common task that is fairly easy to solve with PowerShell is to rename files in a folder. Let’s say your task is to find all files with the extension *.log in a folder and rename all log files with numbers in their names.
$logFolder = "C:\Logs" Get-ChildItem -Path $logFolder -Recurse -Filter *.log | ForEach-Object { $newName = $_.Name -replace "\d+", "" # Remove all digits from the file name $newPath = Join-Path -Path $_.Directory.FullName -ChildPath $newName # Generate a new file path Rename-Item -Path $_.FullName -NewName $newName -Force # Rename the current file with the new name }
In this case, the Join-Path cmdlet is used to build a new file path that includes the current directory name ($_.Directory.FullName) and the new file name. The Rename-Item is used to rename the file and the -Force parameter to overwrite existing files.
Copy new files recursively with PowerShell
You can use PowerShell to copy new files to the destination folder. For example, you want to synchronize two folders and copy only new files created or modified in the last 7 days to the destination folder (on the file server by the UNC path):
$sourceDir = "C:\Source" $targetDir = "\\FS01\Share\TargetFolder" $daysToCompare = 7 $compareDate = (Get-Date).AddDays(-$daysToCompare) Get-ChildItem -Path $source -Recurse | ForEach-Object { # Copy files that are newer than daysToCompare value if ($_.LastWriteTime -gt $compareDate) { $targetFilePath = $_.FullName.Replace($sourceDir, $targetDir) # Make sure that the target folder exists New-Item -ItemType Directory -Path (Split-Path $targetFilePath) -Force | Out-Null # Copy the file to the target Copy-Item $_.FullName -Destination $targetFilePath -Force } }
Remove empty directories recursively with PowerShell
Or you can loop through all subdirectories and empty folders and subfolders if any exist:
$targetFolder = "C:\PS\" Get-ChildItem -Path $targetFolder -Recurse -Force -Directory | Where-Object {$_.GetFileSystemInfos().Count -eq 0} | Remove-Item -Force -Recurse -whatif
Find large files and move them to a different folder
The following example will find all * .iso files larger than 1 GB on the system drive C: and move them to another partition.
First, let’s set the required file size (1 GB):
$filesize=1024*1024*1024
Now let’s loop through all the files and move them to the target directory:
Get-ChildItem C:\* -Include *.iso -Recurse |Where-Object { $_.Length -gt $filesize} |ForEach-Object { Move-Item $_.FullName D:\ISO -whatif}
Loop through text files in a directory using PowerShell
The following file loop example allows to find files containing the text ‘flush_log’ or ‘error’ for all *.log files in the directory, and saves the found lines to files with a new extension (_outlog):
$files = Get-ChildItem C:\ps\ -Recurse *.log foreach ($f in $files){ $outfile = $f.FullName + "_outlog" Get-Content $f.FullName | Where-Object { ($_ -match 'flush_log' -or $_ -match 'error') } | Set-Content $outfile }
Such a PowerShell script can be useful when searching for specific event entries in log files and filtering out all that is unnecessary. Also, you can use this PS code in other scenarios when you need loop through files, read the contents, and do something with it.
You can use a foreach loop to find the user opening a specific file on a network share via SMB. In this example, we’ll connect to a remote computer (SMB host) using CIMSession and loop through the open files:
$filename="*annualreport.xlsx*" $sess = New-CIMSession –Computername ny-fs01 $files = Get-SMBOpenFile -CimSession $sess foreach ($file in $files) { if ($file.path -like $filename) { $file | select path, ClientUserName | fl } }
2 comments
It seems that the remove empty directories is not working, because an expression is only allowed as the first element of a pipeline.
Indeed, there was an error in the previous PowerShell script. The PS code for deleting empty folders might look like this:
Get-ChildItem -Path C:\TargetFolder -Recurse -Force -Directory | Where-Object {$_.GetFileSystemInfos().Count -eq 0} | Remove-Item -Force -Recurse
Article has been updated, thanks!