Update value (if it exists) in JSON from Powershell - json

I have an appSettings.json file in the following format
{
"ConnectionStrings": {
...
},
"appSettings": {
....
"LocalVersion": "1.0.0"
},
"Logging": {
....
},
"AllowedHosts":
....
}
}
I'm trying to write a Powershell script that will update this LocalVersion property. I need to script to 1. check if appSettings object exists, 2. check if LocalVersion property is there and 2. Update the LocalVersion to a new value and save the file.
My script seems to be mostly working, however the writing to the file does not work. The value is getting changed in the Powershell script but not being saved to file.
$appSettingsPath = "C:\git\Source\src\IMStack\appsettings.json"
Write-Host "Reading JSON from " $appSettingsPath
$jsonContent = (Get-Content $appSettingsPath -Raw) | ConvertFrom-Json
Write-Host $jsonContent
if ([bool]($jsonContent.PSobject.Properties.name -match "appSettings")){
$appSettings = $jsonContent.appSettings
Write-Host "Found appSettings" $appSettings
if ([bool]($appSettings.PSobject.Properties.name -match "LocalVersion")){
$version = $appSettings.LocalVersion
Write-Host $version
$jsonContent.appSettings.LocalVersion= "1.1.1"
Write-Host "Writing " $jsonContent " with " $jsonContent.appSettings " to " $appSettingsPath
$jsonContent | ConvertTo-Json -depth 100 | Set-Content "appSettingsPath"
}
}
How can I save this JSON to the file?

Related

Variable substitution: How to update values in a json file and then save the output of the now substituted file

I have a script that I want to do variable substitution on a json file.
It takes json file A and json file B and converts it into a dynamic object.
So you end up with something like this:
For Json File A:
Json file A key
Json file A value
Age
ENTER VALUE
ADDRESS
ENTER VALUE
For Json File B:
Json file B key
Json file B value
Age
38
ADDRESS
10 example lane
If the keys match then the value from Json File B will be subbed into Json File A where the keys match.
So Json File A should end up looking like:
Json file A key
Json file A value
Age
38
ADDRESS
10 example lane
I am able to loop through and find matching keys and set the value of Json File A to the Value from Json File B for that Key.
However, I don't know how to get the new value into the actual json file A
Here are the relevant snippets of code:
$appSettings = Get-Content $JsonFileA -Raw | ConvertFrom-Json | Get-LeafProperty
if ($keyAA -eq $keyBB ) {
write-host "$keyAA and $keyBB match"
$valueAA=$valueBB
write-host "new appsettings value is now $valueAA"
# Save newly populated jsonfile to a new file
$appSettings | ConvertTo-Json -Depth 4 | Set-Content "path/to/output/file"
}
The write-host "new appsettings value is now $appvalue" line shows that it has done what i want and updated the value for Json File A to the value from Json File B for the matching key
But it is not saving the value to the output file. The output file is still just what the original Json File A looks like
FULL CODE for context:
function Get-LeafProperty {
param([Parameter(ValueFromPipeline)] [object] $InputObject, [string] $NamePath)
process {
if ($null -eq $InputObject -or $InputObject -is [DbNull] -or $InputObject.GetType().IsPrimitive -or $InputObject.GetType() -in [string], [datetime], [datetimeoffset], [decimal], [bigint]) {
[pscustomobject] #{ NamePath = $NamePath; Value = $InputObject }
}
elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [System.Collections.IDictionary]) {
$i = 0
foreach ($o in $InputObject) { Get-LeafProperty $o ($NamePath + '[' + $i++ + ']') }
}
else {
$props = if ($InputObject -is [System.Collections.IDictionary] -or $InputObject -is [System.Collections.IEnumerable]) { $InputObject.GetEnumerator() } else { $InputObject.psobject.properties }
$sep = '.' * ($NamePath -ne '')
foreach ($p in $props) {
Get-LeafProperty $p.Value ($NamePath + $sep + $p.Name)
}
}
}
}
$appSettings = Get-Content $jsonfileA -Raw | ConvertFrom-Json | Get-LeafProperty
$secrets = Get-Content $jsonfileB -Raw | ConvertFrom-Json | Get-LeafProperty
$leafProperties | ForEach-Object {
$keyAA = $_.NamePath
$valueAA = $_.Value
$secrets | ForEach-Object {
$keyBB = $_.NamePath
$keyBB = $_.Value
if ($keyAA -eq $keyBB ) {
write-host "$keyAA and $keyBB match"
$valueAA=$valueBB
write-host "new appsettings value is now $valueAA"
$appSettings | ConvertTo-Json -Depth 4 | Set-Content "path/to/output/file"
}
}
}

