I have multiple text documents, each with multiple lines of information, and I'm trying to replace a single line of text within each document with text of my choosing. The single line of text that is to be replaced does not have a consistent length or set of characters across the multiple documents. Also, the placement of this line of text is not always located at the same place within the document. The only consistent factor here is the string directly above the line of text to be replaced is the same string across all documents - "Courier". I'm trying to use the word "Courier" as my reference point with which I'd replace the subsequent line of text with something of my choosing.
Any help would be greatly appreciated! Thanks in advance!
Below I have included the script that I've created so far; however, I am reaching the limits of my capability to complete this. Currently, the script executes successfully without errors, but the line I'm trying to replace does not get replaced - Instead, the text I'm looking to input as the replacement is entered below "Courier" and the text I don't need (that I'd like to be replaced) is moved down the document, now located directly under the new text I've entered in my script. Here's an example of what I get when I run my script in its current state:
Courier
Entry location 153
Sidewalk0156378
In this case, "Sidewalk0156378" is the old text that used to be directly under "Courier" before the script was ran, and it needs to be replaced. "Entry location 153" is the new text that should be taking the place of "Sidewalk0156378".
$path = "C:\temp"
if(!(Test-Path $path)){
New-Item -ItemType Directory -Force -Path $path
}
$currentCourier = "C:\Temp\currentCourier.txt"
$editCourier = "C:\Temp\editCourier.txt"
$newCourier = "C:\Temp\newCourier.txt"
Get-Content $currentCourier | ForEach-Object {
$_
if ($_ -match 'Courier') {
"Entry location"
}
} | Set-Content $editCourier
$oldEntry = Get-Content $editCourier
$rem = #()
#("Courier") | ForEach-Object {
$rem += $oldEntry[(($oldEntry | Select-String -Pattern "$_").LineNumber)..(($oldEntry | Select-String -Pattern "$_").LineNumber+1)]
}
Compare-Object $oldEntry $rem | Select-Object -ExpandProperty InputObject | Set-Content $newEntry
The following uses a switch to process the file line by line in combination with the -Wildcard parameter to match any line having the key word.
& {
$skipNext = $false
switch -Wildcard -File $currentCourier {
# if line contains Courier
'*Courier*' {
# output this line
$_
# set this variable to skip next line
$skipNext = $true
# output new value that replaces next line
'new value here'
# skip next conditions
continue
}
# if the bool was set in previous iteration
{ $skipNext } {
# set it to false again
$skipNext = $false
# and go next
continue
}
# else, output line as is
Default { $_ }
}
} | Set-Content $newCourier
Using the logic above with a hardcoded example:
$content = '
line 1
line 2
line 3 has Courier
line 4 should be replaced
line 5
' -split '\r?\n'
$skipNext = $false
switch -Wildcard ($content) {
'*Courier*' {
$_
$skipNext = $true
'Entry location 153'
continue
}
{ $skipNext } {
$skipNext = $false
continue
}
Default { $_ }
}
Output to the console would become:
line 1
line 2
line 3 has Courier
Entry location 153
line 5
Related
I have a Powershell script with XAML code (for display some windows and buttons)
I try to replace a value of all lines begin with $computer.selectedvalue and finnish by ",1" in a text file.
My text file content :
COMPUTER01,SITE01,PRINTER01,1
COMPUTER05,SITE02,PRINTER01,0
COMPUTER01,SITE03,PRINTER08,1
end with ,1 is the printer by default and ,0 is the secondary printer
Powershell code :
$PLPrinterArray = Get-Printer -ComputerName srvprint | select name
foreach ($PLPrinterArray in $PLPrinterArray) {
[void]$WPFCboPL.items.add(",PL,$($PLPrinterArray.Name)")
}
$LDAPComputerArray = Get-ADComputer -filter * -SearchBase"OU=Ordinateurs,DC=MyDC,DC=Mydomain,DC=org" -Properties name
foreach ($LDAPComputerArray in $LDAPComputerArray) {
[void]$WPFCboComputer.items.add($($LDAPComputerArray.Name))
}
$WPFBoutonDefaut.Add_Click({
#If (Get-Content C:\Users\User\desktop\test.txt
$($WPFCboComputer.selectedvalue)^ ",1$") {
#-replace ",1",",0"}
Add-Content -path "C:\Users\User\Desktop\test.txt"
-value"`n$($WPFCboComputer.selectedvalue)$($WPFCboPL.SelectedValue)$($WPFCboLN.SelectedValue),1"
})
Is it possible to do this and especially in a Button.Add.Click?
Thanks in advance ;-)
I suggest using a switch statement with the -Regex and -File options:
$WPFBoutonDefaut.Add_Click({
$file = 'C:\Users\User\desktop\test.txt'
# Process the input file line by line with regex matching,
# and collect the potentially modified lines in an array.
[array] $modifiedlines =
switch -Regex -File $file {
"^($($WPFCboComputer.SelectedValue),.+,)1$" { $Matches[1] + '0' }
default { $_ } # pass through
}
# Save the modified lines plus the new line back to the file.
# Note: Use -Encoding as needed.
Set-Content $file -Value (
$modifiedLines + "$($WPFCboComputer.SelectedValue)$($WPFCboPL.SelectedValue)$($WPFCboLN.SelectedValue),1"
)
})
i'm quite new to scripting (few weeks) and would be happy about your help.
I've a log-file (.txt) which needs to be changed.
The content is always the same:
random text
random text
successfull
error
random text
random text
random text
error
...
I would like to remove the line containing the word "error", but only if the line above contains the word "successfull".
So far I managed to get all the matching strings out of the File and am able to replace them, but I lose the rest of the text in the process:
get-content "D:\test.txt" | select-string -pattern "error" -context 1,0 | Where-Object {"$_" -match "successfull" } | %{$_ -replace "error.*"} | Out-File "D:\result.txt"
I would really appreciate your help here.
You can use some conditional logic (if statements) to achieve the goal:
$successful = $false
Get-Content d:\test.txt | Foreach-Object {
if ($_ -match "successfull") {
$successful = $true
$_
}
elseif ($_ -match "error" -and $successful) {
$successful = $false
}
else {
$_
$successful = $false
}
}
Since we are piping the Get-Content result into Foreach-Object, $_ becomes the current line being processed (each line is processed one by one). If a line contains successfull, then we mark $successful as $true and still output that line ($_). If the line contains error, then we will only output it if $successful is $false. Anytime we reach a line that does not contain succcesfull, we mark $successful as $false.
No deletion is actually occurring as it is merely not displaying error lines when the conditions are met.
I have a text file in which it has lots of headings and a few sentences below it.
I have wanted to search for the heading and if the heading is available I want to copy sentences below the heading till next heading.
Is it possible in PowerShell please help me I tried
$linenumber= Get-Content "C:\Users\KSYEDSU\Documents\temp\4491309.txt" | select-string $search
Select-String $string $dataRead -Context 1, $linenumber| % { $_.Context.PostContext } | out-file "C:\Users\KSYEDSU\Documents\temp\Results.txt"
But it is throwing an error telling it is expecting interger
$linenumber= Get-Content "C:\Users\KSYEDSU\Documents\temp\4491309.txt" | select-string $search
Select-String $string $dataRead -Context 1, $linenumber| % { $_.Context.PostContext } | out-file "C:\Users\KSYEDSU\Documents\temp\Results.txt"
ex:
Heading A
1234
34545
13213
Heading B
So I will search for Heading A and if it is available then start copying from 1234... till 13213.
Select-String will find the string inside your text but does not return the position as int. You could loop trough your file and look manueally for the heading and collect the data between.
#Get file content as string array
[System.String[]]$FileContent = Get-Content -Path 'C:\Users\KSYEDSU\Documents\temp\4491309.txt'
#For each line in the file
for ($i = 0; $i -lt $FileContent.Count; $i ++)
{
#If the line equals your start header
if ($FileContent[$i] -eq 'Heading A')
{
$i ++ #Get the next line
#Return line until end header appears
while ($FileContent[$i] -ne 'Heading B')
{
$FileContent[$i] #Return line
$i ++ #Get next line
}
}
}
You could use regex to do this, so you don't have to loop through all lines in the text file:
$headingStart = 'Heading A'
$headingEnd = 'Heading B'
# get the file content in one string, including all newline characters
$content = Get-Content "C:\Users\KSYEDSU\Documents\temp\4491309.txt" -Raw
# since the real headings may contain characters that have special meaning in regex, we escape them
$regex = '(?s)^{0}\s+(.*)\s{1}' -f [regex]::Escape($headingStart), [regex]::Escape($headingEnd)
if ($content -match $regex) {
$matches[1] | Out-File -FilePath "C:\Users\KSYEDSU\Documents\temp\Results.txt"
}
else {
Write-Host "No text found between $headingStart and $headingEnd"
}
Using your example, the resulting file will contain:
1234
34545
13213
You can use a switch statement combined with the -Regex and -File flags.
$insideHeaders = $false
. {switch -Regex -File "C:\Users\KSYEDSU\Documents\temp\4491309.txt" {
'Heading A' { $insideHeaders = $true }
'Heading B' { return }
default {
if ($insideHeaders) { $_ }
}
}} | Out-File "C:\Users\KSYEDSU\Documents\temp\Results.txt"
Explanation:
Each value between single quotes is a regex string. You will have to backslash (\) escape any special regex characters, which can be done automatically using [regex]::Escape(string).
When the bottom header (Heading B in this case) is reached, the return statement will exit the switch statement.
All lines not matching one of the headers will trigger the default condition. The default condition will only output a line if the first header has been found.
$data = Select-String -Path $selectedDirectory\$sqlFile -Pattern "GRANT" -Context 5,5
I want to use PowerShell to read .SQL files and we want to make sure that a user isn't using GRANT or DROP or DELETE without a human reviewing the file to see if it's okay.
My 1 line only is looking at GRANT but I don't think it's working.
If the keywords are in the file, I want to display a portion of the text on the screen +/- 5 lines of where the offending text was found.
Is there a way to change the color of the text for the specific line that has the offending search criteria (all other lines will be shown as default)
If you want colors displayed to the console, you will need to utilize Write-Host.
$data = Select-String -Path $selectedDirectory\$sqlFile -Pattern "GRANT|DROP|DELETE" -Context 5,5
$data | Foreach-Object {
$_.Context.Precontext
Write-Host $_.Line -ForeGroundColor Cyan
$_.Context.Postcontext
}
I'll give it a shot.
This function takes a file, searches for those keywords, and then prints +/- 5 lines. It's easy enough that I'm sure you know how it works and how to modify it. You can find the reference for the matchinfo class (returned by Select-String( here.
Function Get-SQLForbiddenWords ($sqlDataFile) {
$data = Select-String -Path $sqlDataFile -Pattern "GRANT|DROP|DELETE"
Foreach ( $line in $data) {
$lineNumberS = $line.LineNumber - 5
$lineNumberE = $line.LineNumber + 5
echo ('Bad Command Detected: {0}' -f $line.line)
(Get-Content $sqlDataFile)[$lineNumberS..$lineNumberE]
echo "`n"
}
}
It was pretty fun. Output:
Bad Command Detected: DROP
this is the sixth line
GRANT
this is the seventh line
this is the eighth line
DROP
this is the ninth line
this is the tenth linet
this is the eleventh line
this is the twelfbthfbth line
For starters, "GRANT" should be in quotes to denote a string.
If you notice, $line = Select-String -Pattern "Grant" will return an object.
If you look at the properties of the object using Get-Member, one of them is LineNumber
If you have read the contents of your file using $data = Get-Content File.sql or any something similar, you will have your data as an array object. Now you can now use this line number to extract the +/- 5 lines as you wish like $data[50..60]. This will show output lines from 50th to 60th line. You can easily replace the 50 and 60 with your variables.
Another way is to use the oss function (=Out-String -Stream).
Select-String "\b(GRANT|DROP|DELETE)\b" .\test.txt -Context 5 | oss | foreach { Write-Host $_ -ForegroundColor ("White","Cyan")[$_[0] -eq '>'] }
The following may make it a bit more readable.
Select-String "\b(GRANT|DROP|DELETE)\b" .\test.txt -Context 5 | Out-String -Stream | foreach {
if(-not $_) { return }
$fileName,$lineNumber,$line = $_.Split(":", 3)
$color = if($_.StartsWith(">")) { "Cyan" } else { "White" }
Write-Host $fileName $lineNumber.PadLeft(3, "0") $line -ForegroundColor $color -Separator " "
}
Sorry for the long post. Wanted to explain in detail.
I'm trying to achieve three things and very nearly there. Probably a school boy error. Tried nested loops etc but could not get it working.
It appears I need to split the $resultszone array.
Search for specific areas within file. In the example below, it's the section after \zones\, test1.in-addr.arpa, test2.in-addr.arpa, etc.
Copy and trim content after area found. In first example, just test1.in-addr.arpa (Removing the beginning "\" and end "]"
Add a line including the area found (example test1.in-addr.arpa), to below the line containing "Type".
Example source file:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\test1.in-addr.arpa]
"Type"=dword:00000001
"SecureSecondaries"=dword:00000002
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\test2.in-addr.arpa]
"Type"=dword:00000001
"SecureSecondaries"=dword:00000002
Expected result
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\test1.in-addr.arpa]
"Type"=dword:00000001
"DatabaseFile"="test1.in-addr.arpa.dns"
"SecureSecondaries"=dword:00000002
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\test2.in-addr.arpa]
"Type"=dword:00000001
"DatabaseFile"="test2.in-addr.arpa.dns"
"SecureSecondaries"=dword:00000002
I've managed to achieve all using the code below, except it adds a line including all the results from area found, for every section.
For example:
"DatabaseFile"="test1.in-addr.arpa test2.in-addr.arpa
#Get FileName Path
$FileName = "C:\temp\test.conf"
#Search for pattern in file and trim to desired format.
#Store array in $resultsZone
$resultszone = Select-String -Path "c:\temp\test.conf" -Pattern '(?<=Zones)(.*)' |
select -expa matches |
select -expa value |
% { $_.Trim("\]") }
# Get contents of file
(Get-Content $FileName) | ForEach-Object {
#Start Loop to find area of File to insert line
$_ # send the current line to output
if ($_ -match "type") {
#Add Line after the selected pattern (type) including area trimmed
"""DatabaseFile" + """=""" + $resultszone + ".dns" + """"
}
} | Set-Content C:\temp\elctest.conf
I think this achieves what you're looking for:
$FileName = "C:\Temp\test.conf"
Get-Content $FileName | ForEach-Object {
$Match = ($_ | Select-String -pattern '(?<=Zones\\)(.*)').matches.value
if ($Match) { $LastMatch = ($Match).Trim("\]") }
$_
if ($LastMatch -and $_ -match 'type') {
"""DatabaseFile" + """=""" + $LastMatch + ".dns" + """"
}
} | Set-Content C:\Temp\elctest.conf
The fix is that we do the Select-String within the loop against each line, and then store when it matches in another variable (named $LastMatch) so that when we reach the line where we want to insert the previous time it matched, we have it.