#PSTip ForEach-Object Gotcha!

Note: This tip requires PowerShell 2.0 or above.

Let’s say you want to rename a bunch of files in a folder and imagine that you used the following code:

Get-ChildItem -Recurse C:\Scripts | ForEach-Object { $count = 0; Rename-Item -Path $_.FullName -NewName "TestScript${Count}$($_.Extension)"; $count++}

What do you think will happen here? You may see numerous error messages that the new file name already exists! Let’s quickly check the syntax of ForEach-Object cmdlet:

PS C:\> Get-Command ForEach-Object -Syntax

ForEach-Object [-Process] <scriptblock[]> [-InputObject <psobject>] [-Begin <scriptblock>] [-End <scriptblock>]
[-RemainingScripts <scriptblock[]>] [-WhatIf] [-Confirm] [<CommonParameters>]

ForEach-Object [-MemberName] <string> [-InputObject <psobject>] [-ArgumentList <Object[]>] [-WhatIf] [-Confirm]

As you see in the above output, the default parameter set of ForEach-Object cmdlet has -Begin, -Process, and -End parameters. When no parameter name is specified, the script block gets assigned to the -Process parameter  and gets executed every time an object is available in the pipeline. So, this behavior causes $count to get re-initialized to zero in every iteration.

So, how do we work around this? This is where the Begin script block comes handy. The script block passed to the -Begin parameter executes only once and it is a good place to initialize $count variable. Let us see how:

Get-ChildItem -Recurse C:\Scripts | ForEach-Object -Begin { $count = 0} -Process { Rename-Item -Path $_.FullName -NewName "TestScript${Count}$($_.Extension)"; $count++}

Also, note that it is not mandatory to specify the -Begin and -Process parameters. We can simply write:

Get-ChildItem -Recurse C:\Scripts | ForEach-Object { $count = 0} { Rename-Item -Path $_.FullName -NewName "TestScript${Count}$($_.Extension)"; $count++}
Share on: