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": "*"
    }
1
  • 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 your question and insert workable examples for both formats. Commented Apr 9, 2022 at 9:21

1 Answer 1

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
}
Sign up to request clarification or add additional context in comments.

11 Comments

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 Well.. my code does replace the whole connection string. Only the search phrase is a partially comparison.
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.
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.
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.