Samer Aamar
Samer Aamar

Reputation: 1408

using regex in powershell script to replace value of a key/value tags

using powershell script, I need to update some values inside an xml file. The xml is a list of keys and values.

More specifically i need to look for the key "servername" and update the relevant value tag with the $myservername. Also to look for the key "serverport" and update the relevant value tag with $myport.

how can this be done using powershell scripting?

i thought of using simple regex replace command as follows

$myservername = "some-server"
$myport = "8888"
$localFile = "C:\_Temp\Files\20190822115503\Manifest.xml"
$content = (Get-Content $localFile) 


$newcontent = $content -replace '"<Key>servername</Key>\s*<Value>(value1)</Value>"', $myservername


$newcontent = $content -replace '"<Key>serverport</Key>\s*<Value>(value1)</Value>"', $myport

The following is just a partial block of the xml file:

    <Properties> 
       <ManifestProperty ValueType="string">
         <Key>servername</Key>
         <Value>value1</Value>
       </ManifestProperty>
       <ManifestProperty ValueType="string">
         <Key>serverport</Key>
         <Value>value2</Value>
       </ManifestProperty>
    </Properties>

We need to search for the "key" and then replace the "value"

Upvotes: 1

Views: 1434

Answers (1)

AdminOfThings
AdminOfThings

Reputation: 25021

If you read the file into PowerShell as an XmlDocument object, you can do the following:

$myservername = "some-server"
$myport = "8888"
$xml = [xml](Get-Content C:\temp\xml.xml)
$xml.SelectNodes("//Properties/ManifestProperty[Key = 'servername']") |
    Foreach-Object {
        $_.Value = $myservername
    }
$xml.SelectNodes("//Properties/ManifestProperty[Key = 'serverport']") |
    Foreach-Object {
    $_.Value = $myport
    }
$xml.Save("C:\Temp\xml.xml")

Accessing the properties of an XML object is going to be more reliable than regex replace.

Explanation:

The code converts the contents of C:\temp\xml.xml into an XmlDocument object using the [xml] type accelerator. The SelectNodes() method uses an XPath to find any child element of Properties with a ManifestProperty element that contains a Key node. The node must contain the text servername or text serverport. Just in case the method returns multiple nodes, the results are piped into a Foreach-Object to update all values.

  • //Properties/ManifestProperty[Key = 'servername'] finds all ManifestProperty elements that are a child of the Properties element and contain at least one child element named Key with a value of servername.

If you must use regex to replace the text strings, which I don't recommend, you can do the following:

$myservername = "some-server"
$myport = "8888"
$xml = Get-Content xml.xml -Raw
$xml = $xml -replace "(?s)(?<=<Key>servername</Key>.*?<Value>)value1(?=</Value>)",$myservername
$xml = $xml -replace "(?s)(?<=<Key>serverport</Key>.*?<Value>)value2(?=</Value>)",$myport

Regex Replace Explanation:

  • Get-Content xml.xml -Raw: Reads the contents of the file as one string. This is useful because we need to match on multiple lines. Removing the -Raw switch, reads the file into PowerShell as an array. With the array output, we can't use regex to read behind or ahead.
  • (?s): This is the single line modifier. It allows for the regex . to match any newline characters. This is particularly useful for matching across multiple lines.
  • (?<=<Key>servername</Key>.*?<Value>): Performs a positive lookbehind assertion (?<=) for the literal match <Key>servername</Key>, 0 or more characters matched ungreedily, and literal match <Value>.
  • value1: Literal match
  • (?=</Value>): Performs a positive lookahead assertion for literal match </Value>.

Below is another way to do regex replace using capture groups:

$myservername = "some-server"
$myport = "8888"
$xml = Get-Content xml.xml -Raw
$xml = $xml -replace "(?s)(?<NameKey><Key>servername</Key>.*?<Value>)value1(?<NameValue></Value>)","`${NameKey}$myservername`${NameValue}"
$xml = $xml -replace "(?s)(?<PortKey><Key>serverport</Key>.*?<Value>)value2(?<PortValue></Value>)","`${PortKey}$myport`${PortValue}"

Regex Replace Explanation:

  • Get-Content xml.xml -Raw: Reads the contents of the file as one string. This is useful because we need to match on multiple lines. Removing the -Raw switch, reads the file into PowerShell as an array. With the array output, we can't use regex to read behind or ahead.
  • (?s): This is the single line modifier. It allows for the regex . to match any newline characters. This is particularly useful for matching across multiple lines.
  • (?<PortKey><Key>serverport</Key>.*?<Value>): (?<PortKey>) creates a capture group called PortKey. Everything matching within the parentheses would be included in that capture. serverport matches literally. .*? is a lazy match of 0 or more characters. <Value> matches literally.
  • value2: Literal match
  • (?<PortValue></Value>): Capture group called PortValue for everything that matches within the parentheses. </Value> is a literal match.
  • "`${PortKey}$myport`${PortValue}": This is the replacement string. Note the double quotes surrounding the expression so that $myport will be interpolated. ${PortKey} and ${PortValue} is the syntax required to access the capture groups. The backtick character is used to escape the $ in those capture group references because we do not want PowerShell to interpolate them.

You don't usually need to name your capture groups. However, since one of the captures is numeric, that can cause problems accessing the default capture groups. The first capture group would be accessed by $1. Since $myport is a number, then `$1$myport will be evaluated as $18888 and the regex match will not have a capture group named 18888. So naming the capture groups, adds better predictability.

Upvotes: 2

Related Questions