3

I'm trying to write a script in PowerShell that searches folders for files containing unnecessary periods, then mass removes the periods from the file names.

ex. Example.File.doc ---> ExampleFile.doc

Whenever I run the code, the console returns the following: "Rename-Item : Cannot bind argument to parameter 'NewName' because it is an empty string."

Does anyone know what the issue is?

Thanks in advance for any help!

$files = get-childitem -path "C:\XXXX\XXXX\XXXX" -Recurse 
foreach($file in $files) {

    if($file.Name -match ".") {
        $newName = $file.Name -ireplace (".", "")
        Rename-Item $file.FullName $newName
        Write-Host "Renamed" $file.Name "to $newName at Location:" $file.FullName
    }
}

Declan
  • 53
  • 1
  • 4

4 Answers4

2

One thing about string replacing: When I tried your example without escaping the period, it didn't replace anything and returned nothing (empty string), which I believe answers your "Cannot bind argument to parameter 'NewName' because it is an empty string"

This worked by escaping the period. Also, it works with or without the parenthesis.

$string = "the.file.name"

$newstring = $string -ireplace "\.", ""
// output: thefilename
$newstring.length
// output: 11

$othernewstring = $string -ireplace ("\.", "")
// output: thefilename
$othernewstring.length
// output: 11

// What the OP tried
$donothingstring = $string -ireplace (".", "")
// output:
$donothingstring.length
// output: 0

There is some additional information on string replace here, just for reference https://vexx32.github.io/2019/03/20/PowerShell-Replace-Operator/

chrisbyte
  • 1,408
  • 3
  • 11
  • 18
  • 2
    The key here is the `-match` and `-replace` operators support _regex_ and you need to escape metacharacters like the period. – Matt Dec 14 '19 at 02:28
2

$file.Name -ireplace (".", "")

invariably returns an empty string, because the -replace operator (-ireplace is just an alias[1]) operates on regexes (regular expressions), in which metacharacter . matches any character[2].

Since -replace always replaces all matches it finds, all characters in the input are replaced with the empty string, yielding an empty string overall, and passing an empty string via $newName to the (positionally implied) -NewName parameter of Rename-Item predictably causes the error message you saw.

To match a . character verbatim in a regex, you must escape it as \.:

$file.Name -replace '\.', '' # Note: no (...) needed; `, ''` optional

Note that it's generally better to use '...' quoting rather than "..." for regexes and string literals meant to be used verbatim.

However:

  • This would also remove the . char. in the filename extension, which is undesired.

  • Also, your command can be streamlined and simplified, as the solution below shows.


A concise, single-pipeline solution:

Get-ChildItem -File -Path "C:\XXXX\XXXX\XXXX" -Recurse -Include *.*.* | 
  Rename-Item -NewName { ($_.BaseName -replace '\.') + $_.Extension } -WhatIf

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

  • -File limits matching to files (not also directories).

  • -Include *.*.* limits matching to file names that contain at least 2 . chars.

  • Piping the files to rename directly from Get-ChildItem to Rename-Item allows you to specify the -NewName argument as a delay-bind script block ({ ... }), which can dynamically derive the new name from the name of the respective input file, using the properties of the System.IO.FileInfo instances received from Get-ChildItem.

  • $_.BaseName -replace '\.' removes all literal . chars. (escaped as \. in the context of the regex (regular expression) that the -replace operator operates on) from the file base name (the part without the (last) filename extension).

    • Note: not specifying a replacement operand is the same as specifying '', the empty string, resulting in the effective removal of the matched string; in other words: ... -replace '\.' is the same as ... -replace '\.', ''
  • + $_.Extension appends the original extension to the modified base name.


[1] -replace is case-insensitive by default, as all PowerShell operators are; -ireplace just makes that fact explicit; there's also the -creplace variant, where the c indicates that the operation is case-sensitive.

[2] in single-line strings; in multi-line strings, . by default doesn't match \n (newlines), though this can be changed via inline matching option (?s) at the start of the regex.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

There are a few other issues here. You should filter your Get-ChildItem results to return only files by including the -File switch.

Your current script will replace the last . before your file's extension which will cause some problems. You need to use the $File.BaseName and $File.Extension attributes to address this problem.

Substitute -ireplace with the .replace() method.

Lastly, you need to use the -like condition in your If statement. The -match condition is used for regexes and will not work correctly here.

$files = get-childitem -Recurse -File 
foreach($file in $files) {

    if($file.BaseName -like "*.*") {
        $newName = $file.BaseName.Replace(".","") + $file.Extension
        Rename-Item $file.FullName $newName
        Write-Host "Renamed" $file.Name "to $newName at Location:" $file.FullName
    }
}
infosecb
  • 129
  • 5
  • 2
    would it be cleaner to drop the test for a dot and just use the `.Replace('.', '')` method on all the file names? that method will simply do nothing if there is no dot to replace ... it may not be obvious enuf, tho. [*frown*] i can't make up my mind on it. – Lee_Dailey Dec 13 '19 at 23:29
0

Here's another solution that takes advantage of PowerShell's pipeline (remove the -WhatIf after you've tested which files will be renamed).

It also uses a lookahead regular expression to replace all but the last dot in the filename.

Get-ChildItem -Recurse  `
    <# Only select files that have more than one '.' in their name #> `
    | Where-Object { ($_.Name.ToCharArray() | Where-Object {$_ -eq '.'} | Measure-Object ).Count -gt 1 } `
    `
    <# Change the location to the directory and rename so you don't have deal with the directory name #> `
    <# Also, use a lookahead regex to replace all but the last dot. #> `
    | ForEach-Object { Push-Location $_.Directory; Rename-Item $_ ($_.Name -replace "\.(?=.*?\.)", "") -Verbose -WhatIf; Pop-Location }

Here's a more concise version of the same commands using aliases:

dir -r | ?{ ($_.Name.ToCharArray() | ?{ $_ -eq '.' } | measure ).Count -gt 1 } | %{ pushd $_.Directory; ren $_ ($_.Name -replace "\.(?=.*?\.)", "") -v -wh; popd }
Glenn
  • 1,687
  • 15
  • 21