ps/Modules/Alkami.PowerShell.Configuration/Private/Set-AppSettingPrivate.ps1

162 lines
7.1 KiB
PowerShell
Raw Normal View History

2023-05-30 22:51:22 -07:00
function Set-AppSettingPrivate {
<#
.SYNOPSIS
Sets an appSetting Key/Value pair in the specified file. Filepath defaults to the 64 bit machine config.
.DESCRIPTION
Set an appSetting key/value pair in the specified config file.
Will default to the global 64 bit machine.config file if no config file value specified.
Will not tickle files where no values have changed.
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 = (Get-DotNetConfigPath -use64Bit $true),
[Parameter(Mandatory = $false)]
[switch]$Force,
[Parameter(Mandatory = $false)]
[switch]$UpdateOnly
)
$logLead = (Get-LogLeadName)
$dirty = $false
## Beware of the case of more than one appSettings element in case we run into issues, le sigh
## Let's go ahead and let the person reading the logs know that there were more than one section
## In the case of multiple found, we want to save the shortest-path to add our settings to in the future
## We should track that as we calculate our paths
$shortestAppSettingsPathSize = [int]::MaxValue # this keeps us having to do 0 checks as well
$appSettingsCollection = @($xml.SelectNodes('//appSettings'))
$preferredAppSettingsLocation = $appSettingsCollection[0]
if ($appSettingsCollection.Count -gt 1) {
Write-Warning "$logLead : More than one appSettings section found to manipulate in [$FilePath]"
foreach($appSettingElement in $appSettingsCollection) {
$fullPath = $appSettingElement.Name
$parentElement = $appSettingElement.ParentNode
while ($null -ne $parentElement) {
if($parentElement.Path) {
$fullPath = $parentElement.Name + '[path=' + $parentElement.Path + ']/' + $fullPath
} else {
$fullPath = $parentElement.Name + '/' + $fullPath
}
$parentElement = $parentElement.ParentNode
}
if ($shortestAppSettingsPathSize -lt $fullPath.length) {
$preferredAppSettingsLocation = $appSettingElement
$shortestAppSettingsPathSize = $fullPath.length
}
Write-Host $fullPath
}
}
if (Test-IsCollectionNullOrEmpty $appSettingsCollection) {
## We don't have an appSettings element to work with
## Let's add one and then store it for later use
Write-Warning "$logLead : No appSettings section found to manipulate in [$FilePath], creating one on <configuration> element"
[void]$xml.SelectSingleNode("configuration").AppendChild($xml.CreateElement("appSettings"))
$preferredAppSettingsLocation = @($xml.SelectNodes('//appSettings'))[0]
$dirty = $true
}
## Now that we have a location to store any settings, let's go see if we have an existing value to update first
$existingSettingValuesByKey = @($xml.configuration.SelectNodes("//appSettings/add[@key='$key']"))
## Crap, we found more than one element with the same key.
## We can't edit this, but honestly the system can't work with it this way either
## Something will absolutely break, but it doesn't have to be us today
if ($existingSettingValuesByKey.length -gt 1) {
Write-Error "$logLead : Found too many nodes with the same Key value in [$FilePath]!!"
foreach($appSettingElement in $existingSettingValuesByKey) {
$fullPath = $appSettingElement.Name
$parentElement = $appSettingElement.ParentNode
while ($null -ne $parentElement) {
if($parentElement.Path) {
$fullPath = $parentElement.Name + '[path=' + $parentElement.Path + ']/' + $fullPath
} else {
$fullPath = $parentElement.Name + '/' + $fullPath
}
$parentElement = $parentElement.ParentNode
}
Write-Host "$logLead : Duplicate node found at: $fullPath"
$appSettingElement.Value = $value
$dirty = $true
}
}
# If the UpdateOnly switch is specified, and an AppSetting was not found, early out of saving anything.
if ($existingSettingValuesByKey.length -eq 0 -and $UpdateOnly) {
Write-Host "$logLead : Could not find appSetting key '$key'. Not creating AppSetting node because UpdateOnly was specified. Exiting.."
return
}
## Now we either have a value in $existingSettingValuesByKey or we don't
## If we have one, we set that value to the requested value
## If we don't have an element there, we create one and stuff it on $preferredAppSettingsLocation
## We should track our dirty status so we only update the file if it has to be touched
## This last part is important for web.config which will bounce a webapp
if ($existingSettingValuesByKey.length -eq 0) {
## We don't have a value yet in the file, let's add it to that $preferredAppSettingsLocation we found before
Write-Host "$logLead : Could not find appSetting key '$key'. Creating one and setting value to '$value'."
$dirty = $true
$appSettingElement = $xml.CreateElement("add")
$appSettingElement.SetAttribute("key", $key)
$appSettingElement.SetAttribute("value", $value)
[void]$preferredAppSettingsLocation.AppendChild($appSettingElement)
} else {
## We have the key already, let's update the value
$existingValue = @($existingSettingValuesByKey)[0].Value
## Ensure that if the case is changed that we for sure update the value (CNE compare)
if ($existingValue -cne $value) {
Write-Host "$logLead : Found appSetting '$key', changing value from '$existingValue' to '$value'."
$dirty = $true
}
@($existingSettingValuesByKey)[0].Value = $value
}
if($dirty) {
Write-Verbose "$logLead : Saving config to path $FilePath"
if ($Force -or $PSCmdlet.ShouldProcess("Ready to write the file [$FilePath] contents?")) {
## Make sure the file gets saved with NoBOM
$utfNoBOM = New-Object System.Text.UTF8Encoding($false)
Save-XMLFile $FilePath $xml.OuterXml $utfNoBOM
} else {
Write-Host "$logLead : When confirmed, will write to file [$FilePath] with contents [[$($xml.OuterXml)]]"
}
} else {
Write-Verbose "$logLead : No changes were made to $FilePath"
}
}