Powershell ForEach-Object processing files in a directory more than once

111 views Asked by At

I have a series of files that all were exported from a base system (Remedy). By default they have 32 characters of ID related information before the filename. I would like to strip these characters out. However, doing so also results in duplicates, so I need a way to rename them without creating duplicates.

examples:

CWL000000004901-z2AF Work Log01-RFP 2012 01 Recommendation for Award.pdf

CWL000000004716-z2AF Work Log01-Computrace Nov 2009.pdf

CWL000000006007-z2AF Work Log01-Computrace Nov 2009.pdf

(the latter 2 will be identical with the preceding characters removed)

$i = 1
Get-ChildItem -Path "C:\temp\Remedy Contract Attachments\Output\CTR_WorkLog_test1_a" | 
ForEach-Object -Process {
    $NewName = $_.BaseName.Substring(32)
    $NewName2 = $NewName + "_" + $i + $_.Extension
    Rename-Item $_.FullName -NewName $NewName2
    $i++
 }

This is my code currently.

It seems to work as long as the total number of characters is less than 64,

Computrace_Nov_2009_156.pdf

Computrace_Nov_2009_157.pdf

but then it seems to be processing the rest repeatedly, like so

ietor_127_502.pdf

There are only 394 files in the folder.

What I've learned from PowerShell is what I've picked up from here mostly, but I'm absolutely stumped. Why is it processing the files multiple times? It throws a bunch of errors saying it can't remove more than the number of characters in a string, so obviously it's still processing them before it finally crashes. But why?

Or is there a better way to do this?

I'm sure it's something small and I'm going to feel silly for fighting with this code all afternoon.

Thanks!

2

There are 2 answers

6
mklement0 On BEST ANSWER
  • Use (...), the grouping operator, to ensure that all input files are collected up front.

    • Not doing so may cause already renamed files to re-enter the enumeration.
  • For efficiency, pipe directly to Rename-Item, and use a delay-bind script block to dynamically determine the new file name.

    • Because delay-bind script blocks execute in a child scope of the caller, a workaround is needed in order to increment a value across invocations - hence the use of a hashtable as the $i value. See this answer for an explanation.
$i = @{ Value = 1 }
(Get-ChildItem -LiteralPath 'C:\temp\Remedy Contract Attachments\Output\CTR_WorkLog_test1_a') | 
  Rename-Item -NewName {
    $_.BaseName.Substring(32) + '_' + $i.Value++ + $_.Extension
  } -WhatIf 

Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf and re-execute once you're sure the operation will do what you want.

9
Olaf On

Try it this way:

$i = 1
$FileList =
Get-ChildItem -Path "C:\temp\Remedy Contract Attachments\Output\CTR_WorkLog_test1_a" -File

foreach ($File in $FileList) {
    $NewName = '{0}_{1:d3}{2}' -f $File.BaseName.Substring(32), $i, $File.Extension
    Rename-Item $File.FullName -NewName $NewName
    $i++
}