Read text file and check for value in a specific position and change when true - powershell

I need to loop through multiple text files and check for a $ value in position 7 on each line of text and replace it with an * when found. But ONLY when it is in position 7. I do not want to change it if it is found in other positions. This is as far as I have gotten. Any help would be greatly appreciated.
Get-ChildItem 'C:\*.txt' -Recurse |
foreach $line in Get-Content $_ {
$linePosition1to5 = $line.Substring(0,6)
$linePosition7 = $line.Substring(6,1)
$linePositionRest = $line.Substring(8)
if($linePosition7 = "$"){
$linePosition7 = "*"
}
$linePosition1to5 + $linePosition7 + $linePositionRest |
Set-Content $_
}

Is there something that doesn't work in your example, or just that all the nested substrings are annoying to work with?
I'd use regex for this one. e.g.
$Lines = Get-Content -Path "C:\examplefile.txt" -raw
$Lines -replace '(?m)(^.{6})\$', '$1*'
To explain the regex:
?m indicates that it's multiline, required because I used raw get-content rather than pulling an array. Array would work too, just needs a loop like you did.
^.{6} line start plus any 6 characters (capture group 1)
$ escaped dollar character
$1* Capture group 1 left as is, dollar replaced with *, anything else not captured and therefore left untouched.

Thanks for code and the explanation. I realized that I left out the -raw option and it did work. Putting it back in it seems to add a line to the end of each file. Unless you can think of reason why I shouldn't I was going to leave it out.
Get-ChildItem 'C:\TEST\*.txt' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '(?m)(^.{6})\$', '$1*'}) |
Set-Content $_
}

Related

Replace a non-unique line of text under a unique line of text in a text file using powershell

I have the following txt file.
[AppRemover]
Enable=0
[CleanWipe]
Enable=0
[RerunSetup]
Enable=0
How do I change the Enable=0 to Enable=1 under [CleanWipe] only?
Below is how I plan on using the code with my file.
$Path = C:\temp\file.txt
$File = Get-Content -Path $Path
# Code to update file
$File | Out-File $Path
You can use -replace to update the value if it is 0.
$Path = C:\temp\file.txt
(Get-Content $Path -Raw) -replace "(?<text>\[CleanWipe\]\r?\nEnable=)0",'${text}1' |
Set-Content $Path
Using a module that parses INI files will be the best solution though. I'd recommend trying PsIni.
Explanation:
The -Raw switch reads the file contents as a single string. This makes it easier to work with newline characters.
-replace performs a regex match and then replace. Below is the regex match breakdown.
(?<text>) is a named capture group. Anything matched within that capture group can be recalled in the replace string as '${text}'.
\[CleanWipe\] is a literal match of [CleanWipe] while escaping the [] characters with \.
\r? is optional carriage return
\n is the newline character
Enable= is a literal match
0 is a literal match
The replace string is the capture group contents and 1 when a match exists. Technically, a capture group is not needed if you want to use a positive lookbehind instead. The positive lookbehind assertion is (?<=). That solution would look like the following:
$Path = C:\temp\file.txt
(Get-Content $Path -Raw) -replace "(?<=\[CleanWipe\]\r?\nEnable=)0",'1' |
Set-Content $Path
The problem with the -replace solutions as they written is they will update the file regardless of a change actually being made to the contents. You would need to add an extra comparison to prevent that. Other issues could be extra white space on any of these lines. You can account for that by adding \s* where you think those possibilities may exist.
Alternative With More Steps:
$file = Get-Content $Path
$TargetIndex = $file.IndexOf('[CleanWipe]') + 1
if ($file[$TargetIndex] -match 'Enable=0') {
$file[$TargetIndex] = 'Enable=1'
$file | Set-Content $Path
}
This solution will only update the file if it meets the match condition. It uses the array method IndexOf() to determine where [CleanWipe] is. Then assumes the line you want to change is in the next index.
IndexOf() is not the only way to find an index. The method requires that your line match the string exactly. You can use Select-String (case-insensitive by default) to return a line number. Since it will be a line number and not an index (indexes start at 0 while line numbers start at 1), it will invariably be the index number you want.
$file = Get-Content $Path
$TargetIndex = ($file | Select-String -Pattern '[CleanWipe]' -SimpleMatch).LineNumber
if ($file[$TargetIndex] -match 'Enable=0') {
$file[$TargetIndex] = 'Enable=1'
$file | Set-Content $Path
}