Powershell: Modify key value pair in JSON file

How do I modify a Key Value Pair in a JSON File with powershell?
We are trying to modify Database Connection, sometimes it can be two levels nested deep, sometimes it can be three levels deep.
Trying to utilize this answer,
Currently we are switching servers in multiple json files, so we can test in different server environments.
Add new key value pair to JSON file in powershell.
"JWTToken": {
"SecretKey": "Security Key For Generate Token",
"Issuer": "ABC Company"
},
"AllowedHosts": "*",
"ModulesConfiguration": {
"AppModules": [ "ABC Modules" ]
},
"ConnectionStrings": {
"DatabaseConnection": "Server=testserver,1433;Database=TestDatabase;User Id=code-developer;password=xyz;Trusted_Connection=False;MultipleActiveResultSets=true;",
"TableStorageConnection": "etc",
"BlobStorageConnection": "etc"
},
Once you convert JSON string to an object with PowerShell, it's not really a problem to then change the properties. The main issue you are going to face here is that your string is currently invalid JSON for .Net or at least it won't be expecting it in the current format. We can fix that though.
Here is your current JSON.
"JWTToken": {
"SecretKey": "Security Key For Generate Token",
"Issuer": "ABC Company"
},
"AllowedHosts": "*",
"ModulesConfiguration": {
"AppModules": [ "ABC Modules" ]
},
"ConnectionStrings": {
"DatabaseConnection": "Server=testserver,1433;Database=TestDatabase;User Id=code-developer;password=xyz;Trusted_Connection=False;MultipleActiveResultSets=true;",
"TableStorageConnection": "etc",
"BlobStorageConnection": "etc"
},
There may be other issues, for PowerShell JSON, in your application.config file, but these two are immediately noticeable to me.
Unnecessary trailing commas
No definitive opening { and closing }
How Can We Fix This?
We can use simple string concatenation to add { and } where necessary.
$RawText = Get-Content -Path .\path_to\application.config -Raw
$RawText = "{ " + $RawText + " }"
To remove any unnecessary parsing issues with trailing commas when parsing the JSON with ConvertFrom-Json we need to remove them via regex. My proposed approach would be to identify them by whether the current array } or ] closes after them, it might be that these closing brackets have a number of spaces or \s before they appear. So we would have a regex that looks like this:
"\,(?=\s*?[\}\]])".
We could then use that with -replace in PowerShell. Of course we will replace them with an empty string.
$FormattedText = $RawText -replace "\,(?=\s*?[\}\]])",""
From here we convert to JSON.
$JsonObj = $FormattedText | ConvertFrom-Json
We can now change your database string by setting a property.
$JsonObj.ConnectionStrings.DatabaseConnection = "your new string"
We use ConvertTo-Json to convert the array back to a Json string.
$JsonString = $JsonObj | ConvertTo-Json
It's not important to return the trailing commas, they aren't valid JSON, but your file needs the first { and last } removing before we commit it back to file with Set-Content.
# Remove the first { and trim white space. Second TrimStart() clears the space.
$JsonString = $JsonString.TrimStart("{").TrimStart()
# Repeat this but for the final } and use TrimEnd().
$JsonString = $JsonString.TrimEnd("}").TrimEnd()
# Write back to file.
$JsonString | Set-Content -Path .\path_to\application.config -Force
Your config file should be written back more or less as you found it. I will try and think of a regex to fix the appearance of the formatting, it shouldn't error, it just doesn't look great. Hope that helps.
EDIT
Here is a function to fix the unsightly appearance of the text in the file.
function Restore-Formatting {
Param (
[parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$InputObject
)
$JsonArray = $InputObject -split "\n"
$Tab = 0
$Output = #()
foreach ($Line in $JsonArray) {
if ($Line -match "{" -or $Line -match "\[") {
$Output += (" " * $Tab) + $Line.TrimStart()
$Tab += 4
}
elseif ($Line -match "^\s+}" -or $Line -match "^\s+\]") {
$Tab -= 4
$Output += (" " * $Tab) + $Line.TrimStart()
}
else {
$Output += (" " * $Tab) + $Line.TrimStart()
}
}
$Output
}
TL;DR Script:
$RawText = Get-Content -Path .\path_to\application.config -Raw
$RawText = "{ " + $RawText + " }"
$FormattedText = $RawText -replace "\,(?=\s*?[\}\]])",""
$JsonObj = $FormattedText | ConvertFrom-Json
$JsonObj.ConnectionStrings.DatabaseConnection = "your new string"
$JsonString = $JsonObj | ConvertTo-Json
$JsonString = $JsonString.TrimStart("{").TrimStart()
$JsonString = $JsonString.TrimEnd("}").TrimEnd()
$JsonString | Restore-Formatting | Set-Content -Path .\path_to\application.config -NoNewLine -Force

Convert and format XML to JSON on Azure Storage Account in Powershell

So i'm trying to convert XML files on an Azure Storage Container to JSON in the same container.
This way I'm able to read the information into an Azure SQL Database via an Azure Datafactory.
I'd like to stay clear from using Logic apps if able.
The JSON files need to be formatted.
And all this through the use of PowerShell scripting.
What i've got so far after some searching on the interwebs and shamelessly copying and pasting powershell code:
#Connect-AzAccount
# Helper function that converts a *simple* XML document to a nested hashtable
# with ordered keys.
function ConvertFrom-Xml {
param([parameter(Mandatory, ValueFromPipeline)] [System.Xml.XmlNode] $node)
process {
if ($node.DocumentElement) { $node = $node.DocumentElement }
$oht = [ordered] #{}
$name = $node.Name
if ($node.FirstChild -is [system.xml.xmltext]) {
$oht.$name = $node.FirstChild.InnerText
} else {
$oht.$name = New-Object System.Collections.ArrayList
foreach ($child in $node.ChildNodes) {
$null = $oht.$name.Add((ConvertFrom-Xml $child))
}
}
$oht
}
}
function Format-Json
{
<#
.SYNOPSIS
Prettifies JSON output.
.DESCRIPTION
Reformats a JSON string so the output looks better than what ConvertTo-Json outputs.
.PARAMETER Json
Required: [string] The JSON text to prettify.
.PARAMETER Minify
Optional: Returns the json string compressed.
.PARAMETER Indentation
Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4.
.PARAMETER AsArray
Optional: If set, the output will be in the form of a string array, otherwise a single string is output.
.EXAMPLE
$json | ConvertTo-Json | Format-Json -Indentation 2
#>
[CmdletBinding(DefaultParameterSetName = 'Prettify')]
Param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
[string]$Json,
[Parameter(ParameterSetName = 'Minify')]
[switch]$Minify,
[Parameter(ParameterSetName = 'Prettify')]
[ValidateRange(1, 1024)]
[int]$Indentation = 4,
[Parameter(ParameterSetName = 'Prettify')]
[switch]$AsArray
)
if ($PSCmdlet.ParameterSetName -eq 'Minify')
{
return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
}
# If the input JSON text has been created with ConvertTo-Json -Compress
# then we first need to reconvert it without compression
if ($Json -notmatch '\r?\n')
{
$Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
}
$indent = 0
$regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'
$result = $Json -split '\r?\n' |
ForEach-Object {
# If the line contains a ] or } character,
# we need to decrement the indentation level unless it is inside quotes.
if ($_ -match "[}\]]$regexUnlessQuoted")
{
$indent = [Math]::Max($indent - $Indentation, 0)
}
# Replace all colon-space combinations by ": " unless it is inside quotes.
$line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')
# If the line contains a [ or { character,
# we need to increment the indentation level unless it is inside quotes.
if ($_ -match "[\{\[]$regexUnlessQuoted")
{
$indent += $Indentation
}
$line
}
if ($AsArray) { return $result }
return $result -Join [Environment]::NewLine
}
# Storage account details
$resourceGroup = "insert resource group here"
$storageAccountName = "insert storage account name here"
$container = "insert container here"
$storageAccountKey = (Get-AzStorageAccountKey -ResourceGroupName $resourceGroup -Name $storageAccountName).Value[0]
$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccountName
# Creating Storage context for Source, destination and log storage accounts
#$context = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey
$context = New-AzStorageContext -ConnectionString "insert connection string here"
$blob_list = Get-AzStorageBlob -Container $container -Context $context
foreach($blob_iterator in $blob_list){
[XML](Get-AzStorageBlobContent $blob_iterator.name -Container $container -Context $context) | ConvertFrom-Xml | ConvertTo-Json -Depth 11 | Format-Json | Set-Content ($blob_iterator.name + '.json')
}
Output =
Cannot convert value "Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel.AzureStorageBlob" to type "System.Xml.XmlDocument". Error: "The specified node cannot
be inserted as the valid child of this node, because the specified node is the wrong type."
At C:\Users\.....\Convert XML to JSON.ps1:116 char:6
+ [XML](Get-AzStorageBlobContent $blob_iterator.name -Container $c ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvalidCastToXmlDocument
When I run the code the script asks me if I want to download the xml file to a local folder on my laptop.
This is not what I want, I want the conversion to be done in Azure on the Storage container.
And I think that I'm adding ".json" to the .xml file name.
So the output would become something like filename.xml.json instead of just filename.json
What's going wrong here?
And how can it be fixed?
Thank you in advance for your help.

Replace a value in a JSON file using Powershell

How can I replace a value in a JSON file with another value taken from a config file (JSON)?
This is the code for the JSON files:
Config.json
{
"ABlob_AAD_Snapshot": [
{
"name": "properties.availability.frequency",
"value": "$Frequency$"
}]
}
ABlob_AAD_Snapshot.json
{
"name": "AzureBlobStore",
"properties": {
"type": "AzureStorage",
"availability": {
"frequency": "Value from Config.json file"
}
}
}
What I'm trying to do is the following:
Go through the Config.json file and store the values for "name" and "value" segments in variables. The "name" segment value is a path in ABlob_AAD_Snapshot.json file.
Follow the path in the ABlob_AAD_Snapshot.json file and replace the segment "frequency" with the value of segment "value" ($frequency$)
After this process, the ABlob_AAD_Snapshot.json should look like this:
{
"name": "AzureBlobStore",
"properties": {
"type": "AzureStorage",
"availability": {
"frequency": "$frequency$"
}
}
}
The problem here is that my original config.json file has more than one array (which represents file names) so I will parse more than one file and the value for "name" segment will not always be the same I mean, in this case, the value (or path) is properties.availability.frequency but it could be properties.activities.scheduler.interval or properties.activities.typeProperties.extendedProperties.WebAppClientID.
So as you can see the name and the quantity of "nodes" could change.
This is my PowerShell script:
$ScriptPath = split-path -parent $MyInvocation.MyCommand.Definition
#path to config file
$ConfigFile = "$ScriptPath\ParameterConfigOriginal.json"
#Convert the json file to PSObject
$json = Get-Content $ConfigFile | Out-String | ConvertFrom-Json
#Get all the arrays (files) in Conifg.json
$files = $json | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name
#Go through all the arrays (files)
Foreach($file in $files)
{
if( $file -eq '$schema') {continue}
#store the path of the file to be modified
$FileName = $file + ".json"
$FilePath = "$ScriptPath\LinkedServices\" + $FileName"
#Go through all the elements of the arrray
Foreach($item in $json.$file)
{
#Store the path
$name = $item.name
#Store the value
$value = $item.value
#Convert the file to be modified to PSObject
$file = Get-Content $FilePath | Out-String | ConvertFrom-Json
#======STUCK IN HERE=============
# How can dynamically navigate through the file nodes like this?
$file.properties.availability.frequency
#and set the corresponding value
$file.properties.availability.frequency = $value
}
}
I'm new in the PowerShell world and I don't know if there is a cmdlet that helps me to do what I need.
Any suggestion will be appreciated.
EDIT
Simple Path
$snapshot.properties.availability.frequency
Path with arrays
$snapshot.properties.activities[0].scheduler.frequency
this is an example of the JSON file with arrays
Destination file
and this is the result
Destination file updated
Any idea on what could be happening?
Invoke-Expression will help you.
#Go through all the arrays (files)
Foreach($file in $files)
{
$snapshot = (Get-Content ("./" + $file + ".json") | ConvertFrom-Json)
# get config corresponds to the $file
$config = Invoke-Expression ('$json.' + $file)
# set value according to the config
Invoke-Expression ('$snapshot.' + $config.name + "='" + $config.value + "'")
# $snapshot.properties.availability.frequency
# -> $Frequency$
}
Edit
You have to use ConvertTo-Json -Depth 100 to write the result to JSON file properly (specify the appropriate depth according to your JSON files.).
Without -Depth option, you will get the result like "#{type=Copy, typeProperties=;, ...}".

Access Object From JSON File in Powershell

I have a JSON file that I am reading in Powershell. The structure of the file is below.
[
["computer1", ["program1", versionX]],
["computer2", ["program2", versionY]],
["computer3", ["program3", "versionX"],
["program1", "versionZ"]
],
]
What I want in the program is use $env:computername and compare it with the computerX in the JSON file. If found a match, then iterate through and get the values of programName and ProgramVersion.
However, I don't know how to search through the objects and find ALL items under that.
This is what I have so far.
$rawData = Get-Content -Raw -Path "file.json" | ConvertFrom-Json
$computername=$env:computername
$data = $rawData -match $computername
This gives me objects under it. But how do I iterate through and get individual values?
But don't know what I do after that.
To start you need to be using a valid JSON file
{
"computer1": {
"program1": "versionX"
},
"computer2": {
"program2": "versionY"
},
"computer3": {
"program3": "versionX",
"program1": "versionZ"
}
}
Then you can access the PSObject Properties
$rawData = Get-Content -Raw -Path "file.json" | ConvertFrom-Json
$rawData.PsObject.Properties |
Select-Object -ExpandProperty Name |
ForEach-Object { IF ($_ -eq $env:COMPUTERNAME) {
Write-Host "Computer Name : " $_
Write-Host "Value : " $rawData."$_"
}
}
EDIT for Computer, Program, and Version as separate values
psobject.Properties.Name will give all the program names.
psobject.Properties.Name[0] will give the first program name.
psobject.Properties.value[0] will give the first program version value.
You need to increment the value to get second value, you can also use -1 as a shortcut for the last value.
$rawData = Get-Content -Raw -Path "file.json" | ConvertFrom-Json
$rawData.PsObject.Properties |
Select-Object -ExpandProperty Name |
ForEach-Object { IF ($_ -eq $env:COMPUTERNAME) {
$Computer = $_
$Values = $rawData.$_
}
}
$Computer
$Values.psobject.Properties
$Values.psobject.Properties.Name
$Values.psobject.Properties.Name[0]
$Values.psobject.Properties.value[0]
$Values.psobject.Properties.Name[1]
$Values.psobject.Properties.value[1]
You could also use the program name
$Values.program1
$Values.program2
$Values.program3