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) }
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
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) } | ...
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))
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)}