Unable to edit/replace string value with plus symbol "+" in text file via PowerShell - windows

I'm trying to update a group of XML based .config files. There is a string in the file that contains a plus sign that I cannot replace with my script:
< SharedPassKey=123456789abcdefghi/JKLM+nopqrst= />
If I include the plus sign, the script does nothing. Since all the configs vary I need to replace the text - cannot just go with new files.
The desired result is that the matching value in the file be replaced with the specified value, but the + symbol is not allowing this. Here is my PS script:
$DIRs = Get-ChildItem -Path "C:\TEST" -Directory
Get-ChildItem $DIRs -File -Recurse -Filter *.config |
ForEach { (Get-Content $_.FullName) |
ForEach { $_ -replace '< SharedPassKey=123456789abcdefghi/JKLM+nopqrst= />','< SharedPassKey=123456789abcdefghi/JKLM.nopqrst= />' } |
Set-Content $_.FullName }
As a test I replaced all the text up to the plus sign and it worked fine, but the plus sign and following were present at the end of the new text as I had left the "+nopqrst" out of the script.
Running on PSv3, FYI.

The -replace operator uses a regular expression for the first postfix argument to define the search pattern. In a regular expression, certain characters have a special meaning. + in particular is a so-called "quantifier" with the meaning "one or more times the preceding (sub)expression". In order to replace literal special characters, you need to escape them.
Fortunately, there's a built-in method for escaping strings:
$_ -replace [RegEx]::Escape('< SharedPassKey=123456789abcdefghi/JKLM+nopqrst= />'),'< SharedPassKey=123456789abcdefghi/JKLM.nopqrst= />'

If you have valid XML data and you want to modify the value of a node attribute in an XML file you may want to consider using a proper XML parser, like this:
$xmlfile = 'C:\path\to\your.config'
[xml]$xml = Get-Content $xmlfile
$attr = $xml.SelectSingleNode('//somenode/#SharedPassKey')
$attr.'#text' = $attr.'#text'.Replace('+', '.')
$xml.Save($xmlfile)
Make the XPath expression for selecting the attribute as specific as it needs to be. That will allow you to replace a + with a . in just that attribute.

Related

Why it doesn't change the content inside the file?

