PowerShell -Replace appending (oddly) instead of replacing - powershell

EDIT: PowerShell version is 5.1
I am writing some code that will take the value of a variable from a file, and if that string is made up of other variables within the file, it will locate them and replace it to reconstruct the run-time value. It assumes the variable contains a string, and that the string describes a directory.
For example, the file contains:
$var0 = "C:\Users\v-anad\Documents"
$var1 = "$var0\TestFolder"
Then when the code looks for $var1, it should return something like: "C:\Users\v-anad\Documents\TestFolder\"
However, the actual output I see is:
\TestFolder"-anad\Documents"
When it replaces, it deletes the correct substring ($var0), but when it inserts the value of $var0, it skips over the characters that existed there before, and appends the remaining characters to the end of the string. I have no clue what/where I've gone wrong.
Here is the code in question:
function Get-Var-Value-In-File([string]$varName, [string]$file) {
$regex = "(?<=\$varName = )[^`n]*"
$content = Get-Content -Raw $file
return [regex]::Match($content, $regex).Value
}
$file = 'C:\Users\v-anad\Documents\TestFolder\TestVars.ps1'
$var = '$var1'
$value = Get-Var-Value-In-File $var $file
$regex = "\$[^\\]*"
$nextVar = [regex]::Match($value, $regex).Value
$nextValue = Get-Var-Value-In-File $nextVar $file
Write-Output "$var = $value"
Write-Output "$nextVar = $nextValue"
Write-Output $nextVar.Replace($nextVar, $nextValue)
Write-Output ($value -replace [regex]::Escape($nextVar),$nextValue)
Output:
$var1 = "$var0\TestFolder"
$var0 = "C:\Users\v-anad\Documents"
"C:\Users\v-anad\Documents"
\TestFolder"-anad\Documents"
Note how the code above does not account for the extra quotes that would be inserted into the final value, so should this curious behavior be fixed, the output will be: ""C:\Users\v-anad\Documents"\TestFolder\"

