It's a pretty common requirement that deploying instances of Sitecore will require slightly different configuration on different servers. Different roles, like content management and content deployment, will require different settings and features to work. So it's not surprising that there are a variety of approaches to how you manage this configuration in your projects.
In the past I've often made use of separate config files, where you have a file for "config changes needed on all servers" and then further files for "config changes needed for CM servers", and even down to the level of "config changes needed on server CD01" if necessary. This works fine if your deployment process understands which files should go on which servers.
Recently, however, Sitecore have started to offer a "role based configuration" approach in the configuration of v9 – so you can deploy a single config file and the server can pick and choose sections of its configuration based on what role it is performing. But back in the real world, most of us are still supporting V8 (and older) sites, so is it possible for them to adopt something similar to this idea? Here's one approach that achieves something similar:
<configuration xmlns:role="RoleManagement" xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <settings> <setting name="CD-Only-Setting" role:RemoveOn="CM"> <patch:attribute name="value">some value</patch:attribute> </setting> </settings </sitecore> </configuration>
Using a namespace ensures our changes to the XML won't clash with anything else that's in the file – though it does make processing it a bit trickier, as I'll get to later.
function Update-ConfigFile { param ( [string]$configFile, [string[]]$roles ) process { $xml = New-Object System.Xml.XmlDocument $xml.Load($configFile) $xns = New-Object System.Xml.XmlNamespaceManager $xml.NameTable $xns.AddNamespace("role", "RoleManagement") $elements = $xml.DocumentElement.SelectNodes("descendant::*[@role:RemoveOn]", $xns) Write-Host "Found $($elements.Count) elements to process in $configFile" $count = 0 foreach($element in $elements) { $value = $element.SelectSingleNode("@role:RemoveOn", $xns).Value if($roles -match $value) { $count = $count + 1 $element.ParentNode.RemoveChild($element) | Out-Null } else { $element.Attributes.RemoveNamedItem("RemoveOn", "RoleManagement") | Out-Null } } Write-Host "Made $count removals in $configFile" $xml.Save($configFile) } }
It takes a path to a config file, and an array of role names that this server performs. The config file gets loaded into memory as an
XmlDocument
, and the custom namespace gets registered. This is necessary, because without an
XmlNamespaceManager
which knows about the namespaces you're dealing with, any xpath queries you try to run that use your namespace will fail.
The code can then select any element which has a
role:RemoveOn
attribute, across the whole document. For each element found, it checks if that value is contained in the set of roles passed in. If there's a match, the entire element can be removed. However if there isn't a match then all that needs removing is the custom attribute.
And finally, once all the changes have been made, the file can be saved.
So running this code against the sample above on a "CM" sever gets:
<configuration xmlns:role="RemoveOn" xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <settings> </settings> </sitecore> </configuration>
<configuration xmlns:d3p1="RoleManagement" xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <settings> <setting name="CD-Only-Setting" d3p1:RemoveOn="CM"> <patch:attribute name="value">some value</patch:attribute> </setting> </settings </sitecore> </configuration>
That's still perfectly valid XML, but Sitecore doesn't like having any of this left in the config files. So any left-over custom namespace stuff needs to be tidied up. Normally XML processing would be the right way to go about this, but because the custom namespace ends up with an unpredictable name if you run XDT, plain old text processing ends up easier:
function Remove-UnwantedNamespaces { param ( [string]$file ) process { $txt = Get-Content $file $expr = '(xmlns:[^=]*="RoleManagement"\s*)|([\w]*:RemoveOn=".*?"\s*)' $result = $txt -replace $expr, "" $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($file, $result, $Utf8NoBomEncoding) } }
This loads the text out of the file specified, runs a regular expression over it, and then saves the data back to disk being explicit about saving the file without a UTF8 byte order mark – since I found that Sitecore gets unhappy if that's not done correctly.
The regular expression looks a bit complicated, but it's just checking for one of two terms. It will match either the
xmlns
declaration at the top of the file, no matter what namespace name is declared, or it will match an instance of the custom
RemoveOn
attribute no matter what namespace prefix is used. If either of these is found, the entire attribute will be removed including its value.
param( [Parameter(Mandatory=$true)] [string]$configFolder, [Parameter(Mandatory=$true)] [string]$configPattern, [Parameter(Mandatory=$true)] [string]$currentRoles ) function Split-RoleString { param ( [string] $roleString ) process { $splitString = $roleString -split "," return $splitString | % { $_.Trim() } } } $roles = Split-RoleString $currentRoles $filesToProcess = Get-ChildItem -Path $configFolder -Filter $configPattern -Recurse | Select-Object -ExpandProperty FullName Write-Host "Start: Updating config files to remove instance-specific features" foreach($file in $filesToProcess) { Update-ConfigFile $file $roles Remove-UnwantedNamespaces $file } Write-Host "Done: Updating config files to remove instance-specific features"
The script takes three parameters: The folder config files live in, the naming pattern for finding the relevant files and the roles this server performs.
The roles can be a comma-separated string, and the
Split-RoleString
splits that out into an array of roles. Since the logic here works on the basis removing things that match, if you have servers with mixed roles you need to name them with a special role name. Passing "CD,CM" will cause config to be deleted that match either "CD" or "CM".
The code can then fetch the set of files to process (by saying "find all files matching the supplied pattern, in any folder under the path provided). Each of these files can then be processed in turn, buy calling the main replacement function and the function to remove any left-over custom XML.
So the release process can run the code with something like:
.\ConfigServerRole.ps1 "c:\inetpub\mysite\website\App_Config\" "*.config" "CM"
If you want to experiment with the code above, it's all available as a gist.
Editied to add: Jason St-Cyr points out that Alen Pelin did some interesting work on how config roles could apply to older versions of Sitecore via the "change Sitecore" route. That may be of interest to you too...↑ Back to top