this code supposed to look for all demo.txt in the disk and change them from "demo" to "demodemo997182625" and then check if the file has changed or not
$found = 0;
$notfound = 0;
foreach ($file in Get-ChildItem -Path C:\ -Recurse -Filter demo.txt -ErrorAction SilentlyContinue )
{
(Get-Content $file).Replace("demo","demo997182625") | Set-Content $file
$x = (Get-Content $file).contain("demo997182625")
if($x -eq $null){
$found = 1 + $found;
}
else {
$notfound = 1 + $notfound;
}
}
Write-Host "Changed" $found;
Write-Host "Not Changed" $notfound;
A few remarks on your code:
the .Replace() and .Contains() string methods work case-sensitive, so .Replace("demo","demo997182625") won't find and replace "Demo". To have it work case-insensitively, use the -replace operator instead.
updated files can be reprocessed by Get-ChildItem, unless you have that part finish completely first. The easiest way to do that is by enclosing it between brackets
I would only save the file if there was something updated (i.e. the new value was found after -replace), otherwise leave it be
Get-ChildItem returns both FileInfo and DirectoryInfo objects. Since you are interested in changing files only, append the -File switch
best use the FullName property of the found file on the Get- and Set-Content cmdlets instead of the whole FileInfo object
$found = $notfound = 0
# surround the Get-ChildItem line with brackets, so it will finish before iterating on the result
# otherwise, it could reprocess files that were allready updated
foreach ($file in (Get-ChildItem -Path 'C:\' -Recurse -Filter 'demo.txt' -File -ErrorAction SilentlyContinue)) {
# -replace uses regex, so surround the search string with '\b' boundary markers in order to do a whole-word search
$content = (Get-Content -Path $file.FullName -Raw -Force) -replace '\bdemo\b', 'demo997182625'
# test if the content now has the new value (again, use '\b' boundary markers)
if ($content -match '\bdemo997182625\b') {
# save the updated file
$content | Set-Content -Path $file.FullName -Force
$found++
}
else {$notfound++}
}
Write-Host "Changed: $found"
Write-Host "Not Changed: $notfound"
P.S. If your search string contains characters that in regex have special meaning (see table below), you need to escape these with a backslash when using regex operators -replace and -match.
Special Characters in Regex
Char
Description
Meaning
\
Backslash
Used to escape a special character
^
Caret
Beginning of a string
$
Dollar sign
End of a string
.
Period or dot
Matches any single character
|
Vertical bar or pipe symbol
Matches previous OR next character/group
?
Question mark
Match zero or one of the previous
*
Asterisk or star
Match zero, one or more of the previous
+
Plus sign
Match one or more of the previous
( )
Opening and closing parenthesis
Group characters
[ ]
Opening and closing square bracket
Matches a range of characters
{ }
Opening and closing curly brace
Matches a specified number of occurrences of the previous

parse the contents of a variable powershell

I would like to parse the contents of a variable to display only the path of the .exe without the startup options
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, DisplayName, State, Pathname, StartName
foreach ($service in $services) {
$Service.Pathname
}
Result
C:\WINDOWS\system32\svchost.exe -k UnistackSvcGroup
C:\WINDOWS\System32\svchost.exe -k LocalServiceNetworkRestricted -p
Thank you in advance for your feedback
To offer a concise alternative, which also trims enclosing " chars. from command lines with executables containing spaces and recognizes argument-less unquoted executable paths containing spaces without enclosing "...":
((Get-CimInstance win32_service).PathName -match '\.exe\b').ForEach({
if (Test-Path -LiteralPath $_) { # Check if the entire string is an unquoted executable path alone.
$_
} else {
$_ -replace '^(?:"(.+?)"|([^ ]+)).*', '$1$2'
}
})
The above uses:
member-access enumeration to get all .PathName values simply by using property access at the collection level.
the regex-based -match operator to limit the values to those containing the verbatim substring .exe (regex metachar. . must be escaped as \.) at a word boundary (\b).
the .ForEach() array method, for faster processing (than with the ForEach-Object cmdlet) of values already in memory.
The Test-Path is used to check if the entire command line refers to a single executable, possibly with spaces in its path; if so, the entire string ($_) is passed out.
Otherwise, the command line is assumed to include arguments and/or an executable path enclosed in embedded "...".
the regex-based -replace operator with a regex that matches the entire input string and replaces it only with the substring of interest, the executable file path, via capture groups ((...)).
Note: The regex works whether or not the executable path ends in .exe, but given that the command line by definition does contain .exe, simpler solutions are possible, as shown in Santiago's helpful answer and elkenyo's helpful answer, though the latter should be made more robust:
('"C:\Program Files\bar2.exe" -baz' -split '(?<=\.exe\b)')[0].Trim('"')
For an explanation of how the -replace regex and substitution work, see this regex101.com snippet.
A simple demonstration:
$commandLines = 'c:\tools\foo1.exe',
'c:\tools\foo2.exe -bar',
'"C:\Program Files\bar1.exe"',
'"C:\Program Files\bar2.exe" -baz'
$commandLines -replace '^(?:"(.+?)"|([^ ]+)).*', '$1$2'
The above yields:
c:\tools\foo1.exe
c:\tools\foo2.exe
C:\Program Files\bar1.exe
C:\Program Files\bar2.exe
This should work for you;
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} | select Name, DisplayName, State, Pathname, StartName
foreach ($service in $services) {
($Service.PathName -Split ".exe")[0]
# to include .exe:
# "$(($Service.PathName -Split ".exe")[0]).exe"
}
You could use a Select-Object calculated property like so:
$services = Get-WmiObject win32_service | ?{$_.PathName -like '*.exe*'} |
Select-Object Name, DisplayName, State,
#{n='PathName';e={[regex]::Match($_.PathName,'.*\.exe"?').Value}}
I'm using "? at the end of the regex because some Paths are enclosed with ", this will include it.
If you want to exclude the enclosing " you could use (though there is probably a better way):
[regex]::Match($_.PathName,'.*\.exe').Value.TrimStart('"')
Link to test it and explanation: https://regex101.com/r/BFlhVv/1
Then if you do $services.PathName it would show you all the paths without arguments.

Rename multiple files in a directory by looping through a list of names in a .txt / .csv file for a partial match

I'm trying to automate renaming of many multiple files in a Windows 7 directory. I need to search a source index file (.txt or .csv which is a list of extended file names) and, where there is a partial match to the original file name, copy the first 12 characters (of the relevant string in the index file) and rename the original file accordingly (and preserving original file extension).
e.g.
(a) Files currently in the Windows directory are named as follows (hundreds of files):
23456abc.doc
76543cab.doc
92837bca.doc
(b) Values in the .txt/.csv file as follows (hundreds of values - NOTE: these do not have file extensions):
BetterName1.RandomText1.23456abc.MoreRandomText1
BetterName2.RandomText2.76543cab.MoreRandomText2
BetterName3.RandomText3.92837bca.MoreRandomText3
(c) Desired Result is for the files to be auto renamed as follows:
[by searching for filename in (a) within the list of values in (b) and, where there is a match, returning the first 12 characters as the new filename whilst preserving the original file extension]
BetterName1.doc
BetterName2.doc
BetterName3.doc
NOTE: My preference is to use an Index file for the look-up that is in .txt format. However in need I can also use a .csv
I have never used PowerShell before and am new to Windows batch scripting. I have searched around and tried to cobble together snippets of code into a Windows batch script (also tried a PowerShell script) to achieve this but my knowledge in this area is seriously lacking so unfortunately I'm still struggling away at square one.
Any assistance would be greatly appreciated. Thank you in advance.
P.S. Here is a PowerShell script that I tried to get working but to no avail.
$fdPath = 'C:\TEST\Data'
$sourcelistFiles = Get-ChildItem -Path $FDPATH\*.txt | ForEach-Object {$_.user } FullName
$findReplaceList = Import-Csv -Path $FDPATH\AllNames.csv
$totalitems = $sourcelistFiles.count
$currentrow = 0
foreach ($sourcelistFile in $sourcelistFiles)
{
$currentrow += 1
Write-Progress -Activity "Processing record $currentrow of $totalitems" -Status "Progress:" -PercentComplete (($currentrow / $totalitems) * 100)
[string] $txtSourceListFile = Get-Content $sourcelistFile | Out-String
ForEach ($findReplaceItem in $findReplaceList)
{
$txtSourceListFile = $txtSourceListFile -replace "$($findReplaceitem.FindString)", "$($findReplaceitem.ReplaceString)"
}
$txtSourceListFile | Set-Content ($sourcelistFile)
-NoNewLine
}
$FDPATH = 'C:\TEST\Data'
foreach($obj in (Import-Csv -Path $FDPATH\AllNames.csv)){
foreach($thing in $(gci -Path $FDPATH\*.txt)){
if("123.hash.avocado" -match $thing.basename){$ret = $thing.fullname}
}
$stuff = $obj -split "."
ren -Path $ret -NewName $stuff[0]
}
See if this works, it iterates through the csv then iterates through the directory to see if the directory's name is in the csv's line that is being iterated, then sets a variable to be the fullname of the file and renames it to the first name before the period.
Import-CSV LISTA.csv -Header newFileName | % { Copy-Item -Path archivo_convert.pdf -Destination "$($_.newfilename).pdf" }

