Compare:
Measure-Command {
# ~4,000 files
$Files = Get-ChildItem -Path "C:\Windows\System32\DriverStore" -File -Recurse
foreach ($file in $Files) {
$file.LastWriteTime.Date;
}
} | Select-Object -Property TotalMilliseconds
Measure-Command {
Get-ChildItem -Path "C:\Windows\System32\DriverStore" -File -Recurse | ForEach-Object {
$_.LastWriteTime.Date;
}
} | Select-Object -Property TotalMilliseconds
On my system and run hot (i.e., after running both multiple times), the foreach statement takes 790-855 ms. The ForEach-Object takes 860-890 ms. That's a 10% cost at most, and this particular operation is trivial. Is that "meaningful"? In some senses, yes, because 10% is a lot, but realistically, no because the scripts both run in less than a second. I'm not writing an application here.However, let's take something decidedly non-trivial:
Measure-Command {
# ~4,000 files
$Files = Get-ChildItem -Path "C:\Windows\System32\DriverStore" -File -Recurse
$Algorithm = @('MD5','SHA1','SHA256')
foreach ($file in $Files) {
Get-FileHash -Path $file.FullName -Algorithm ($Algorithm[(Get-Random -Minimum 0 -Maximum 3)]);
}
} | Select-Object -Property TotalMilliseconds
Measure-Command {
$Algorithm = @('MD5','SHA1','SHA256')
Get-ChildItem -Path "C:\Windows\System32\DriverStore" -File -Recurse | ForEach-Object {
Get-FileHash -Path $_.FullName -Algorithm ($Algorithm[(Get-Random -Minimum 0 -Maximum 3)]);
}
} | Select-Object -Property TotalMilliseconds
Now, run hot, foreach takes an average of 20.3 seconds over three runs. ForEach-Object takes an average of 20.8 seconds over three runs. Run cold (i.e., after a reboot), they both take about 63-65 seconds. This is on a slightly aging laptop with a spinning metal disk.Most of my workloads involve calling commands that involve significant time like Get-FileHash does. I'm doing things like splitting PDFs based on content and inserting them into an SQL database, or fetching 8,000 records from Active Directory and verifying file share permissions. I have found that for nested loops that I tend to use a ForEach-Object in the pipeline and then use foreach statements for operations on arrayed properties in the object and that works well, but for the outer loop, no, it's not usually worth the time to refactor or eliminate the pipeline.