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
Related
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.
I have a directory of .txt files that look like this:
[LINETYPE]S[STARTTIME]00:00:00
[LINETYPE]P[STARTTIME]00:00:00
[LINETYPE]B[STARTTIME]00:59:00
[LINETYPE]C[STARTTIME]00:59:00
[LINETYPE]C[STARTTIME]00:59:30
[LINETYPE]S[STARTTIME]01:00:00
[LINETYPE]P[STARTTIME]01:00:00
[LINETYPE]B[STARTTIME]01:59:00
[LINETYPE]C[STARTTIME]01:59:00
[LINETYPE]C[STARTTIME]01:59:30
[LINETYPE]S[STARTTIME]02:00:00
I'd like to remove all occurrences of [LINETYPE]S except the first, which happens to always be 00:00:00 and on the first line, and then re-save the file to a new location.
That is, [LINETYPE]S[STARTTIME]00:00:00 must always be present, but the other lines that start with [LINETYPE]S need to be removed.
This is what I came up with, which works except it removes all [LINETYPE]S lines, including the first. I can't seem to figure out how to do that part after Googling for a while, so I'm hoping someone can point me in the right direction. Thanks for your help!
Get-ChildItem "C:\Users\Me\Desktop\Samples" -Filter *.txt | ForEach-Object {
Get-Content $_.FullName | Where-Object {
$_ -notmatch "\[LINETYPE\]S"
} | Set-Content ('C:\Users\Me\Desktop\Samples\Final\' + $_.BaseName + '.txt')
}
i couldn't figure out how to do this via a pipeline [blush], so i went with a foreach loop and a compound test.
# fake reading in a text file
# in real life, use Get-Content
$InStuff = #'
[LINETYPE]S[STARTTIME]00:00:00
[LINETYPE]P[STARTTIME]00:00:00
[LINETYPE]B[STARTTIME]00:59:00
[LINETYPE]C[STARTTIME]00:59:00
[LINETYPE]C[STARTTIME]00:59:30
[LINETYPE]S[STARTTIME]01:00:00
[LINETYPE]P[STARTTIME]01:00:00
[LINETYPE]B[STARTTIME]01:59:00
[LINETYPE]C[STARTTIME]01:59:00
[LINETYPE]C[STARTTIME]01:59:30
[LINETYPE]S[STARTTIME]02:00:00
'# -split [System.Environment]::NewLine
$KeepFirst = '[LINETYPE]S'
$FoundFirst = $False
$FilteredList = foreach ($IS_Item in $InStuff)
{
if ($IS_Item.StartsWith($KeepFirst))
{
if (-not $FoundFirst)
{
$IS_Item
$FoundFirst = $True
}
}
else
{
$IS_Item
}
}
$FilteredList
output ...
[LINETYPE]S[STARTTIME]00:00:00
[LINETYPE]P[STARTTIME]00:00:00
[LINETYPE]B[STARTTIME]00:59:00
[LINETYPE]C[STARTTIME]00:59:00
[LINETYPE]C[STARTTIME]00:59:30
[LINETYPE]P[STARTTIME]01:00:00
[LINETYPE]B[STARTTIME]01:59:00
[LINETYPE]C[STARTTIME]01:59:00
[LINETYPE]C[STARTTIME]01:59:30
at that point, you can send the new collection out to a file. [grin]
Try the following:
Get-ChildItem "C:\Users\Me\Desktop\Samples" -Filter *.txt |
Foreach-Object {
$count = 0
Get-Content $_.FullName |
Where-Object { $_ -notmatch '\[LINETYPE\]S' -or $count++ -eq 0 } |
Set-Content ('C:\Users\Me\Desktop\Samples\Final\' + $_.BaseName + '.txt')
}
The script block passed to Where-Object runs in the same scope as the caller, so variable $count can be directly updated.
The 1st line that does contain [LINETYPE]S is included, because $count is 0 at that point, after which $count is incremented ($count++); subsequent [LINETYPE]S are not included, because $count is then already greater than 0.
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.
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.
How to write script in powershell which finds given string in all files in given directory and changes it to given second one ?
thanks for any help,
bye
Maybe something like this
$files = Get-ChildItem "DirectoryContainingFiles"
foreach ($file in $files)
{
$content = Get-Content -path $file.fullname
$content | foreach {$_ -replace "toreplace", "replacewith"} |
Set-Content $file.fullname
}
If the string to replace spans multiple lines then using Get-Content isn't going to cut it unless you stitch together the output of Get-Content into a single string. It's easier to use [io.file]::ReadAllText() in this case e.g.:
Get-ChildItem | Where {!$_.PSIsContainer} |
Foreach { $txt = [IO.File]::ReadAllText($_.fullname);
$txt -replace $old,$new; $txt | Out-File $_}
Note with with $old, you may need to use a regex directive like '(?s)' at the beginning to indicate that . matches newline characters also.
I believe that you can get the list of all files in a directory (simple?). Now comes the replacement part. Here is how you can do it with power shell:
type somefile.txt | %{$_ -replace "string_to_be_replaces","new_strings"}
Modify it as per your need. You can also redirect the output to a new file the same way you do other redirection (using: >).
To get the list of files, use:
Get-ChildItem <DIR_PATH> -name