2

My requirement is, I have a properties file say C:\google\configuration\backup\configuration.properties with content shown below

backup.path = C:\\ProgramData\\google\\backup
backup.volume.guid = \\\\?\\Volume{49e5d325-8065-49f4-bf0d-r4be94cc1feb}\\
backup.max.count = 10

I have a method that takes key and value as input.

function Script:change_or_replace_value([string]$key, [string]$value) {

    $origional_file_content = Get-Content $CONF_FILE_LOCATION
    $key_value_map = ConvertFrom-StringData($origional_file_content -join [Environment]::NewLine)
    $old_value = $key_value_map.$key
    $Old_file_pattern = "$key = $old_value"
    $new_file_pattern = "$key = $value"

    $origional_file_content | ForEach-Object {$_ -Replace $Old_file_pattern, $new_file_pattern} | Set-Content $NEW_FILE_LOCATION

}
  1. If key is "backup.volume.guid" and value is "\\?\Volume{111111-222-222-444-r4be94cc1feb}\" method should replace the text
backup.path = C:\\ProgramData\\google\\backup
backup.volume.guid = \\\\?\\Volume{111111-222-222-444-r4be94cc1feb}\\
backup.max.count = 10
  1. If key is "backup.volume.guid" and value is "" method should remove the line
backup.path = C:\\ProgramData\\google\\backup
backup.max.count = 10

If the value is empty delete the line else replace the text for the given key.

  • It contains special character like \ or other characters
  • How to delete the content if the key exists and value is an empty string
sandeep kamath
  • 133
  • 2
  • 11

1 Answers1

5

Your current approach has two problems, based on your attempt to update the properties by string manipulation via the file content as a single string:

  • In the ForEach-Object script block you'd need a different command to eliminate a line, because the -replace operator always returns something: if the regex pattern doesn't match the input, the input string is passed through.

  • You're missing an additional string-replacement step: ConvertFrom-StringData considers \ an escape character, so any pair of \\ in the input file turns into a single \ in the resulting hashtable. Therefore, you'll also have to double the \\ in $oldvalue and $value in order for the string replacement on the original file content to work.

  • Also, -replace, because it expects regex (regular expression) as the search operand, requires metacharachters such as \ to be escaped by \-escaping them; you could do that with [regex]::Escape($Old_file_pattern).


I suggest a different approach that avoids these problems, namely:

  • Directly modify the hashtable that ConvertFrom-StringData returns.

  • Then serialize the updated hashtable to the output file, using string formatting.

    • As part of the string formatting, ouble the \ in the values again by using the [string] type's .Replace() method, which operates on literal strings and is simpler (and faster) in this case; however, you could also use the somewhat counter-intuitive -replace '\\', '\\'
# Assign your real path here.
$OCUM_CONF_FILE_LOCATION = 'in.properties'

# Only for demonstration here: create a sample input file.
@'
backup.path = C:\\ProgramData\\google\\backup
backup.volume.guid = \\\\?\\Volume{49e5d325-8065-49f4-bf0d-r4be94cc1feb}\\
backup.max.count = 10
'@ > $OCUM_CONF_FILE_LOCATION

# Function which either updates, adds, or removes an entry.
# NOTE: 
#   * This function updates input file $OCUM_CONF_FILE_LOCATION *in place*.
#     To be safe, be sure to have a backup copy before you try this.
#   * Set-Content's default character encoding is used to save the updated file.
#     Use the -Encoding parameter as needed.
function Update-PropertiesFile ([string]$key, [string]$value) {
  $ht = ConvertFrom-StringData (Get-Content -Raw $OCUM_CONF_FILE_LOCATION)
  if ($ht.Contains($key)) { # update or delete existing entry
    if ('' -eq $value) { $ht.Remove($key) }
    else               { $ht[$key] = $value }
  } elseif ('' -eq $value) { # entry to remove not found
    Write-Warning "No entry with key '$key' found; nothing to remove."
    return
  } else { # new entry 
    $ht[$key] = $value
  }
  # Serialize the updated hashtable back to the input file.
  Set-Content $OCUM_CONF_FILE_LOCATION -Value $( 
    foreach ($key in $ht.Keys) {
     '{0} = {1}' -f $key, $ht[$key].Replace('\', '\\')
    }
  )
}
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • A lot of information to learn from your answer. I will try it out and let you know. I am worried about "To be safe, be sure to have a backup copy before you try this.". Does this mean is it possible to have any issues here ? – sandeep kamath Dec 18 '20 at 08:26
  • Also in my case, i am going to replace 10-15 keys at a time. Is there any better way of handling it all at once instead of running the same method again and again 10-15 times? – sandeep kamath Dec 18 '20 at 08:26
  • 1
    @sandeepkamath: Re risk: I was mostly thinking that during development of the function that you make sure that a bug doesn't accidentally destroy entries. Once the function works as intended, the risk of data loss is very small, but does exist: if writing back to the input file gets interrupted, e.g., due to a power outage, data loss can occur. If you want to plan for that, write to a temporary file first, and then replace the original file. – mklement0 Dec 18 '20 at 13:46
  • 1
    @sandeepkamath: Re multiple keys: it wouldn't be hard to extend the function to take parallel _arrays_ of keys and values, though perhaps passing a _hashtable_ is the better choice. If you run into trouble implementing that, I suggest you create a _new_ question post. – mklement0 Dec 18 '20 at 13:48
  • 2
    This is working well for all my needs. it is handling special cases and removing values. As you mentioned, I am updating multiple values at a time so I may have to go with your suggestion of passing hashtable instead of calling this function multiple times. – sandeep kamath Jan 05 '21 at 05:23
  • 1
    loses the order of the file, but i don't really care working for me – Nicholas DiPiazza Jun 10 '22 at 20:47