0

I’m an amateur at Powershell but I’m trying to figure out a way to use Powershell to find a connection string value using a wild card and replace it with a whole new connection string. A colleague and I threw together a script to find a server name inside of a connection string and replace it with a new server name. Now I need to replace the whole contents inside the string with a whole new one. Bonus is we would like to do this for both a xml config file and a json config file.

I would first like to search for a specific text so that I can find only the connection that pertains to a specific database. Once that is found it want to replace the whole text inside of specific wild card pattern.

Example: Look for the value of “SpecificServiceName” in

<add name="DbConnection" connectionString="USER ID=SomeId;PASSWORD=abc123;DATA SOURCE=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=servername.mydomain.com)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=SpecificServiceName)));PERSIST SECURITY INFO=True" providerName="Oracle.ManagedDataAccess.Client" />

If that is found then I would like to replace everything inside of the connectionString value with something new. So, look for connectionString=”*” and replace the “*” with

"USER ID=myNewUserId;PASSWORD=newPassword;DATA SOURCE=(DESCRIPTION_LIST = (LOAD_BALANCE = off)(FAILOVER = on)(DESCRIPTION =(CONNECT_TIMEOUT = 5)(TRANSPORT_CONNECT_TIMEOUT = 3)(RETRY_COUNT = 3)(ADDRESS_LIST =(LOAD_BALANCE = on)(ADDRESS = (PROTOCOL = TCP)(HOST = primaryserver.domain.com)(PORT = 1521)))(CONNECT_DATA =(SERVICE_NAME = SERVICENAME_SVC)))(DESCRIPTION =(CONNECT_TIMEOUT = 5)(TRANSPORT_CONNECT_TIMEOUT = 3)(RETRY_COUNT = 3)(ADDRESS_LIST =(LOAD_BALANCE = on)(ADDRESS = (PROTOCOL = TCP)(HOST = drserver.domain.com)(PORT = 1521)))(CONNECT_DATA =(SERVICE_NAME = SERVICENAME_SVC))));PERSIST SECURITY INFO=True" providerName="Oracle.ManagedDataAccess.Client"

Any suggestions or examples you can point me to for something like this?

Here is what we used to find a specific server name inside a connection string. It works well for just replacing a server name but I’m trying to do something similar but with the above scenario.

#After this runs, run it again to double check that all the words to replace were actually replaced. You can just run it, and then choose n, or close the script and check the ResultFile.csv. If its empty, then all of them should be updated.
 
$folderPath = "D:\Inetpub\websites"
$wordToReplace = "oldServerName"
$wordToUpdate = "newServerName"
$exportCSVPath = "D:\Script Files\ResultFile.csv"
 
#Send list of files to csv
$filesToUpdate = Get-ChildItem -Path $folderPath -include ('*.config', '*.json') -Recurse | Select-String $wordToReplace -List | Select Path | Export-CSV $exportCSVPath
 
 
#Ask for validation
Write-Output "Replacing $($wordToReplace) with $($wordToUpdate) in $($folderPath)"
$response = Read-Host "Check D:\ResultFile.csv and make sure everything listed should be updated! Confirm (y/n)"
 
#If response = y
if($response -eq "y"){
 
#Get list again, for some reason, the above list gets deleted...maybe because we are sending to CSV?
$files = Get-ChildItem -Path $folderPath -include ('*.config', '*.json') -Recurse | Select-String $wordToReplace -List | Select Path
 
#Print out each file name and update
foreach($file in $files){
Write-Output "Updating file: $($file.Path)"
 
#probably a better way to do upper vs lower, but didnt spend time researching...for some reason this isnt case insensitive, but the above ones are...
Get-ChildItem $file.Path -Recurse | ForEach {(Get-Content $_).Replace($wordToReplace.ToUpper(), $wordToUpdate)  |  Set-Content $_ }
 
Get-ChildItem $file.Path -Recurse | ForEach {(Get-Content $_).Replace($wordToReplace.ToLower(), $wordToUpdate)  |  Set-Content $_ }
}
}
else{
Write-Output "Update Aborted"
}
 
#just pauses to see output
$response = Read-Host

Appsettings Example

    {
      "FolderLocation": {
        "Input": "D:\\ImportFiles\\AppName",
        "Export": "D:\\ImportFiles\\AppName\\Export",
      },
      "FileName": {
        "Input": "InputFileName.csv",
        "Export": "ExportFileName.txt"
      },
      "ConnectionStrings": {
        "DbConnection": "user id=MyUserId;password=ABC123;data source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=Server1Name.com)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=MyServiceName)))",
"OtherConnection": "user id=MyUserId;password=ABC123;data source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=Server1Name.com)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=MyServiceName)))"
      },
      "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
          "Default": "Trace",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*"
    }
