0

I need to truncate filenames to 35 characters (including extension) so I run this script and it worked for the directory it self (PowerShell, Windows 10).

Get-ChildItem *.pdf | rename-item -NewName {$_.name.substring(0,31) + $_.Extension} 

Then I wanted to apply the same script including subdirectories:

Get-ChildItem -Recurse -Include *.pdf | Rename-Item -NewName {$_.Name.substring(0,31) + $_.Extension}

This script gave me an error like this for each file:

Rename-Item : Error in input to script block for parameter 'NewName'. Exception when calling "Substring" with the arguments "2": "The index and length must reference a location in the string. Parameter name: length"
On line: 1 Character: 62
+ ... *.pdf | Rename-Item -NewName {$_.Name.substring(0,31) + $_.Extension}
+                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (C:\User\prsn..._file_name_long.pdf:PSObject) [Rename-Item], ParameterBindingException
    + FullyQualifiedErrorId : ScriptBlockArgumentInvocationFailed,Microsoft.PowerShell.Commands.RenameItemCommand

I tried this one but it doesn't go on subdirectories: Command to truncate all filenames at 255 characters

I found this one but it doesn't have an answer: https://superuser.com/questions/1188711/how-to-recursively-truncate-filenames-and-directory-names-in-powershell

Rodrick
  • 595
  • 10
  • 27

3 Answers3

1

Use the -replace regex operator to remove anything after the first 35 chars - it will simply ignore any string that doesn't have at least 35 characters and return it as-is:

$_.Name -replace '(?<=^.{35}).*$'
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
1

I don't think you can use $_ that way. I think you have to wrap it in a ForEach loop in order to get the references to work that way. Once that's done, you'll have to specify the path of the file you want to rename as well:

Something like:

Get-ChildItem -Recurse -Include *.pdf -File | 
ForEach-Object{
    Rename-Item -Path $_.FullName -NewName ( $_.BaseName.SubString(0, 31) + $_.Extension )
    }

Notice I used parenthesis instead of curly braces. If you use a script block it may not evaluate. There are other ways to make it happen, but I think using parens is the most obvious.

Notice I used $_.BaseName instead of name. Base name doesn't include the extension. though I don't know how your sub-string works out I left it in for you decide.

You can combine this with @Mathias's answer or some modification of it. That might give you a better or more reliable way to derive the new file name. I haven't tested it, but it might look something like:

Get-ChildItem -Recurse -Include *.pdf -File | 
    ForEach-Object{        
        Rename-Item -Path $_.FullName -NewName ( ($_.Name -replace '(?<=^.{35}).*$') + $_.Extension )
        }
Steven
  • 6,817
  • 1
  • 14
  • 14
  • The first answer throws an error when a filenames is less than 35 (still works fine). The second answer removes file extension. – 7cc Jun 19 '20 at 12:21
  • @7cc , Yes I got the same, and frankly expected issue with SubString indexing. Unfortunately we're sometimes left to assume, in this case that there's a reliable expectation of the file name length. If we were left to write for every possible issue with a given approach we wouldn't get very far. If you don't mind I'll incorporate in the above examples. – Steven Jun 19 '20 at 14:41
1

The correct error message is
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string.

The reason is that the second argument is greater than the length of the file name. e.g. "abc".Substring(0,4) throws.

Answer

$renameTarget = dir -File -Recurse *.pdf | ? { $_.Name.Length -gt 35 }
$renameTarget | Rename-Item -NewName {
  $_.name.substring(0, 31) + ".pdf"
}

or

dir *.pdf -File -Recurse | Rename-Item -NewName {
  $_.name.substring(0, [Math]::Min($_.BaseName.length, 31)) + ".pdf"
}
7cc
  • 1,149
  • 4
  • 10