Doing agile development means deploying your application very frequently into many environments. You might have several environments that all have different configuration settings. Changing these settings by hand results in time consuming mistakes. I have built a couple different console deployment tools over the years that handled this. Usually you would run the console tool with a command line argument that would specify the environment that you wanted to configure and this tool would look up all the settings in a property file and make the changes in the app.config or web.config file. I thought it would be fun to do something similar using powershell.
So what do I want this script to do?
- Keep environment specific settings in a property file
- Support having 1 to N property files (i.e. Dev, Test, Build etc) for a project that is accepted as a parameter into the script
- The web.config or app.config file is modified with the values that come from the property file
- Support changing connection strings and application settings in version 1 of this script
The first steps in creating this script is to have a set of functions that can easily be used to set the entries in a config file. I would use this script in all my projects that needed to have the ability to set connection and application settings. This library of useful functions will be called Xml-Config.ps1
function Set-ConnectionString([string]$fileName, [string]$outFileName, [string]$name, [string]$value)
{
# Load the config file up in memory
[xml]$a = get-content $fileName;
# Find the connection string to change
$a.configuration.connectionstrings.selectsinglenode("add[@name='" + $name + "']")
.connectionString = $value
# Write it out to the new file
Format-XML $a | out-file $outFileName
}
function Set-ConnectionString
([string]$fileName, [string]$outFileName, [string]$name, [string]$value)
{
# Load the config file up in memory
[xml]$a = get-content $fileName;
# Find the cennection string to change
$a.configuration.connectionstrings.selectsinglenode("add[@name='" + $name + "']")
.connectionString = $value
# Write it out to the new file
Format-XML $a | out-file $outFileName
}
function Set-ApplicationSetting
([string]$fileName, [string]$outFileName, [string]$name, [string]$value)
{
# Load the config file up in memory
[xml]$a = get-content $fileName;
# Find the app settings item to change
$a.configuration.appSettings.selectsinglenode("add[@key='" + $name + "']").value = $value
# Write it out to the new file
Format-XML $a | out-file $outFileName
}
function Format-XML ([xml]$xml, $indent=2)
{
$StringWriter = New-Object System.IO.StringWriter
$XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter
$xmlWriter.Formatting = "indented"
$xmlWriter.Indentation = $Indent
$xml.WriteContentTo($XmlWriter)
$XmlWriter.Flush()
$StringWriter.Flush()
Write-Output $StringWriter.ToString()
}
Next I needed a script file that was specific to the software project. I wouldn’t be re-using this script from project to project as it has very specific details that only apply to one project. For example a software project might have a web.config for the web application but an app.config for a windows service. It would be the job of this second script to know where these configs are located and tie the property values to the functions above. In the following example the web.config has two settings that I want to change at deployment time (connection string and application setting). These settings will be different for Test, Build and Development environments. This script will be called dev.ps1.
dev.ps1
param([string]$propertyFile)
$workDir = Get-Location
. $workDir\Xml-Config.ps1
. $workDir\$propertyFile.ps1
# Change the connection string
Set-ConnectionString "web.config" "FMS_DB" $connectionString
# Change the app setting for the path to the backups
Set-ApplicationSetting "web.config" "DatabaseBackupRoot" $backupPath
Next I needed to create the property files that represented each environment. The following property file was called Test.ps1.
[string]$connectionString = "Data Source=(local); Database=FMS_TST; Integrated Security=true;"
[string]$backupPath = "c:\data\test"
So now the dev.ps1 script can be called passing in the environment that is being deployed. In the following example the Test environment is being deployed.
./dev.ps1 -propertyFile Test
Conclusion
I have shown you a simple way to use powershell to easily configure your application by using property files. This technique can also be used in the automated build process to set the configuration before running integration tests. I plan on expanding on this technique and exposing functions to set other frequently used configuration settings (logging details, WCF Endpoints, Other XML configurations). Powershell is very powerful and makes automating complex tasks easier with less code. In my command line console applications that performed the same function it would take a lot more lines of code to achieve the same results.