Iterating through files in a directory is a core task in systems administration and automated workflows. In PowerShell, files are not treated as plain strings containing directory paths; instead, querying the filesystem emits strongly typed System.IO.FileInfo objects. A common automation task involves processing every file in a folder, such as renaming, filtering, converting, or archiving them in bulk. The standard pattern combines Get-ChildItem to retrieve files with ForEach-Object (or a foreach statement) to act on each one individually in a streaming, memory-efficient manner.
The Get-ChildItem and Pipeline Iteration Pattern
The standard idiom for processing files in PowerShell combines Get-ChildItem (which discovers files) with ForEach-Object (which processes each file individually via the pipeline):
Get-ChildItem -Path <Path> -Filter <Filter> -File | ForEach-Object { <Logic> }Get-ChildItem -Path "C:\Reports" -Filter "*.csv" | ForEach-Object {
Write-Output "Found file: $($_.Name)"
}where:
-Path "C:\Reports"specifies the target directory.-Filter "*.csv"restricts results to files matching the wildcard pattern.$_represents the currentFileInfoobject as it flows through the pipeline.
Found file: january.csv
Found file: february.csv
Found file: march.csvFile Object Properties
When iterating through files, the current object in the pipeline ($_ or $PSItem) provides direct access to rich file metadata:
| Property | Type | Description |
|---|---|---|
$_.Name |
string |
The file name including its extension (e.g., app.log). |
$_.BaseName |
string |
The file name without its extension (e.g., app). |
$_.Extension |
string |
The file extension including the leading dot (e.g., .log). |
$_.FullName |
string |
The absolute path to the file on disk. |
$_.Length |
long |
The file size expressed in raw bytes. |
$_.LastWriteTime |
DateTime |
Timestamp when the file was last modified. |
$_.DirectoryName |
string |
The parent folder path holding the file. |
param (
[string]$TargetDirectory = "$env:TEMP\LogDrop"
)
Get-ChildItem -Path $TargetDirectory -Filter "*.log" -File | ForEach-Object {
[PSCustomObject]@{
FileName = $_.Name
BaseName = $_.BaseName
SizeBytes = $_.Length
SizeKB = [math]::Round($_.Length / 1KB, 2)
LastModified = $_.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
}
}where:
-Path $TargetDirectoryspecifies the folder to search.-Filter "*.log"uses native filesystem provider pattern matching to isolate.logfiles.-Fileensures that directories matching the pattern are excluded from the output.$_captures each individualFileInfoobject passing through the pipeline.[math]::Round($_.Length / 1KB, 2)calculates the file size in kilobytes using the built-in1KBnumeric multiplier.
Execute the script from the console:
pwsh ./enumerate-files.ps1 -TargetDirectory "C:\AppLogs"FileName BaseName SizeBytes SizeKB LastModified
-------- -------- --------- ------ ------------
audit-2024.log audit-2024 1048576 1024.0 2024-10-24 14:15
service-trace.log service-tr 52428 51.2 2024-10-24 16:30
web-access.log web-access 204800 200.0 2024-10-24 18:02Filtering Further with Where-Object
Combine Where-Object in the pipeline to narrow down files by additional criteria beyond a simple wildcard.
Get-ChildItem -Path "C:\Reports" -File | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) } | ForEach-Object {
Write-Output "$($_.Name) was modified in the last 7 days."
}march.csv was modified in the last 7 days.Recursive File Search (-Recurse)
To traverse an entire directory tree including all nested child folders, add the -Recurse switch. You can optionally restrict recursion depth using -Depth:
Get-ChildItem -Path "C:\Projects" -Filter "*.json" -File -Recurse -Depth 2Practical Example: Batch Renaming Files
A common real-world task is renaming multiple files based on a specific pattern, for instance, adding a date prefix to every file in a folder.
$targetFolder = "C:\Reports"
$datePrefix = Get-Date -Format "yyyy-MM-dd"
Get-ChildItem -Path $targetFolder -File | ForEach-Object {
$newName = "$datePrefix`_$($_.Name)"
Rename-Item -Path $_.FullName -NewName $newName
Write-Output "Renamed $($_.Name) to $newName"
}where:
$_.FullNameprovides the complete path required byRename-Item.- The backtick (
`) before the underscore escapes it, preventing PowerShell from interpreting$datePrefix_as a single (nonexistent) variable name.
./batch-rename.ps1Renamed january.csv to 2024-03-14_january.csv
Renamed february.csv to 2024-03-14_february.csv
Renamed march.csv to 2024-03-14_march.csvBatch Rename with Structured Logging
A more advanced pattern uses Rename-Item with -PassThru to emit confirmation objects back into the pipeline for logging:
param (
[string]$DirectoryPath = "$PSScriptRoot\Staging",
[string]$Prefix = "ARCHIVE"
)
if (-not (Test-Path -Path $DirectoryPath)) {
Write-Error "Target directory '$DirectoryPath' does not exist."
exit 1
}
$CurrentDate = Get-Date -Format "yyyyMMdd"
Get-ChildItem -Path $DirectoryPath -Filter "*.txt" -File | ForEach-Object {
$NewFileName = "{0}_{1}_{2}.log" -f $Prefix, $CurrentDate, $_.BaseName
Rename-Item -Path $_.FullName -NewName $NewFileName -PassThru | ForEach-Object {
[PSCustomObject]@{
OriginalName = $_.OldName # Retained via pipeline context
NewName = $_.Name
Destination = $_.FullName
RenamedAt = (Get-Date -Format "HH:mm:ss")
}
}
}where:
Test-Pathchecks whether the target folder exists before attempting modifications.Get-ChildItemcollects.txtfiles in the staging folder."{0}_{1}_{2}.log" -f ...uses composite string formatting to build the standardized new filename.Rename-Item -Path $_.FullName -NewName $NewFileNamerenames the file on disk.-PassThruoutputs the modified file object back into the pipeline for confirmation logging.
Execute the script from the console:
pwsh ./batch-rename-logs.ps1 -DirectoryPath "C:\Scripts\Staging" -Prefix "PROD"NewName Destination RenamedAt
------- ----------- ---------
PROD_20241024_trace-event.log C:\Scripts\Staging\PROD_20241024... 22:45:10
PROD_20241024_user-action.log C:\Scripts\Staging\PROD_20241024... 22:45:10Practical Example: Batch Processing File Contents
Beyond renaming, this pattern is also highly useful for reading and transforming file contents in bulk. For example, you could use it to convert every .txt file in a folder to uppercase content.
$targetFolder = "C:\Data\Incoming"
$outputFolder = "C:\Data\Processed"
if (-not (Test-Path $outputFolder)) {
New-Item -ItemType Directory -Path $outputFolder | Out-Null
}
Get-ChildItem -Path $targetFolder -Filter "*.txt" | ForEach-Object {
$content = Get-Content -Path $_.FullName -Raw
$processedContent = $content.ToUpper()
$outputPath = Join-Path $outputFolder $_.Name
Set-Content -Path $outputPath -Value $processedContent
Write-Output "Processed: $($_.Name)"
}where:
-Rawreads the entire file as a single string, rather than an array of lines.Join-Pathsafely combines the output folder and filename into a full path, avoiding manual string concatenation errors.
./batch-process.ps1Processed: notes.txt
Processed: readme.txt