Caverman
  • 3,371
  • 9
  • 59
  • 115
  • You want to change text inside `.config` and also `.json` files, but these have very different formats. Looking at your example, the `.config` files are XML while `.json` is well... JSON. You shouldn't try updating these files using plain-text `.Replace()`, but instead load and convert them depending on their format as objects, change the values you need to update and write out new. If possible, [edit](https://stackoverflow.com/posts/71797729/edit) your question and insert workable examples for both formats. – Theo Apr 09 '22 at 09:21

1 Answers1

1

Continuing from my comment, you really should manipulate XML and JSON not as regular plain-text.

Assuming your (XML) .config files look like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="DbConnection" connectionString="USER ID=SomeId;PASSWORD=abc123;DATA SOURCE=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=servername.mydomain.com)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=SpecificServiceName)));PERSIST SECURITY INFO=True" providerName="Oracle.ManagedDataAccess.Client" />
  </connectionStrings>
</configuration>

and your .json files are like this:

{
    "configuration": {
        "connectionStrings": {
            "add": {
                "name": "DbConnection",
                "connectionString": "USER ID=SomeId;PASSWORD=abc123;DATA SOURCE=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=servername.mydomain.com)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=SpecificServiceName)));PERSIST SECURITY INFO=True",
                "providerName": "Oracle.ManagedDataAccess.Client"
            }
        }
    }
}

Without those Read-Host lines in the code (I think you should do all the user questions BEFORE entering the code), here's how I would do it:

$folderPath          = 'D:\Inetpub\websites'
$exportCSVPath       = 'D:\somewhere\UpdateResults.csv'
$wordToSearch        = 'SpecificServiceName'
$newConnectionString = 'USER ID=myNewUserId;PASSWORD=newPassword;DATA SOURCE=(DESCRIPTION_LIST = (LOAD_BALANCE = off)(FAILOVER = on)(DESCRIPTION =(CONNECT_TIMEOUT = 5)(TRANSPORT_CONNECT_TIMEOUT = 3)(RETRY_COUNT = 3)(ADDRESS_LIST =(LOAD_BALANCE = on)(ADDRESS = (PROTOCOL = TCP)(HOST = primaryserver.domain.com)(PORT = 1521)))(CONNECT_DATA =(SERVICE_NAME = SERVICENAME_SVC)))(DESCRIPTION =(CONNECT_TIMEOUT = 5)(TRANSPORT_CONNECT_TIMEOUT = 3)(RETRY_COUNT = 3)(ADDRESS_LIST =(LOAD_BALANCE = on)(ADDRESS = (PROTOCOL = TCP)(HOST = drserver.domain.com)(PORT = 1521)))(CONNECT_DATA =(SERVICE_NAME = SERVICENAME_SVC))));PERSIST SECURITY INFO=True" providerName="Oracle.ManagedDataAccess.Client"'

# get a list of the files
$configFiles = Get-ChildItem -Path $folderPath -Include ('*.config', '*.json') -File -Recurse

$result = foreach ($file in $configFiles) {
    # do NOT use Write-Output because that will mess up the $result
    Write-Host "Processing file '$($file.FullName)'"

    # create a flag to track if the file is updated or not
    $updated = $false
    # prepare an object for the CSV
    $objOut = [PsCustomObject]@{
                  File    = $file.FullName
                  Updated = $updated
                  Status  = $null
              }

    # what filetype?
    if ($file.Extension -eq '.json') {
        try {
            $json = (Get-Content -Path $file.FullName -Raw) | ConvertFrom-Json -ErrorAction Stop
            $json.configuration.connectionStrings.add | Where-Object { $_.connectionString -like "*$wordToSearch*" } | ForEach-Object {
                $_.connectionString = $newConnectionString
                $updated = $true
            }
            if ($updated) { 
                $json | ConvertTo-Json -Depth 99 | Set-Content -Path $file.FullName -Force   # Depth 99 for safety..
            }
            # fill the output objects details
            $objOut.Updated = $updated
            $objOut.Status  = 'OK'
        }
        catch {
            # set the error in the output object
            $objOut.Status  = "Error: $($_.Exception.Message)"
        }
    }
    else {  
        # assume XML
        try {
            $xml = [System.Xml.XmlDocument]::new()
            $xml.Load($file.FullName)
            $xml.DocumentElement.connectionStrings.add | Where-Object { $_.connectionString -like "*$wordToSearch*" } | ForEach-Object {
                $_.connectionString = $newConnectionString
                $updated = $true
            }
            if ($updated) { 
                $xml.Save($file.FullName) 
            }
            # fill the output objects details
            $objOut.Updated = $updated
            $objOut.Status  = 'OK'
        }
        catch {
            # set the error in the output object
            $objOut.Status  = "Error: $($_.Exception.Message)"
        }
    }
    # output the object so it gets collected in variable $result
    $objOut
}

