function Set-AppSettingPrivateJson { <# .SYNOPSIS Sets an appSetting Key/Value pair in the specified file. This does so according to the JSON appSettings.json expected schema. .DESCRIPTION Sets an appSetting Key/Value pair in the specified file. This does so according to the JSON appSettings.json expected schema. Can connect to remote computers as well. .PARAMETER Key [string] The appSetting key .PARAMETER Value [string] The appSetting value to set, if it's different than the existing file .PARAMETER filePath [string] The location to change settings in. Defaults to the global 64bit machine.config file. .PARAMETER Force [switch] Allow forcing the write of the process. This is due to the option for ShouldProcess. .PARAMETER UpdateOnly [switch] Only updates a key's value if it exists. Will not create the missing AppSetting key. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact='None')] param ( [Parameter(Mandatory = $true)] [string]$Key, [Parameter(Mandatory = $true)] [AllowEmptyString()] [string]$Value, [Parameter(Mandatory = $false)] [Alias("Path")] [string]$FilePath, [Parameter(Mandatory = $false)] [switch]$Force, [Parameter(Mandatory = $false)] [switch]$UpdateOnly ) $logLead = (Get-LogLeadName) $dirty = $false $json = (ConvertFrom-Json -InputObject (Get-Content -Path $FilePath -Raw)) # appsettings.json has a magic trick for us # if I specify Parent:Child then I'm looking for something like # { "Parent": { "Child": value } } # if I specify Parent.Child then I'm looking for something like # { "Parent.Child": value } # So we want to be able to do a depth search by the colon'd parts $keyNodeList = $Key.Split(':') if (Test-IsCollectionNullOrEmpty $keyNodeList) { throw "$logLead : For config file [$FilePath] - No valid key specified - Can not continue. Key was [$Key]" } foreach($node in $keyNodeList) { if ([string]::IsNullOrWhiteSpace($node)) { throw "$logLead : For config file [$FilePath] - Found invalid node element [$node] in Key path [$Key]" } } $lastNode = "" $tempNode = $json foreach($node in $keyNodeList) { Write-Verbose "$logLead : Checking `$tempNode [$tempNode] for property `$node [$node]" # If the UpdateOnly switch is specified, and an AppSetting was not found, early out of saving anything. if ($UpdateOnly -and ($null -eq $tempNode.$node)) { Write-Warning "$logLead : For config file [$FilePath] - Could not find specified Key [$Key]. Not creating AppSetting node because UpdateOnly was specified. Exiting.." return } # TODO: This doesn't handle the case of "the existing node is a value and the asked-for value is a complex object" # Right now this just does nothing in that case, but it should error or warn if ($null -eq $tempNode.$node) { # If this is not the last entry in the list, keep adding subobjects so we can add values if ($node -ne $keyNodeList[-1]) { Write-Host "$logLead : Could not find sub-element with key [$node]. Adding one so we can add sub-elements." $tempNode | Add-Member -MemberType NoteProperty -Name $node -Value (New-Object -TypeName "PSCustomObject") } else { # Otherwise set it to the value that we wanted to add since we didn't have a value yet anyways Write-Host "$logLead : Could not find appSetting Key [$node]. Creating one and setting value to [$Value]." $tempNode | Add-Member -MemberType NoteProperty -Name $node -Value $Value } $dirty = $true } else { # if it's the last node in our list and the value doesn't match, set the value here if ($node -eq $keyNodeList[-1]) { if ($tempNode.$node -cne $Value) { Write-Host "$logLead : Found appSetting [$node], changing value from [$($tempNode.$node)] to [$Value]." $tempNode.$node = $Value $dirty = $true } } else{ if ($tempNode.$node.GetType().Name -ne "PSCustomObject") { Write-Error "$logLead : Asked to add a subproperty [$node] to element [$lastNode] but the element [$lastNode] can not take properties" return } } } $tempNode = $tempNode.$node $lastNode = $node } # Only useful when debugging. Don't write typically. $debugFinalValue = ConvertTo-Json -InputObject $json -Depth 100 -Compress:$true Write-Host "$logLead [$debugFinalValue]" if($dirty) { Write-Verbose "$logLead : Saving config to path [$FilePath]" if ($Force -or $PSCmdlet.ShouldProcess("Ready to write the file [$FilePath] contents?")) { Set-Content -Path $FilePath -Value ($json | Format-Json) } else { Write-Host "$logLead : When confirmed, will write to file [$FilePath]" } } else { Write-Verbose "$logLead : No value changes were made to $FilePath. Not updating the file." } }