I cannot update ini file. Treating ini file as hastable in Powershell - powershell

I need to update ini configuration file. I managed to convert the file to hastable and updating values. But when I check if the changes are correct in the file, it hasn't changed. Add-Content doesn't work. do I need to convert to String to use Add-Content function?
Configuration file is filled with plain text also.
"ini" Configuration file:
[sqlScript1Deployment]
sqlServerName = '??????????'
olapServerName = '??????????'
(...)
My ps1 code:
[hashtable]$ht = Get-Configuration($iniFilepath)
$ht["sqlScript1Deployment"]["sqlServerName"] = 'Master'
$ht | Add-Content $iniFilepath
Expected code in "ini" file:
[sqlScript1Deployment]
sqlServerName = 'Master'
Actual result in "ini" file:
[sqlScript1Deployment]
sqlServerName = '??????????'

I have no idea where you got the Get-Configuration function from, but if it creates a hashtable where each Key is a Section for the INI and every Value is a name/value pair like this:
$ht = #{
'sqlScript1Deployment' = #{
'sqlServerName' = '??????????'
'olapServerName' = '??????????'
}
}
The following code may help:
# set the new value for sqlServerName
$ht['sqlScript1Deployment']['sqlServerName'] = 'Master'
# write the Hashtable back to disk as .INI file
$sb = New-Object -TypeName System.Text.StringBuilder
# the Keys are the Sections in the Ini file
# the properties are name/value pairs within these keys
foreach ($section in $ht.Keys) {
[void]$sb.AppendLine("[$section]")
foreach ($name in $ht[$section].Keys) {
$value = $ht[$section][$name]
# the value needs to be quoted when:
# - it begins or ends with whitespace characters
# - it contains single or double quote characters
# - it contains possible comment characters ('#' or ';')
if ($value -match '^\s+|[#;"'']|\s+$') {
# escape quotes inside the value and surround the value with double quote marks
$value = '"' + ($value -replace '(["''])', '\$1') + '"'
}
[void]$sb.AppendLine("$name = $value")
}
}
$sb.ToString() | Out-File $iniFilepath
[void]$sb.Clear()
The resulting file will look like this:
[sqlScript1Deployment]
sqlServerName = Master
olapServerName = ??????????

Related

Powershell : Update properties file with key = value

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
}
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
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
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('\', '\\')
}
)
}

PowerShell -Replace appending (oddly) instead of replacing

