2

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) }
Andrew
  • 23
  • 5
  • 1
    Once you have this script under control doing what you want it to do I recommend going over to CodeReview.SE to have the experts there give you some suggestions on how to improve your script. – Matt Oct 20 '17 at 19:33
  • Well don't I feel noobtastic... I've never visited CodeReview.SE, looks like I have a new site to catch up on if I get any downtime. Thanks for the pointer Matt! – TheMadTechnician Oct 20 '17 at 19:44
  • @TheMadTechnician We need more PowerShellers there. If I keep answering question I am never going to learn anything. – Matt Oct 20 '17 at 19:48
  • @Matt Thank you for the recommendation. I will certainly be looking at that. – Andrew Oct 20 '17 at 20:45
  • @Andrew please don't add you answer in the question add it as a separate answer see [tour] – Petter Friberg Oct 20 '17 at 21:07

2 Answers2

1

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)}
    
Matt
  • 45,022
  • 8
  • 78
  • 119
  • Better yet, make the FileSystem provider filter results rather than getting all results and making PowerShell filter things. `gci -Path $path -Filter "*$char*"|%{renaming stuff here}` – TheMadTechnician Oct 20 '17 at 19:36
  • I'm working on it. There are several points that need to be addressed here. I was trying to suggest what to do with fewer changes as possible. There are some lessons to be learned here I didn't want to skip. – Matt Oct 20 '17 at 19:37
  • @Matt Thank you for your response! I attempted to implement `[regex]::Escape()` in the same fashion as you in your first example. This resulted in the characters not being found and no getting replaced. – Andrew Oct 20 '17 at 20:26
  • @Matt I have successfully implemented the script by following your third suggestion. I've also fixed the `$input` var and am altering the basename instead of the name. Here is the solution: `gci -Path "$path" | Where-Object { $_.Basename.Contains($char) } | ForEach-Object { $_ | rename-item -NewName ($_.Basename.Replace($char, $newChar) + $_.extension) }` – Andrew Oct 20 '17 at 21:01
0

THE SOLUTION: I have implemented the suggestions by @Matt and solved the errors with the following:

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