The problem was there was a carriage return (\r, or `r in PowerShell) that my regex included in the match, causing the behavior when the string replacement occurred. Thanks to PerSerAl for being a second pair of eyes to catch it.

Related

powershell How to capture 'left side' of the -replace regex?

First time posting.
i have the following code to replace the suffix of an email and its working fine
replace all characters after # sign with #testdomain.com
$a = 'john.doe#domain.com'
$b = $a -replace "[?=#].*", '#testdomain.com'
$b
john.doe#testdomain.com
what i would like to do, is to capture the actual left side 'source' regex expression to a variable, which would be #domain.com so that i know what i;m replacing and i don;t know how to do it.
Sorry if this had been posted before.
Thank you
So, I'm not sure if this is possible using only the -replace operator and without the use of -match which would store the capture group on the $Matches automatic variable.
This is how you could do it using the regex class directly:
$a = 'john.doe#domain.com'
$Capture = #{}
$b = [regex]::Replace($a, "[?=#].*", {
param($s)
'#testdomain.com'
$Capture.Value = $s.Value
})
$b # => john.doe#testdomain.com
$Capture.Value # => #domain.com
This what I could think using only -replace, adding a delimiter (// in this case) to the replaced string followed by the capture group $0 and then splitting the replacement. Though, this is obviously not a robust solution.
$a = 'john.doe#domain.com'
$b, $capture = $a -replace "[?=#].*", '#testdomain.com//$0' -split '//'
$b # => john.doe#testdomain.com
$capture # => #domain.com
To change the user part you can replace ^.*(?=#):
PS ~> $a = 'john.doe#domain.com'
PS ~> $a -replace '^.*(?=#)', 'jane.doe'
jane.doe#domain.com
The (?=#) construct is known as a lookahead, and describes a zero-length string at the position immediately before the #.

Powershell : Update properties file with key = value

My requirement is, I have a properties file say C:\google\configuration\backup\configuration.properties
with content shown below
backup.path = C:\\ProgramData\\google\\backup
backup.volume.guid = \\\\?\\Volume{49e5d325-8065-49f4-bf0d-r4be94cc1feb}\\
backup.max.count = 10
I have a method that takes key and value as input.
function Script:change_or_replace_value([string]$key, [string]$value) {
$origional_file_content = Get-Content $CONF_FILE_LOCATION
$key_value_map = ConvertFrom-StringData($origional_file_content -join [Environment]::NewLine)
$old_value = $key_value_map.$key
$Old_file_pattern = "$key = $old_value"
$new_file_pattern = "$key = $value"
$origional_file_content | ForEach-Object {$_ -Replace $Old_file_pattern, $new_file_pattern} | Set-Content $NEW_FILE_LOCATION
}
If key is "backup.volume.guid" and value is "\\?\Volume{111111-222-222-444-r4be94cc1feb}\"
method should replace the text
backup.path = C:\\ProgramData\\google\\backup
backup.volume.guid = \\\\?\\Volume{111111-222-222-444-r4be94cc1feb}\\
backup.max.count = 10
If key is "backup.volume.guid" and value is "" method should remove the line
backup.path = C:\\ProgramData\\google\\backup
backup.max.count = 10
If the value is empty delete the line else replace the text for the given key.
It contains special character like \ or other characters
How to delete the content if the key exists and value is an empty string
Your current approach has two problems, based on your attempt to update the properties by string manipulation via the file content as a single string:
In the ForEach-Object script block you'd need a different command to eliminate a line, because the -replace operator always returns something: if the regex pattern doesn't match the input, the input string is passed through.
You're missing an additional string-replacement step: ConvertFrom-StringData considers \ an escape character, so any pair of \\ in the input file turns into a single \ in the resulting hashtable. Therefore, you'll also have to double the \\ in $oldvalue and $value in order for the string replacement on the original file content to work.
Also, -replace, because it expects regex (regular expression) as the search operand, requires metacharachters such as \ to be escaped by \-escaping them; you could do that with [regex]::Escape($Old_file_pattern).
I suggest a different approach that avoids these problems, namely:
Directly modify the hashtable that ConvertFrom-StringData returns.
Then serialize the updated hashtable to the output file, using string formatting.
As part of the string formatting, ouble the \ in the values again by using the [string] type's .Replace() method, which operates on literal strings and is simpler (and faster) in this case; however, you could also use the somewhat counter-intuitive -replace '\\', '\\'
# Assign your real path here.
$OCUM_CONF_FILE_LOCATION = 'in.properties'
# Only for demonstration here: create a sample input file.
#'
backup.path = C:\\ProgramData\\google\\backup
backup.volume.guid = \\\\?\\Volume{49e5d325-8065-49f4-bf0d-r4be94cc1feb}\\
backup.max.count = 10
'# > $OCUM_CONF_FILE_LOCATION
# Function which either updates, adds, or removes an entry.
# NOTE:
# * This function updates input file $OCUM_CONF_FILE_LOCATION *in place*.
# To be safe, be sure to have a backup copy before you try this.
# * Set-Content's default character encoding is used to save the updated file.
# Use the -Encoding parameter as needed.
function Update-PropertiesFile ([string]$key, [string]$value) {
$ht = ConvertFrom-StringData (Get-Content -Raw $OCUM_CONF_FILE_LOCATION)
if ($ht.Contains($key)) { # update or delete existing entry
if ('' -eq $value) { $ht.Remove($key) }
else { $ht[$key] = $value }
} elseif ('' -eq $value) { # entry to remove not found
Write-Warning "No entry with key '$key' found; nothing to remove."
return
} else { # new entry
$ht[$key] = $value
}
# Serialize the updated hashtable back to the input file.
Set-Content $OCUM_CONF_FILE_LOCATION -Value $(
foreach ($key in $ht.Keys) {
'{0} = {1}' -f $key, $ht[$key].Replace('\', '\\')
}
)
}

Reading strings from text files using switch -regex returns null element

Question:
The intention of my script is to filter out the name and phone number from both text files and add them into a hash table with the name being the key and the phone number being the value.
The problem I am facing is
$name = $_.Current is returning $null, as a result of which my hash is not getting populated.
Can someone tell me what the issue is?
Contents of File1.txt:
Lori
234 east 2nd street
Raleigh nc 12345
9199617621
lori#hotmail.com
=================
Contents of File2.txt:
Robert
2531 10th Avenue
Seattle WA 93413
2068869421
robert#hotmail.com
Sample Code:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' { $name = $_.current}
'^\d{10}' {
$phone = $_.current
$hash.Add($name,$phone)
$name=$phone=$null
}
default
{
write-host "Nothing matched"
}
}
$hash
Remove the current property reference from $_:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' {
$name = $_
}
'^\d{10}' {
$phone = $_
$hash.Add($name, $phone)
$name = $phone = $null
}
default {
Write-Host "Nothing matched"
}
}
$hash
Mathias R. Jessen's helpful answer explains your problem and offers an effective solution:
it is automatic variable $_ / $PSItem itself that contains the current input object (whatever its type is - what properties $_ / $PSItem has therefore depends on the input object's specific type).
Aside from that, there's potential for making the code both less verbose and more efficient:
# Initialize the output hashtable.
$hash = #{}
# Create the regex that will be used on each input file's content.
# (?...) sets options: i ... case-insensitive; m ... ^ and $ match
# the beginning and end of every *line*.
$re = [regex] '(?im)^([a-z]+|\d{10})$'
# Loop over each input file's content (as a whole, thanks to -Raw).
Get-Content -Raw File*.txt | foreach {
# Look for name and phone number.
$matchColl = $re.Matches($_)
if ($matchColl.Count -eq 2) { # Both found, add hashtable entry.
$hash.Add($matchColl.Value[0], $matchColl.Value[1])
} else {
Write-Host "Nothing matched."
}
}
# Output the resulting hashtable.
$hash
A note on the construction of the .NET [System.Text.RegularExpressions.Regex] object (or [regex] for short), [regex] '(?im)^([a-z]+|\d{10})$':
Embedding matching options IgnoreCase and Multiline as inline options i and m directly in the regex string ((?im) is convenient, in that it allows using simple cast syntax ([regex] ...) to construct the regular-expression .NET object.
However, this syntax may be obscure and, furthermore, not all matching options are available in inline form, so here's the more verbose, but easier-to-read equivalent:
$re = New-Object regex -ArgumentList '^([a-z]+|\d{10})$', 'IgnoreCase, Multiline'
Note that the two options must be specified comma-separated, as a single string, which PowerShell translates into the bit-OR-ed values of the corresponding enumeration values.
other solution, use convertfrom-string
$template=#'
{name*:Lori}
{street:234 east 2nd street}
{city:Raleigh nc 12345}
{phone:9199617621}
{mail:lori#hotmail.com}
{name*:Robert}
{street:2531 10th Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
{name*:Robert}
{street:2531 Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
'#
Get-Content -Path "c:\temp\file*.txt" | ConvertFrom-String -TemplateContent $template | select name, phone

How to properly string replace in Powershell without appending the replaced variable to a newline?

I'm pretty new to powershell/programming so bear with me. I have this bug that appends the new renamed path to a new-line without the rest of path.
The console output:
/content/pizza/en/ingredients/
helloworld/menu-eng.html
What I want:
/content/pizza/en/ingredients/helloworld/menu-eng.html
What the code below is supposed to do is rename a bunch paths. Right now testName is hard-coded but after I get this to work properly it will be dynamic.
My code:
$testName = "helloworld"
$text = (Get-Content W:\test\Rename\rename.csv) | Out-String
$listOfUri = Import-Csv W:\test\Rename\rename.csv
foreach ($element in $listOfUri) {
if ($element -match "menu-eng.html") {
$elementString = $element.'ColumnTitle' | Out-String
$elementString = $elementString.Replace('menu-eng.html', '')
$varPath1 = $elementString
$elementString = $elementString.Insert('', 'http://www.pizza.com')
$elementName = ([System.Uri]$elementString).Segments[-1]
$elementString = $elementString.Replace($elementName, '')
$elementString = $elementString.Replace('http://www.pizza.com', '')
$varPath2 = $elementString.Insert($elementString.Length, $testName + '/')
$text = $text.Replace($varPath1.Trim(), $varPath2)
}
}
$text
Assuming your .csv file looks like this:
ColumnTitle,Idk
/content/pizza/en/ingredients/SPAM/menu-eng.html,Stuff
Then:
$testName = 'helloworld'
foreach ($row in Import-CSV d:\rename.csv) {
$bit = $row.'ColumnTitle'.Split('/')[-2]
$row.'ColumnTitle'.replace($bit, $testName)
}
I have no real idea what all the rest of your code is for, particularly my earlier comment, your line:
$text = (Get-Content W:\test\Rename\rename.csv) | Out-String
is making $text into an /array/ of all the lines in the file, including the headers. You can still use .Replace() on it in PowerShell, but it's going to do the replace on every line. I can't quite see how that gives you the output you get, but it will give you multiple lines for every line in the input file.

PowerShell: get data between two strings

I've got next data into variable $out (type is Object[]):
Success...
Go!
0:#217> trace && .quit
0x000 Subline : _OK
0x008 Timed : NO
0x016 Check : _OK
0x022 Post :
0x030 Offset : None
0x038 Hint : False
0x050 NextHint : False
quit:
I need extract text between string 0:#217> trace && .quit and quit:
I wrote:
[Regex]::Match($out, "(?<=.quit').+?(?=quit:)").Value
But this extracts required data into a line (type String), not a column (Object[]). How to fix this?
P.S.
I solved the problem by myself as follows
([Regex]'(?is)(?:(?<=\.quit).+(?=quit:))').Match(($out -join "`n")).Value
But maybe there is more perfect way to do this?
Problem solved
([Regex]'\s+0x([^q]+)').Match(($out -join "`n")).Value
Just use the -split function to create a String[] of your result:
$result = ([regex]::Match($a, '\.quit(.*)quit:').Groups[1].value) -split [System.Environment]::NewLine
[Edit: this will work if $out is a String[], e.g. from $out = Get-Content results.txt, from your other comments, you might have something different].
As a general approach, have a true/false flag which chooses whether lines are allowed through or not, and when you see the first line then set the flag, and when you see the last line you want, change the flag.
$middle = foreach ($line in $out) {
if ($line -match '^quit') { $allow = $false }
if ($allow) { write-output $line }
if ($line -match '0:#217>') { $allow = $true }
}
The ordering of the tests determines whether the start or end lines show up in the results or not.
This can be shortened on the console for typing, into something like:
# loop # end line clears flag # print? # start line sets flag
$out |% { if($_ -match '^quit'){$f=0}; if ($f){$_}; if ($_ -match '0:#217>'){$f=1} }
This might work, but it makes some assumptions:
$out -match '^ '
$out is always a String[]. Force it with #($out) if it might be a single string.
You are using PowerShell v4 or v5, so -operator will act as a filter on an array.
Your example data is accurate, and all the lines you want start with a space, and all the other lines do not.
The following is for very simple searches, get the string data between a starting string and ending string.
Upsides
Very simplistic
Downsides
Does poorly for data with multiple matches etc
Code
Function Get-StringBetweenStartEnd {
Param($Text,$Start,$End)
$Regex = [Regex]::new("(?<="+$Start+")(.*)(?="+$End+")")
$Match = $Regex.Match($String)
if($Match.Success) { Return $Match.Value}else{Return ""}
}
Example Usage
$String = "Test: disconnected: 10.10.10.1::59270 (VNC Viewer closed)"
$Result = Get-StringBetweenStartEnd -Text $String -Start "nected:" -End "::"
$Result.Trim()
Output:
10.10.10.1