Powershell out username from win32_userprofile localpath

Ok, I am trying to pull only the username out of the localpath properties of the following command:
(Get-WmiObject -ComputerName $ComputerName -Class win32_UserProfile -Filter "localPath like 'c:\\Users\\%'" | sort localpath).localpath.replace('C:\Users\','')
This replaces the c:\Users\ that I don't need. However, at the end of some of the names, there is a .000 or the domain name. For the life of me, I can't not remember how to remove all items after a character. If you know a better way to do this please let me know.
Output:
sfricks
sguess.001
sholcombe
srabanal.000
srainey.OPR.000
ssanders
sspecht.OPR
I can use the split option, however, it takes it from a single line of code to 3+.
$Return = #()
$usernames = (Get-WmiObject -ComputerName $ComputerName -Class win32_UserProfile -Filter "localPath like 'c:\\Users\\%'" -ErrorAction Stop | sort localpath).localpath.replace('C:\Users\','')
foreach ($user in $usernames) {
$Return += $user.Split('.')[0]
}
$Return
I am trying to find the workaround using -replace. I'm not too sure how to do it yet. Just need someone to point me in the right direction.
Where you need to catch variable text for replacement, you're probably better off using regular expressions and the -replace operator instead of the .replace() method. For your purposes, after doing the .replace('C:\Users\',''), use -replace '\..*$',''. The decomposition of the expression is as follows:
\. - Literal dot. The . is normally a special character meaning 'match anything';
the \ escapes it and removes the special meaning.
.* - Match any number of any character. The dot takes on its special character
meaning here, and the star says 'any number of the previous character'
$ - End of string.
There's a good cheat sheet for regular expressions at https://www.debuggex.com/cheatsheet/regex/pcre

Powershell: find/replace string using wildcard - syntax difficulty

I have the following text in a file named ".connectionInfo":
device name = "WTG001" {
address = "172.28.16.1";
port = 80;
timeout = 3;
rfs = true;
operatingSystem = "vxworks";
}
I have one file as the master (shown above) and wish to create multiple copies in multiple directories with this IP address incrementing by one. In directory "WTG001", I would have .connectionInfo with "172.28.16.1", in directory "WTG002", I would have .connectionInfo with "172.28.16.2", etc.
I have the following Powerscript file and would like to use a counter (IP_Counter) to increment the last digits of the IP address:
$folder="C:\work\Scripting";
$txtFile="C:\work\Scripting\TurbineConfig.txt";
$pattern="\d+.+";
get-content $txtFile | %
{
$IP_Counter = 1
if($_ -match $pattern)
{
Copy-Item -Path C:\work\Scripting\.connectionInfo -Destination "$folder\$_.vxworks";
(Get-Content C:\work\Scripting\.connectionInfo) |
Foreach-Object {$_ -replace '172.28.16.*','172.28.16.$IP_Counter'} |
Out-File "$folder\.connectionInfo";
$IP_Counter++;
...
}
}
I'm having problems with the proper syntax for the "-replace" attribute. I want to replace just the last 3 digits of the IP address with the value $IP_Counter. I think using '172.28.16.*' wildcard finds the last number in the IP but I cannot figure out the precise syntax for the replace string. Please help me identify what '172.28.16.$IP_Counter' should look like.
I can use that same knowledge to replace the "WTG001" text also.
You can use the following -replace expression:
Foreach-Object {$_ -replace '172\.28\.16\.\d+',"172.28.16.$IP_Counter"}
\d+ represents digits only so the semicolon at the end of the line and the quotes won't be replaced. You also need to use double quotes " around the replacement string to interpolate $IP_Counter
I hope there is also some bounds checking for $IP_Counter - in your example it looks like it would loop forever.

Resources