How can I escape commas within double quotes during a replace? - powershell

I am using PowerShell to iterate over a specified list of files, using -replace to increment a version string within each and the version string contains commas. For example:
VERSION 6,21,0,0
needs to become:
VERSION 7,1,0,0
Here is what I'm doing:
ForEach-Object -InputObject $Files -Process {
foreach ($str in $_) {
(Get-Content -Path $str) | ForEach {
$_ -replace "VERSION \d+,\d+,0,0","VERSION $MAJOR,$MINOR,0,0"
} | Set-Content -Encoding UTF8 $str
}
which is mostly working, except the commas are being replaced by dots.
VERSION 6,21,0,0
becomes:
VERSION 7.1.0.0
I understand the need for double quotes so $MAJOR and $MINOR are evaluated, but when I do that the commas are handled as shown above. Using the backtick to escape the commas has no effect on the output. What am I doing wrong?

Related

how to replace text ($) with quotation marks in a text file by using powershell

I am trying to replace the text ($) with quotation marks in a text file by using PowerShell
Here is the code I am trying, But not giving expected results:
$FilePath = "C:\PT\Test\"
Get-ChildItem $FilePath -Filter *.TXT | ForEach-Object {
(Get-Content $_.FullName) | Foreach-Object {
$_ -replace ',"$"', ',"$",'
} | Set-Content $_.FullName
}
As pointed out by Abraham in his comment, the $ is a reserved character in regex known as Anchor, if you want to match a literal $ using -replace you would need to escape it: \$:
Get-ChildItem $FilePath -Filter *.TXT | ForEach-Object {
(Get-Content $_.FullName -Raw) -replace ',"\$"',',"$",' |
Set-Content $_.FullName
}
The other alternative is to use the .Replace(..) string method which will match literal characters.
Get-ChildItem $FilePath -Filter *.TXT | ForEach-Object {
(Get-Content $_.FullName -Raw).Replace(',"$"',',"$",') |
Set-Content $_.FullName
}
By looking at your code seems like you want to replace ,"$" with ,"$",, if this was not the case let me know.
The -replace operator:
uses a regex as the search-pattern operand, and therefore requires a verbatim $ to be escaped as \$ in order to be interpreted as such (an unescaped $ is a regex metacharacter representing the end of the input string)
also uses $ as a metacharacter in the substitution operand, namely to refer to the text that the regex captured ($&) or parts thereof (e.g. $1 to refer to what the first capture group ((...)) captured). In this case, escape it as $$.
Note: Situationally, even an unescaped $ may work, namely if, in combination with the subsequent characters, if any, it cannot be interpreted as a placeholder such as $& or $1. However, in the interest of robustness it is better to escape.
Therefore, use:
PS> ',"$"20' -replace ',"\$"', ',"$$",'
,"$",20
Taking a step back:
As Abraham Zinala suggests, for verbatim string replacements, the .Replace() string method is sufficient, whose use doesn't necessitate escaping, and which is faster to boot:
PS> ',"$"20'.Replace(',"$"', ',"$",')
,"$",20
Caveat: Unlike -replace, .Replace() is case-sensitive - invariably in Windows PowerShell and by default in PowerShell (Core) 7+.
See this answer for a detailed juxtaposition of -replace and .Replace(), including how to perform escaping for -replace programmatically.
To put it all together, along with an optimization that reads each file in full, with Get-Content -Raw, for much faster processing (to avoid appending a trailing newline, -NoNewLine is used with Set-Content):
$FilePath = "C:\PT\Test"
Get-ChildItem $FilePath -Filter *.TXT | ForEach-Object {
(Get-Content -Raw $_.FullName).Replace(',"$"', ',"$",') |
Set-Content -NoNewLine $_.FullName
}

Change pipe delimited file to comma delimited in Powershell

I have a pipe delimited .TXT file. I need to change the delimiter to a comma instead but still keep the file extension as .TXT. The file looks like this:
Column 1 |Column 2
13|2019-09-30
96|2019-09-26
173|2019-09-25
I am using Windows Powershell 5.1 version for my script.
I am using the following code:
$file = New-Object System.IO.StreamReader -Arg "c:\file.txt"
$outstream = [System.IO.StreamWriter] "c:\out.txt"
while ($line = $file.ReadLine()) {
$s = $line -replace '|', ','
$outstream.WriteLine($s)
}
$file.close()
$outstream.close()
Instead of just replacing the pipe with a comma, the output file looks like this:
C,o,l,u,m,n, 1 , |,C,o,l,u,m,n, 2
1,3,|,2,0,1,9,-,0,9,-,3,0
9,6,|2,0,1,9,-,0,9,-,2,6
1,7,3,|,2,0,1,9,-,0,9,-,2,5
The only problem with your answer is in how you try to replace the | characters in the input:
$s = $line -replace '|', ',' # WRONG
PowerShell's -replace operator expects a regex (regular expression) as its first RHS operand, and | is a regex metacharacter (has special meaning)[1]; to use it as a literal character, you must \-escape it:
# '\'-escape regex metacharacter '|' to treat it literally.
$s = $line -replace '\|', ','
While PowerShell's -replace operator is very flexible, in simple cases such as this one you can alternatively use the [string] type's .Replace() method, which performs literal string replacements and therefore doesn't require escaping (it's also faster than -replace):
# Use literal string replacement.
# Note: .Replace() is case-*sensitive*, unlike -replace
$s = $line.Replace('|', ',')
[1] | denotes an alternation in a regex, meaning that the subexpressions on either side are matched against the input string and one of them matching is sufficient; if your full regex is just |, it effectively matches the empty string before and after each character in the input, which explains your symptom; e.g., 'foo' -replace '|', '#' yields #f#o#o#
You can use Import-Csv and Export-Csv by specifying the -Delimiter.
Import-Csv -Delimiter '|' -Path "c:\file.txt" | Export-Csv -Delimiter ',' -Path "c:\file.txt" -NoTypeInformation
You will find the -split and -join operators to be of interest.
Get-Content -Path "C:\File.TXT" | ForEach-Object { ($_ -split "\|") -join "," } | Set-Content -Path "C:\Out.TXT"

