Search code examples
stringpowershellreplacerename-item-cmdlet

Powershell Script to replace characters in path strings errors out due to "(" or ")"


I have written a Powershell script that receives N number of characters that have to be replaced by another given character from a user. For example, the user wants to replace (, ), and -. The user enters the characters that have to be replaced through this loop:

#Declare array for special characters to replace
$replaceChars = @( )

#Instruct the user about entering characters to replace
Write-Host
Write-Host "Please enter what you would like to replace in your names. This can contain one or more characters. Example: (2) or ^ or _" -ForegroundColor Cyan
Write-Host "Enter -1 to end your input and continue." -ForegroundColor Cyan

#Get values until sentinal is passed
$input = "" #input from user declared to blank

#loop for special characters
while($input -ne -1) {

    #get the input from the user
    $input = Read-Host -Prompt "Enter what you would like to replace (-1 to finish)"

    #if the input isn't sentinal, put it in the replaceChars array
    if($replaceChars.Length -gt 0 -and $input -eq -1) {
        Write-Host
        Write-Host "Your entries have been stored." -ForegroundColor Green
    } #end-if
    elseif($replaceChars.Length -eq 0 -and $input -eq -1) {
        Write-Host
        Write-Host "ERROR: You must enter at least one character to continue." -ForegroundColor Red
        $input = $NULL
    }
    elseif($input -eq '') {
    Write-Host
    Write-Host "Invalid entry. Entry cannot be blank, please enter a value." -ForegroundColor Red
    }#end-elseif
    elseif($input.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -ge 0) {
        Write-Host
        Write-Host "Invalid entry. File names cannot contain / \ : * ? `" < > |" -ForegroundColor Red
    }#end-elseif
    else {
        $replaceChars += $input
    } #end-else

}#end-while

The user then enters the replacement character through this code:

#Get the char to replace to
Write-Host
Write-Host "Please enter what you want to replace the selected characters with. Leave blank and hit enter to delete the old characters." -ForegroundColor Cyan
$newChar = Read-Host -Prompt "Please enter new character(s)"
while($newChar.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -ge 0) {
    Write-Host
    Write-Host "The entry is invalid for file names. File names cannot contain / \ : * ? `" < > |" -ForegroundColor Red
    $newChar = Read-Host -Prompt "Please enter new character(s)"
}
Write-Host
Write-Host "New character has been stored" -ForegroundColor Green

I am then processing the entries with this:

#Iterate through each character 
foreach($char in $replaceChars) {
    if($type -eq 1) {
        gci -File -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item  -NewName $_.Name.Replace($char, $newChar) }
    } elseif ($type -eq 2) { #end-if
        gci -Directory -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item  -NewName $_.Name.Replace($char, $newChar) }
    } else { #end-elseif 
        gci -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item  -NewName $_.Name.Replace($char, $newChar) }
    }#end-else
} #end-foreach

THE ERROR: If a user enters something like ( or ), then the last part of the script fails out with the following error code:

parsing "(" - Not enough )'s.
At <path to script>:109 char:50
+ ... gci -File -Path "$path" | Where-Object { $_.Name -match $char } | For ...
+                                              ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], ArgumentException
    + FullyQualifiedErrorId : System.ArgumentException

MY SOLUTIONS: I have attempted to use [regex]::Escape() to escape the $char and $newChar variables to fix the parentheses error. I have also attempted to resolve this by enclosing the variables with single and double quotes; however, this prevents the actual character replacement from occuring. I understand the parentheses/special characters is what is causing the issue, and I need to escape those somehow; however, the [regex]::Escape() solution breaks the character matching and ends up not replacing any of the user's entered characters.

MY QUESTION: How can I successfully escape the $char and $newChar variables in the GCI cmdlet and still replace special characters like ( or )

gci -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item  -NewName $_.Name.Replace($char, $newChar) }

Solution

  • How did you try to implement [regex]::Escape() exactly? If you tried to save $char before hand as escaped I see your problem. Since your .replace() method does not use regex. This should leave you with a few options

    1. Just update the where clause to use the escape character just for the comparison. That was $char is not permanently changed. This would be a good idea regardless if you plan on using -match and not supporting regex characters.

      gci -Path "$path" | Where-Object { $_.Name -match [regex]::Escape($char) } | ...
      
    2. Save $char as escaped and use the -replace operator which also supports regex.

      gci -Path "$path" | Where-Object { $_.Name -match $char } | 
          ForEach-Object { $_ | rename-item  -NewName ($_.Name -replace $char, $newChar)) 
      
    3. Not use regex operators at all

      gci -Path "$path" | Where-Object { $_.Name.Contains($char) } | ...
      

    Couple points of review

    • Don't use the name $input. It is a reserved variable name. See about_automatic_variables

    • Since you are working with the fullname of the file I would caution that you could also accidentally edit the extension of the file. The basename property might be useful to you in that case.

    • As TheMadTechnician points out you can avoid the regex/non-regex issue by filtering at the Get-ChildItem level instead.

      gci -Path "$path" -Filter "*$char*" | Rename-Item  -NewName {$_.Name.Replace($char, $newChar)}