# output on screen
$result

# save the results as CSV file
$result | Export-Csv -Path $exportCSVPath -NoTypeInformation

Needless to say that you should try this on a copy of the contents of the D:\Inetpub\websites folder first!

P.S. PowerShell creates valid JSON, but the format is quite ugly. If you want to prettify that, you can use the function I posted earlier Format-Json


Using your example JSON, change this part in the if ($file.Extension -eq '.json') {..} block

 $json.configuration.connectionStrings.add | Where-Object { $_.connectionString -like "*$wordToSearch*" } | ForEach-Object {
    $_.connectionString = $newConnectionString
    $updated = $true
}

into

$json.connectionStrings | Where-Object { $_.DbConnection -like "*$wordToSearch*" } | ForEach-Object {
    $_.DbConnection = $newConnectionString
    $updated = $true
}

In case you cannot rely on the element's name being DbConnection, you can use this loop instead:

# get the actual element name and value, filter the values that pass the comparison
$json.connectionStrings.PsObject.Properties | Where-Object { $_.Value -like "*$wordToSearch*" } | ForEach-Object {
    $json.connectionStrings.$($_.Name) = $newConnectionString
    $updated = $true
}
Theo
  • 57,719
  • 8
  • 24
  • 41
  • Thanks for the feed back. Yes, we are trying to update connection strings in both .Net Framework apps (XML) and .Net Core apps (JSON). When you're just looking to change the sever name like we've done before, the script I posted works pretty well. Now we are having to update connection strings to completely new ones that will work with a DR server in case of a fail over event. Replacing the whole sting is much more difficult which is why I posted. I tried researching for something similar to fit our scenario and didn't find anything. I'll test this out tomorrow at work and report back. – Caverman Apr 10 '22 at 15:06
  • @Caverman Well.. my code **does** replace the whole connection string. Only the search phrase is a partially comparison. – Theo Apr 10 '22 at 15:24
  • This looks to be working, although I don't 100% understand the whole logic (things like -Depth 99). Of course in our applications nothing can be simple. We have a few sites that have more than one DB connection and this updates both. I'll have to look into seeing if I can narrow it down to only replacing ones that have a specific name. I can probably figure that one out or worst case we will have to manually update those few scenarios. – Caverman Apr 11 '22 at 17:54
  • 1
    Thanks so much for this!!! You've saved me a ton of time and frustration. I'm not sure I would have ever come up with a script like this. Plus this is a good teaching lesson on how we can do more with Powershell in the future. – Caverman Apr 11 '22 at 17:56
  • The XML piece is working but I can't get it to find and update the JSON file. Typically our JSON files have "ConnectionStrings":{ "DBConnection": XXXXX} as the format. I changed { $_.connectionString -like "*$wordToSearch*" } to { $_.DBConnection -like "*$wordToSearch*" } but that didn't seem to work. Any suggestions or something you can point me to? – Caverman Apr 11 '22 at 21:13
  • @Caverman Well, for one thing, the `-like` operator is used to compare part of a longer string inside another string and for that it needs wildcards, so that should be `"*$wordToSearch*"`. Since I don't have a real example of what your json files look line, I had to guess. If you edit your question adn add an workable example of that so I can see the structure, I can adjust my answer to match that. Of course, sanitized any sensitive data, but leave the **structure** as is. – Theo Apr 12 '22 at 07:06
  • @Caverman Without those asterikses, `-like` is the same as `-eq` and that would mean you have to provide the **entire** connection string exactly to look for (although case-insensitive). – Theo Apr 12 '22 at 07:08
  • I added the structure of an appsettings file we typically use to the original post. – Caverman Apr 12 '22 at 13:38
  • I was able to tweak the code to get it to update the "DbConnection" section in the JSON file but I'm now trying to figure out how I would update the string if it happened to be name something besides "DbConnection" just to make sure to catch any connection that didn't happen to be name "DbConnection". Works in the XML side the way it is. Trying a couple different things on the JSON side for this but at the moment it's only updating if the name is "DbConnection". – Caverman Apr 12 '22 at 15:02
  • @Caverman Please see my edit. I have added code for when the connectionStrings element name is unknown. – Theo Apr 13 '22 at 12:08
  • 1
    Perfect! Thanks so much. You've taught both me and my colleague on this. You examples and explanations were spot on for us. We will be using these technics going forward. I had figured out the part about using _.DbConnection but I was trying to use a "*" wildcard to catch all the different names. – Caverman Apr 13 '22 at 13:12