Replacing String without replacing whole content of file powershell

Trying to edit this line of a file ("VoIP.Enabled "1"). I wanna change the 1 to a zero. When I change it with
$dewprefs = Get-Content .\dewrito_prefs.cfg
$dewprefs | Select-String "VoIP.Enabled" | ForEach-Object {$_ -replace "1","0"} | Set-Content .\dewrito_prefs.cfg}`
However when I use this script, it removes 100 other lines, edits the right line, then deletes everything else, just leaving the line I wanted to edit.
Any help on this matter would be highly appreciated
Select-String acts as a filter: that is, the input it is given is only passed out if it matches a pattern.
Therefore, only the line of interest is written to the output file.
Do not use Select-String if all input lines - though possibly modified - should be passed through; use only ForEach-Object, and conditionally modify each input line:
$dewprefs = Get-Content .\dewrito_prefs.cfg
$dewprefs |
ForEach-Object { if ($_ -match 'VoIP\.Enabled') { $_ -replace '1', '0' } else { $_ } } |
Set-Content .\dewrito_prefs.cfg
$_ -match 'VoIP\.Enabled' now does what Select-String did in your original command: it matches only if the input line at hand contains literal VoIP.Enabled (note how the . is escaped as \. to ensure that is treated as a literal in the context of a regular expression).
Note how both branches of the if statement produce output:
$_ -replace '1', '0' outputs the result of replacing all instances of 1 in the input line with 0
$_ simply passes the input line through as-is.
Most likely you could replace the if statement with a single -replace expression, however, and, assuming that the file is small enough to be read as a whole (quite likely, in the case of a configuration file), you can use a variant of Stu's helpful simplification.
Taking full advantage of the fact that -replace supports regexes (regular expressions), the code can update lines based on a key name such as VoIP.Enabled only, without needing to know that key's current value.
$key = 'VoIP.Enabled'
$newValue = '1'
# Construct a regex that matches the entire target line.
$regex = '^\s*' + [regex]::Escape($key) + '\b.*$'
# Build the replacement line.
$modifiedLine = "$key $newValue"
(Get-Content .\dewrito_prefs.cfg) -replace $regex, $modifiedLine | Set-Content .\dewrito_prefs.cfg
Note that writing the output back to the input file only works because the input file was read into memory as a whole, up front, due to enclosing the Get-Content call in (...).
This will work too, with PowerShell v3+, and is a little more succinct:
(Get-Content .\dewrito_prefs.cfg).replace('"VoIP.Enabled "1"', '"VoIP.Enabled "0"') |
Set-Content .\dewrito_prefs.cfg
Your quotes are a little strange (3 double quotes in total?), I've mimicked what you've asked, however.

Pulling a substring for each line in file

Using Powershell, I am simply trying to pull 15 characters starting from the 37th position of any record that begins with a 6. I'd like to loop through and generate a record for each instance so it can later be put into an output file. But I seem to not be hitting the correct syntax just to return the 15 characters I know I am missing something obvious. Been at this for a while. Here is my script:
$content = Get-Content -Path .\tmfhsyst*.txt | Where-Object { $_.StartsWith("6") }
foreach ($line in $contents)
{
$val102 = $line.substring(36,15)
}
write-output $val102
Just as Bill_Stewart pointed out, you need to move your Write-Output line inside the ForEach loop. A possibly better way to do it would just be to pipe it:
Get-Content -Path .\tmfhsyst*.txt | Where-Object { $_.StartsWith("6") } | foreach{$_.substring(36,15)}
That should give you the output you desired.
Using Substring() has the disadvantage that it will raise an error if the string is shorter than start index + substring length. You can avoid this with a regular expression match:
(Get-Content -Path .\tmfhsyst*.txt) -match '^6.{35}(.{15})' | % { $matches[1] }

Replace ^M with <space> in all lines of a file

I have a log file with ^M embedded throughout. I would like to replace the ^M with a single space.
I have tried variations on this:
(Get-Content C:\temp\send.log) | Foreach-Object {$_ -replace "^M", ' '} | Set-Content C:\temp\send.out
The output file contains a newline where each ^M had been, not at all what I was looking for...
The problem I am trying to solve involves examining the last $cnt lines of the file:
$new = Get-Content $fn | Select-Object -Last $cnt;
$new
When I display $new, the ^M are interpreted as CR/LF.
How can I remove/replace the ^M? Thanks for any pointers....
Sounds like ^M is not being replaced by your -replace method, it's likely the replace method is trying to replace capital letter M at the beginning of the string (^). Upon opening the file, ^M is then being interpreted as a carriage return.
Perhaps try replacing the carriage returns (^M) before displaying the contents:
(Get-Content C:\temp\send.log) |
Foreach-Object {$_ -replace "`r", ' '} |
Set-Content C:\temp\send.out
or
$new = Get-Content $fn | Select-Object -Last $cnt;
$new.replace("`r"," ")
Could this be as simple as escaping the ^ character? If you only need the last $count lines of the file you can use the -Tail parameter on Get-Content. Depending if you need to match ^M as case sensitive you might opt for -creplace instead of -replace.
Get-Content $inputfile -Tail $count | ForEach-Object { $_ -creplace '\^m',' ' } | Set-Content $outputfile
This isn't an answer, but since you asked for a few pointers, this might help set things straight.
Try this:
$new = Get-Content $fn | Select-Object -Last $cnt;
$new
$new.gettype()
$new[0].gettype()
I expect you're going to see that $new is an array of objects, and that $new[0] is a string. I'm going to suggest that $new[0] doesn't contain CR or LF or CRLF or anything like that. And I'm going to suggest that, when you ask for the display of $new in its entirety, what you are getting is each string ($new[0] followed by $new[1] ...) with CRLF inserted as a separator.
If I'm right, replacing CR or CRLF with space isn't going to do you any good at all. It's the CRLFs that are being inserted on output to a file that are preventing you from succeeding.
This is as far as I got towards solving your problem.

