PowerShell - Find next entry of KeywordB after finding first entry of keywordA

80 views Asked by At

I'm looking to use Powershell to automatically tweak some 'Max' values in a text configuration file. The configuration file looks something like this (but contains around 200 entries).

ChannelName          = ItemA
Min                  = 0.0
Max                  = 100.0
Units                = Deg C

ChannelName          = ItemB
Units                = Deg C
Min                  = 0.0
OtherItem1           = 123
OtherItem2           = 456

ChannelName          = ItemC
Units                = Deg C
Min                  = 0.0
Max                  = 100.0

It should be noted that,

  • Whilst all ChannelNames should each have an associated 'Max' entry with them, some may not if people have incorrectly manually edited this file in the past
  • The 'Max' entry is not always in the same place after the channel name so we are unable to count down X lines after finding the channel name we are interested in

I appreciate that I have not included some proper example code but as a newbie I can't get anything to work and hence reaching out for help. I would like the code to work as follows,

EXAMPLE TASK: Set 'Max' value of 'ItemB' to a value of 200

Get-Content "C:\ConfigFile.txt"
Scan down text file looking for 'ItemB'
Error message if Item B not found and exit
If ItemB is found, continue down the list until either 'ChannelName' or 'Max' is found
If a 'ChannelName' entry is found first, 'ItemB' is missing its 'Max' entry - Error message and exit
If a 'Max' entry is found first, set 'Max' to equal 200

We need to ensure proper error checking is in place to ensure the 'Max' value of ItemC does not get incorrectly changed to 200 as the 'Max' value of ItemB is missing in this example.

1

There are 1 answers

6
Theo On BEST ANSWER

This is one approach.

Read the file in as single, multiline string and split on the empty newlines to get an array of 'Channel' textblocks.

Loop through the blocks and if the block is for Channel 'ItemB', check if there is a Max value. If not, add it to the block.

Finally, join the textblocks with two newlines again and write to file

$channelNameToUpdate = 'ItemB'  # or 'Speed' or 'SpeedAverage' or..
# split on two (or more) newlines and discard any block that is empty or whitespace-only
$data = (Get-Content -Path 'D:\Test\theconfigfile.txt' -Raw) -split '(\r?\n){2,}' | Where-Object {$_ -match '\S'}
for ($i = 0; $i -lt $data.Count; $i++) {
    if ($data[$i] -match "(?m)^ChannelName\s+=\s*$channelNameToUpdate\s*$") {
        if ($data[$i] -notmatch '(?m)^Max') {
            # no Max.. line, so add it
            $data[$i] += "`r`nMax                  = 200.0"
        }
        else {
            # replace the existing Max.. line
            $data[$i] = $data[$i] -replace '(?m)^Max.*$', "Max                  = 200.0"
        }
    }
}

# join the textblocks with a double newline and overwrite the original file
# -PassThru will show the result on screen
$data -join "`r`n`r`n" | Set-Content -Path 'D:\Test\theconfigfile2.txt' -PassThru -Force

Output:

ChannelName          = ItemA
Min                  = 0.0
Max                  = 100.0
Units                = Deg C

ChannelName          = ItemB
Units                = Deg C
Min                  = 0.0
OtherItem1           = 123
OtherItem2           = 456
Max                  = 200.0

ChannelName          = ItemC
Units                = Deg C
Min                  = 0.0
Max                  = 100.0

If you want to add a Max value everywhere it is missing in the file, just change the for loop to

for ($i = 0; $i -lt $data.Count; $i++) {
    if ($data[$i] -notmatch '(?m)^Max') {
        $data[$i] += "`r`nMax                  = 200.0"
    }
}

Regex details:

(?m)^ChannelName\s+=\sItemB\s$

(?m)            Match the remainder of the regex with the options: ^ and $ match at line breaks (m)
^               Assert position at the beginning of a line (at beginning of the string or after a line break character)
ChannelName     Match the characters “ChannelName” literally
\s              Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
   +            Between one and unlimited times, as many times as possible, giving back as needed (greedy)
=               Match the character “=” literally
\s              Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
   *            Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
ItemB           Match the characters “ItemB” literally
\s              Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
   *            Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
$               Assert position at the end of a line (at the end of the string or before a line break character)

(?m)^Max

(?m)           Match the remainder of the regex with the options: ^ and $ match at line breaks (m)
^              Assert position at the beginning of a line (at beginning of the string or after a line break character)
Max            Match the characters “Max” literally