Search and replace a string in PowerShell

I need to search and replace values in a file using the values from another file. For example, A.txt has a string with a value LICENSE_KEY_LOC=test_lic and B.txt contains the string LICENSE_KEY_LOC= or some value in it. Now I need to replace the complete string in B.txt with the value from A.txt. I tried the following but for some reason it does not work.
$filename = "C:\temp\A.txt"
Get-Content $filename | ForEach-Object {
$val = $_
$var = $_.Split("=")[0]
$var1 = Write-Host $var'='
$_ -replace "$var1", "$val"
} | Set-Content C:\temp\B.txt
You may use the following, which assumes LICENSE_KEY_LOC=string is on a line by itself in the file and only exists once:
$filename = Get-Content "c:\temp\A.txt"
$replace = ($filename | Select-String -pattern "(?<=^LICENSE_KEY_LOC=).*$").matches.value
(Get-Content B.txt) -replace "(?<=^LICENSE_KEY_LOC=).*$","$replace" | Set-Content "c:\temp\B.txt"
For updating multiple single keys/fields in a file, you can use an array and loop through each element by updating the $Keys array:
$filename = Get-Content "c:\temp\A.txt"
$Keys = #("LICENSE_KEY_LOC","DB_UName","DB_PASSWD")
ForEach ($Key in $Keys) {
$replace = ($filename | Select-String -pattern "(?<=^$Key=).*$").matches.value
(Get-Content "c:\temp\B.txt") -replace "(?<=^$Key=).*$","$replace" | Set-Content "c:\temp\B.txt"
}
You can put this into a function as well to make it more modular:
Function Update-Fields {
Param(
[Parameter(Mandatory=$true)]
[Alias("S")]
[ValidateScript({Test-Path $_})]
[string]$SourcePath,
[Parameter(Mandatory=$true)]
[Alias("D")]
[ValidateScript({Test-Path $_})]
[string]$DestinationPath,
[Parameter(Mandatory=$true)]
[string[]]$Fields
)
$filename = Get-Content $SourcePath
ForEach ($Key in $Fields) {
$replace = ($filename | Select-String -pattern "(?<=^$Key=).*$").matches.value
(Get-Content $DestinationPath) -replace "(?<=^$Key=).*$","$replace" | Set-Content $DestinationPath
}
}
Update-Fields -S c:\temp\a.txt -D c:\temp\b.txt -Fields "LICENSE_KEY_LOC","DB_UName","DB_PASSWD"
Explanation - Variables and Regex:
$replace contains the result of a string selection that matches a regex pattern. This is a case-insensitive match, but you can make it case-sensitive using -CaseSensitive parameter in the Select-String command.
(?<=^LICENSE_KEY_LOC=): Performs a positive lookbehind regex (non-capturing) of the string LICENSE_KEY_LOC= at the beginning of a line.
(?<=) is a positive lookbehind mechanism of regex
^ marks the beginning of the string on each line
LICENSE_KEY_LOC= is a string literal of the text
.*$: Matches all characters except newline and carriage return until the end of the string on each line
.* matches zero or more characters except newline and carriage return because we did not specify single line mode.
$ marks the end of the string on each line
-replace "(?<=^LICENSE_KEY_LOC=).*$","$replace" is the replace operator that does a regex match (first set of double quotes) and replaces the contents of that match with other strings or part of the regex capture (second set of double quotes).
"$replace" becomes the value of the $replace variable since we used double quotes. If we had used single quotes around the variable, then the replacement string would be literally $replace.
Get-Content "c:\temp\A.txt" gets the contents of the file A.txt. It reads each line as a [string] and stores each line in an [array] object.
Explanation - Function:
Parameters
$SourcePath represents the path to the source file that you want to read. I added alias S so that -S switch could be used when running the command. It validates that the path exists ({Test-Path $_}) before executing any changes to the files.
$DestinationPath represents the path to the source file that you want to read. I added alias D so that -D switch could be used when running the command. It validates that the path exists ({Test-Path $_}) before executing any changes to the files.
$Fields is a string array. You can input a single string or multiple strings in an array format (#("string1","string2") or "string1","string2"). You can create a variable that contains the string array and then just use the variable as the parameter value like -Fields $MyArray.

String replacement not working in powershell script at runtime

I have powershell file in which i have line of variable decalration as below
[string] $global:myExePath = "\\myshare\code\scripts";
I want to replace \\myshare\code\scripts with \\mynewshare\code1\psscript at runtime by executing a powershell script.
I am using
Get-Content $originalfile | ForEach-Object { $_ -replace "\\myshare\code\scripts", $mynewcodelocation.FullName } | Set-Content ($originalfile)
If am execuing
{ $_ -replace "scripts", $mynewcodelocation.FullName } it is working fine, but it is not working for { $_ -replace "\\myshare\code\scripts", $mynewcodelocation.FullName }
What is wrong here ?
'\' is a special regex character used to escape other special character.You need to double each back slash to match one back slash.
-replace "\\\\myshare\\code\\scripts",$mynewcodelocation.FullName
When you don't know the content of a string you can use the escape method to escape a string for you:
$unc = [regex]::escape("\\myshare\code\scripts")
$unc
\\\\myshare\\code\\scripts
-replace $unc,$mynewcodelocation.FullName

Problems with replacing newline

Iam trying to replace following string with PowerShell:
...
("
Intel(R) Network Connections 14.2.100.0
","
14.2.100.0
")
...
The code that I use is:
Get-Content $logfilepath |
Foreach-Object { $_ -replace '`r`n`r`n', 'xx'} |
Set-Content $logfilepath_new
But I have no success, can someone say me, where the error is?
First, you are using single quotes in the replace string -
'`r`n`r`n'
that means they are treated verbatim and not as newline characters, so you have to use -
"`r`n`r`n"
To replace, read the file as string and use the Replace() method
$content=[string] $template= [System.IO.File]::ReadAllText("test.txt")
$content.Replace("`r`n`r`n","xx")
Get-content returns an array of lines, so CRLF is essentially your delimiter. Two CRLF sequences back to back would be interpreted as the end of the currrent line, followed by a null line, so no line (object) should contain '`r`n`r`n'. A multi-line regex replace would probably be a better choice.
as alternate method using PS cmdlets:
Get-Content $logfilepath |
Foreach-Object -Begin { $content="" } -Process { $content += $_ ; $content += "xx" } -End { $content } |
Set-Content $logfilepath_new
I used the following code to replace somestring with newline:
$nl = [System.Environment]::NewLine
$content = $content.Replace( somestring, $nl )