ps/Modules/Alkami.PowerShell.Configuration/Private/Set-AppSettingPrivateJson.ps1
2023-05-30 22:51:22 -07:00

128 lines
5.2 KiB
PowerShell

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 = "<root>"
$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."
}
}