1

I have a directory of files with the following naming convention: '### Bunch of random names.txt' and I want to rename the files to the same thing minus the '### '.

Should be simple enough with:

Get-ChildItem -File | Rename-Item -newname { $_.Name.SubString(4,$_.Name.Length) }

However I get an "Index and length must refer to a location within the string."

I verify the Name.Length with:

Get-ChildItem -File | select Name, @{ N='name length';E={$_.Name.Length) } }

$_.Name.Length returns the right int value for each file in the directory

When I try this:

Get-ChildItem -File | select Name, @{N='name length';E={ $_.Name.SubString(4,$_.Name.Length) } }

The 'name length' column is blank

Why does substring not like $_.Name.Length? What am I missing here?

Gary
  • 13
  • 1
  • 3
  • Seeing as you know the prefix, I'd do it this way anyway: $TestPath | Get-ChildItem -File | Rename-Item -NewName { ($_.Name).Replace('#### ','') } – Scepticalist Feb 03 '19 at 08:18
  • Sorry, I should have specified, the ### could be any 3 digit numeral. – Gary Feb 03 '19 at 19:35

3 Answers3

1

If you do not specify a second (length) parameter to the Substring() function, it will return the remainder of the string taken from the first parameter (the character index), so you can simply do:

Get-ChildItem -File | ForEach-Object { $_ | Rename-Item -NewName $_.Name.SubString(4)}

The error you got "Index and length must refer to a location within the string." means that the second parameter (the wanted length) exceeds the total length of the string, because you are cutting off the first 4 characters.

$_.Name.SubString(4,$_.Name.Length - 4)

would work, but is overkill in this case.


EDIT

Given the OPs comments, I tested some more and indeed... There seems to be a problem with piping the results from Get-ChildItem directly to the Rename-Item cmdlet. (I'm using Powershell 5.1)

It seems you need to capture the items from the Get-ChildItem cmdlet and iterate that captured collection in order to rename te files. Otherwise, some files could be processed and renamed more than once.

You can capture the collection of files in a variable first like this:

$files = Get-ChildItem -File
foreach($file in $files) { $file | Rename-Item -NewName $file.Name.SubString(4)}

Or by enclosing the Get-ChildItem part in brackets as PetSerAl suggested:

(Get-ChildItem -File) | Rename-Item -newname { $_.Name.SubString(4) }

I found an explanation for this in this answer:

There appears to be a bug that can cause bulk file renaming to fail under certain conditions. If the files are renamed by piping a directory listing to Rename-Item, any file that's renamed to something that's alphabetically higher than its current name is reprocessed by its new name as it's encountered later in the directory listing.

Theo
  • 57,719
  • 8
  • 24
  • 41
  • I thought the same thing, however when I either omit the 2nd parameter for length or use the name.length -4, it truncates the first 8 characters from the filename. – Gary Feb 03 '19 at 19:24
  • I really can't see how.. Can you give us a real life example maybe? – Theo Feb 03 '19 at 19:30
  • @user2234575 Please see the edit on my answer. I have a working solution, but it puzzles me too.. – Theo Feb 03 '19 at 20:08
  • Yeah, I found some weird behavior too, if I rename 27 files, it works fine, the 28th causes it to fail. – Gary Feb 03 '19 at 20:26
  • @user2234575 In my testcases, putting the Foreach-Object in helped and all files were renamed correctly. Does it work for you too now? I must say I had never encountered this bug before, so thanks for the learning experience! +1 – Theo Feb 03 '19 at 20:31
  • I tried the ForEach and it still failed with more than 27 files – Gary Feb 03 '19 at 20:35
  • @user2234575 Weird, for me it worked. Try `$files = Get-ChildItem -File; foreach($file in $files) { $file | Rename-Item -NewName $file.Name.SubString(4)}`. That way you are capturing the items in a variable `$files` first and iterate through that collection. It then should really not be able to process the same file twice I would think. – Theo Feb 03 '19 at 20:38
  • Adding the file objects to the variable worked successfully! Thanks Theo – Gary Feb 03 '19 at 20:46
  • @user2234575 Victory at last! – Theo Feb 03 '19 at 20:49
  • *MUST use a `ForEach-Object`* So, something like that `(Get-ChildItem -File) | Rename-Item -newname { $_.Name.SubString(4) }` not working for you? – user4003407 Feb 03 '19 at 20:49
  • @PetSerAl I did test that way also, but encountered the exact same bug there. – Theo Feb 03 '19 at 20:54
  • @PetSerAl I tried your code again today and.. I now cannot reproduce the problem with it. It works just fine, so I must have done something stupid before. I have edited my answer and added your suggestion aswell. – Theo Feb 04 '19 at 08:44
0

Instead of using SubString you can use Remove:

Get-ChildItem -File | Rename-Item -NewName { $_.Name.Remove(0,4) }
Shayki Abramczyk
  • 36,824
  • 16
  • 89
  • 114
  • Oddly enough, this had the same result as omitting the 2nd parameter for length in the name.substring as suggested above. It truncated the first 8 characters instead of 4. – Gary Feb 03 '19 at 19:27
0

one more solution which checks for # every where

Get-ChildItem -Path E:\temp\*#*  | Rename-Item -NewName  { $_.fullname.replace('#','') }
TheGameiswar
  • 27,855
  • 8
  • 56
  • 94
  • Sorry, I should have specified...the # is a wildcard for any 0-9 character. I am only interested in removing the first 4 characters, there could be numerals and/or '#' in the rest of the filename. – Gary Feb 03 '19 at 19:31