EDIT: PowerShell version is 5.1
I am writing some code that will take the value of a variable from a file, and if that string is made up of other variables within the file, it will locate them and replace it to reconstruct the run-time value. It assumes the variable contains a string, and that the string describes a directory.
For example, the file contains:
$var0 = "C:\Users\v-anad\Documents"
$var1 = "$var0\TestFolder"
Then when the code looks for $var1, it should return something like: "C:\Users\v-anad\Documents\TestFolder\"
However, the actual output I see is:
\TestFolder"-anad\Documents"
When it replaces, it deletes the correct substring ($var0), but when it inserts the value of $var0, it skips over the characters that existed there before, and appends the remaining characters to the end of the string. I have no clue what/where I've gone wrong.
Here is the code in question:
function Get-Var-Value-In-File([string]$varName, [string]$file) {
$regex = "(?<=\$varName = )[^`n]*"
$content = Get-Content -Raw $file
return [regex]::Match($content, $regex).Value
}
$file = 'C:\Users\v-anad\Documents\TestFolder\TestVars.ps1'
$var = '$var1'
$value = Get-Var-Value-In-File $var $file
$regex = "\$[^\\]*"
$nextVar = [regex]::Match($value, $regex).Value
$nextValue = Get-Var-Value-In-File $nextVar $file
Write-Output "$var = $value"
Write-Output "$nextVar = $nextValue"
Write-Output $nextVar.Replace($nextVar, $nextValue)
Write-Output ($value -replace [regex]::Escape($nextVar),$nextValue)
Output:
$var1 = "$var0\TestFolder"
$var0 = "C:\Users\v-anad\Documents"
"C:\Users\v-anad\Documents"
\TestFolder"-anad\Documents"
Note how the code above does not account for the extra quotes that would be inserted into the final value, so should this curious behavior be fixed, the output will be: ""C:\Users\v-anad\Documents"\TestFolder\"
The problem was there was a carriage return (\r, or `r in PowerShell) that my regex included in the match, causing the behavior when the string replacement occurred. Thanks to PerSerAl for being a second pair of eyes to catch it.

Grab parameters from url and drop them to powershell variables

i have a powershell listener running on my windows-box. Code from Powershell-Gallery: https://www.powershellgallery.com/packages/HttpListener/1.0.2
The "Client" calls the listener with the following example url:
With Powershell i do:
invoke-webrequest -Uri "http://127.0.0.1:8888/Test?id='1234'&param2='##$$'&param3='This is a test!'"
I have no idea, how to drop the parameters from the url to variables with the same name in powershell. I need to bring the parameters to powershellvariables to simply echo them. this is my last missing part. The parameters are separated with & and parameternames are case-sensitive.
To be more detailed, the id from the url should be in a powershell-variable named $id with the value 1234. The Variables can contain spaces, special characters, numbers, alphas. They are case sensitive. The parametervalue could be "My Name is "Anna"! My Pets Name is 'Bello'. " with all the "dirty" Characters like '%$"!{[().
Can someone point me to the right way how to get this solved?
In a valid url, the $ characters should have been escaped to %24
The other 'dirty' character % in Url escaped form is %25
This means the example url is invalid and should be:
$url = "http://127.0.0.1:8888/Test?id='1234'&param2='##%24%24'&param3='This is a test!'"
Then the following does work
$url = "http://127.0.0.1:8888/Test?id='1234'&param2='##%24%24'&param3='This is a test!'"
if ($url -is [uri]) {
$url = $url.ToString()
}
# test if the url has a query string
if ($url.IndexOf('?') -ge 0) {
# get the part of the url after the question mark to get the query string
$query = ($url -split '\?')[1]
# or use: $query = $url.Substring($url.IndexOf('?') + 1)
# remove possible fragment part of the query string
$query = $query.Split('#')[0]
# detect variable names and their values in the query string
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$varName = [uri]::UnescapeDataString($kv[0]).Trim()
$varValue = [uri]::UnescapeDataString($kv[1])
New-Variable -Name $varname -Value $varValue -Force
}
}
else {
Write-Warning "No query string found as part of the given URL"
}
Prove it by writing the newly created variables to the console
Write-Host "`$id = $id"
Write-Host "`$param2 = $param2"
Write-Host "`$param3 = $param3"
which in this example would print
$id = '1234'
$param2 = '##$$'
$param3 = 'This is a test!'
However, I personally would not like to create variables like this, because of the risk of overwriting already existing ones.
I think it would be better to store them in a hash like this:
# detect variable names and their values in the query string
# and store them in a Hashtable
$queryHash = #{}
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$name = [uri]::UnescapeDataString($kv[0]).Trim()
$queryHash[$name] = [uri]::UnescapeDataString($kv[1])
}
$queryHash
which outputs
Name Value
---- -----
id '1234'
param2 '##$$'
param3 'This is a test!'

Reading strings from text files using switch -regex returns null element