Find and Replace character only in certain column positions in each line

I'm trying to write a script to find all the periods in the first 11 characters or last 147 characters of each line (lines are fixed width of 193, so I'm attempting to ignore characters 12 through 45).
First I want a script that will just find all the periods from the first or last part of each line, but then if I find them I would like to replace all periods with 0's, but ignore periods on the 12th through 45th line and leaving those in place. It would scan all the *.dat files in the directory and create period free copies in a subfolder. So far I have:
$data = get-content "*.dat"
foreach($line in $data)
{
$line.substring(0,12)
$line.substring(46,147)
}
Then I run this with > Output.txt then do a select-string Output.txt -pattern ".". As you can see I'm a long ways from my goal as presently my program is mashing all the files together, and I haven't figured out how to do any replacement yet.
Get-Item *.dat |
ForEach-Object {
$file = $_
$_ |
Get-Content |
ForEach-Object {
$beginning = $_.Substring(0,12) -replace '\.','0'
$middle = $_.Substring(12,44)
$end = $_.Substring(45,147) -replace '\.','0'
'{0}{1}{2}' -f $beginning,$middle,$end
} |
Set-Content -Path (Join-Path $OutputDir $file.Name)
}
You can use the powershell -replace operator to replace the "." with "0". Then use substring as you do to build up the three portions of the string you're interested in to get the updated string. This will output an updated line for each line of your input.
$data = get-content "*.dat"
foreach($line in $data)
{
($line.SubString(0,12) -replace "\.","0") + $line.SubString(13,34) + ($line.substring(46,147) -replace "\.","0")
}
Note that the -replace operator performs a regular expression match and the "." is a special regular expression character so you need to escape it with a "\".