Add support for msdeploy package-style parameters transformations
This discussion started as a support thread here:
http://help.octopusdeploy.com/discussions/questions/2571-web-package-based-parameters
Let's say I have an App.config for a Windows Service that looks like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- snip -->
<system.net>
<mailSettings>
<smtp from="user@localdomain.org">
<network host="smtp.local" />
</smtp>
</mailSettings>
</system.net>
<!-- snip -->
</configuration>
With the built-in transformations (appSettings and connectionStrings) I cannot replace the SMTP server host.
For web projects in the past, I've used msdeploy (or web deploy) package parameters to do this transformation from environment to environment.
The idea is you create a parameters.xml file locally to your project, and then each environment has a corresponding setParameters.xml file that defines the values for the defined parameters.
So, a parameters.xml file would look like this:
<parameters>
<parameter name="Smtp Server" description="Address to server that provides smtp" defaultValue="smtp.dev" tags="" >
<parameterEntry kind="XmlFile" scope="\\Service.exe.config$" match="/configuration/system.net/mailSettings/smtp/network/@host" />
</parameter>
</parameters>
And instead of the setParameters.xml file typically used, we could just use the Octopus Variables functionality. For the example above, you'd just need a Variable for "Smtp Server".
I've put something together that works for me for now, but would prefer if something like this could be built in.
Here's what I've got for now:
A custom Powershell Script (imported into Octopus Deploy Lirary as a Script module):
[Reflection.Assembly]::LoadWithPartialName("System.Xml") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("System.Xml.XPath") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq") | Out-Null
function Sync-DeployParameters { param($variables, $pathToParameters = "parameters.xml", $fileFilters = @("*.config", "*.xml"))
$pathToParameters = (Resolve-Path($pathToParameters)).Path
if(!(Test-Path $pathToParameters)) {
throw "Could not find parameters file: $pathToParameters"
}
$parametersXml = [System.Xml.Linq.XDocument]::Load($pathToParameters)
$parameters = $parametersXml.Descendants('parameter')
foreach($parameter in $parameters) {
$key = $parameter.Attribute('name').Value
Write-Output "'$key'"
if($variables.ContainsKey($key) -eq $false) {
Write-Output "No matching variable. Skipping."
continue
}
$parameterEntry = $parameter.Element('parameterEntry')
$kind = $parameterEntry.Attribute('kind').Value
if($kind -ne 'XmlFile') {
Write-Output "parameterEntry is not of kind XmlFile. Skipping."
continue
}
$scope = $parameterEntry.Attribute('scope').Value
$xpathMatch = $parameterEntry.Attribute('match').Value
$pathsInScope = Get-PathsToConfigs $scope @($fileFilters)
if(!($pathsInScope)) {
Write-Output "Could not find scope: $scope"
}
foreach($pathInScope in $pathsInScope) {
if((Test-Path $pathInScope) -eq $false) {
continue
}
Set-XPathValue $pathInScope $xpathMatch $variables[$key]
}
}
}
function Get-PathsToConfigs { param([string]$scope, [string[]]$fileFilters)
$relativelyScoped = "\.$scope"
return Get-ChildItem . -Recurse -Include $fileFilters | Where-Object { (Resolve-Path($_.FullName) -Relative) -match $relativelyScoped } | %{ $_.FullName }
}
function Set-XPathValue { param([string]$pathToFile, [string]$xpath, [string]$value)
Write-Output "$pathToFile - $xpath='$value'"
$document = New-Object System.Xml.XmlDocument
$document.PreserveWhitespace = $true
$document.Load($pathToFile)
$navigator = $document.CreateNavigator()
$manager = New-Object System.Xml.XmlNamespaceManager($navigator.NameTable)
foreach ($nav in $navigator.Select($xpath, $manager))
{
$nav.SetValue($value)
}
$document.Save($pathToFile)
}
Export-ModuleMember -Function Sync-DeployParameters
And then inside my process (Nuget package) that deploys the Windows Service, I have Custom Powershell scripts turned on that execute as a 'Deployment script':
Sync-DeployParameters $OctopusParameters
This is incomplete; I haven't tested it much. One obvious missing feature is to use the defaultValue specified in the parameters.xml file, but that should be easy to do.
Is it possible something like this could get built into Octopus Deploy? Or did I miss the whole point and there's an easier way to do this? I've seen the substitute in files feature, but I don't like it as much because I have to keep templates around of my config files.

-
Nicholas Lechnowskyj commented
Albert, the Configuration Transforms feature is limited, it only updates appSettings and connectionString elements.
I agree that we need something more powerful, the majority of my settings are in custom configuration sections and I have to write powershell scripts to do the web.config transformations.
-
Donnie Jones commented
Thanks Gary.
I've thought about that, but I've tried to avoid having any concept of environments in the source since I feel like that's a concept best managed by Octopus Deploy. But I do agree that it solves the problem I have; I just don't like it as much!
Another reason I like the parameters.xml approach is I have about 20 projects that are already using this, so migrating those to an Octopus Deploy project would be a piece of cake.
So, I'll just stick with my custom powershell script for now. Is there a way to close this suggestion?
-
Anonymous commented
Hi Donnie, I just commented without reading your last paragraph regarding the unwanted template files. So my amended suggestion would be to use a combination of transformation files and variable substitution:
Create a transform file for each environment, e.g. app.dev.config and populate the transform elements with Octopus variables, e.g.
<network xdt:Locator="XPath(/configuration/system.net/mailSettings/smtp/network)" xdt:Transform="SetAttributes(host)" host="#{SmtpServer}" />Octopus will apply the transformations and then substitute all variables in the freshly-transformed file, as you require.
-
Donnie Jones commented
So I just tested this. It does not appear to work. I don't think the built-in transformations work for Web Deploy (msdeploy) parameterization. See a discussion of the differences here:
http://vishaljoshi.blogspot.com/2010/06/parameterization-vs-webconfig.html
-
Albert Weinert commented
this is allready in Octospus deploy, install the "Configuration transforms" on "Configure features" while Deploying the Nuget. Works like the web.config Transformation.
http://docs.octopusdeploy.com/display/OD/Configuration+files
-
Donnie Jones commented
Sorry for the formatting. Seems UserVoice doesn't support any kind of formatting what-so-ever.