Question:
The intention of my script is to filter out the name and phone number from both text files and add them into a hash table with the name being the key and the phone number being the value.
The problem I am facing is
$name = $_.Current is returning $null, as a result of which my hash is not getting populated.
Can someone tell me what the issue is?
Contents of File1.txt:
Lori
234 east 2nd street
Raleigh nc 12345
9199617621
lori#hotmail.com
=================
Contents of File2.txt:
Robert
2531 10th Avenue
Seattle WA 93413
2068869421
robert#hotmail.com
Sample Code:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' { $name = $_.current}
'^\d{10}' {
$phone = $_.current
$hash.Add($name,$phone)
$name=$phone=$null
}
default
{
write-host "Nothing matched"
}
}
$hash
Remove the current property reference from $_:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' {
$name = $_
}
'^\d{10}' {
$phone = $_
$hash.Add($name, $phone)
$name = $phone = $null
}
default {
Write-Host "Nothing matched"
}
}
$hash
Mathias R. Jessen's helpful answer explains your problem and offers an effective solution:
it is automatic variable $_ / $PSItem itself that contains the current input object (whatever its type is - what properties $_ / $PSItem has therefore depends on the input object's specific type).
Aside from that, there's potential for making the code both less verbose and more efficient:
# Initialize the output hashtable.
$hash = #{}
# Create the regex that will be used on each input file's content.
# (?...) sets options: i ... case-insensitive; m ... ^ and $ match
# the beginning and end of every *line*.
$re = [regex] '(?im)^([a-z]+|\d{10})$'
# Loop over each input file's content (as a whole, thanks to -Raw).
Get-Content -Raw File*.txt | foreach {
# Look for name and phone number.
$matchColl = $re.Matches($_)
if ($matchColl.Count -eq 2) { # Both found, add hashtable entry.
$hash.Add($matchColl.Value[0], $matchColl.Value[1])
} else {
Write-Host "Nothing matched."
}
}
# Output the resulting hashtable.
$hash
A note on the construction of the .NET [System.Text.RegularExpressions.Regex] object (or [regex] for short), [regex] '(?im)^([a-z]+|\d{10})$':
Embedding matching options IgnoreCase and Multiline as inline options i and m directly in the regex string ((?im) is convenient, in that it allows using simple cast syntax ([regex] ...) to construct the regular-expression .NET object.
However, this syntax may be obscure and, furthermore, not all matching options are available in inline form, so here's the more verbose, but easier-to-read equivalent:
$re = New-Object regex -ArgumentList '^([a-z]+|\d{10})$', 'IgnoreCase, Multiline'
Note that the two options must be specified comma-separated, as a single string, which PowerShell translates into the bit-OR-ed values of the corresponding enumeration values.
other solution, use convertfrom-string
$template=#'
{name*:Lori}
{street:234 east 2nd street}
{city:Raleigh nc 12345}
{phone:9199617621}
{mail:lori#hotmail.com}
{name*:Robert}
{street:2531 10th Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
{name*:Robert}
{street:2531 Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
'#
Get-Content -Path "c:\temp\file*.txt" | ConvertFrom-String -TemplateContent $template | select name, phone

How to Parse .ini File into PowerShell Variable(s)

I have the following .ini file (named metrics.ini) containing a single record, but it may have more records added to it in the future:
$DatabaseConnection_STR=MYSERVER\MYINSTANCE
I need to parse this file into a PowerShell variable. I can parse the string with the following, but I am at a loss for creating the new $DatabaseConnection_STR variable (based on what was parsed from the .ini file). I don't want to hardcode $DatabaseConnection_STR in my script--I would rather let the script figure it out so that is can handle additional variables in the future.
# This code assumes that no blank lines are in the file--a blank line will cause an early termination of the read loop
########################################
#
# Confirm that the file exists on disk
#
########################################
$IniFile_NME="C:\temp\metrics.ini"
dir $IniFile_NME
########################################
#
# Parse the file
#
########################################
$InputFile = [System.IO.File]::OpenText("$IniFile_NME")
while($InputRecord = $InputFile.ReadLine())
{
# Display the current record
write-host "`$InputRecord=$InputRecord"
write-host ""
# Determine the position of the equal sign (=)
$Pos = $InputRecord.IndexOf('=')
write-host "`$Pos=$Pos"
# Determine the length of the record
$Len = $InputRecord.Length
write-host "`$Len=$Len"
# Parse the record
$Variable_NME = $InputRecord.Substring(0, $Pos)
$VariableValue_STR = $InputRecord.Substring($Pos + 1, $Len -$Pos -1)
write-host "`$Variable_NME=$Variable_NME"
write-host "`$VariableValue_STR=$VariableValue_STR"
# Create a new variable based on the parsed information--**the next line fails**
`$Variable_NME=$VariableValue_STR
}
$InputFile.Close()
Any ideas?
It's probably easier and more concise to use the split command. You could also store your configuration values in a hash table:
$config = #{}
Get-Content $IniFile_NME | foreach {
$line = $_.split("=")
$config.($line[0]) = $line[1]
}
You could still use the same method of creating variables if don't want a hash table, but using Powershell's reading, looping, and splitting will make it easier.
This works. It turns out that the New-Variable command does not use a dollar sign ($) with its "-name" parameter; so, I had to parse that out. See below.
# This code assumes that no blank lines are in the file--a blank line will cause an early termination of the read loop
########################################
#
# Confirm that the file exists on disk
#
########################################
$IniFile_NME="C:\temp\metrics.ini"
dir $IniFile_NME
########################################
#
# Parse the file
#
########################################
$InputFile = [System.IO.File]::OpenText("$IniFile_NME")
while($InputRecord = $InputFile.ReadLine())
{
# Display the current record
write-host "`$InputRecord=$InputRecord"
write-host ""
# Determine the position of the equal sign (=)
$Pos = $InputRecord.IndexOf('=')
write-host "`$Pos=$Pos"
# Determine the length of the record
$Len = $InputRecord.Length
write-host "`$Len=$Len"
# Parse the record
$Variable_NME = $InputRecord.Substring(1, $Pos -1)
$VariableValue_STR = $InputRecord.Substring($Pos + 1, $Len -$Pos -1)
write-host "`$Variable_NME=$Variable_NME"
write-host "`$VariableValue_STR=$VariableValue_STR"
# Create a new variable based on the parsed information
new-variable -name $Variable_NME -value $VariableValue_STR
get-variable -name $Variable_NME
}
$InputFile.Close()