Short answer: Yes you can. But it is difficult".
Long answer: When we deploy sites to destinations, we have the usual web.test.config and web.prod.config. This worked fine until we introduced log4net.test.config and log4net.prod.config. MSBuild will not automatically go through and replace all of these. It will only work with web.config.
If you want nitty gritty to go to the last piece of code. It shows the functions that need to be taken with one configuration and replaced with a replacement. But ... it will make more sense if I describe the whole process.
Process:
- Msbuild creates a package of zip files for the site.
- We wrote a custom .net application that will take this zip file and replace the configuration on each of the files. Restart the zip file.
- Run the msdeploy command to deploy the packaged file.
MSbuild will not automatically replace all additional configs. Interestingly, MSBuild will remove any "additional" configs. This way your log4net.test.config will disappear after building it. So, the first thing you need to do is say msdbuild so that these additional files are in place.
You need to modify the vbProj file to include the new parameter:
<AutoParameterizationWebConfigConnectionStrings>False</AutoParameterizationWebConfigConnectionStrings>
Open the vbProj file for the web application in your favorite text editor. Go to each deployment configuration that you want to apply this application to (release, prod, debug, etc.) and add this configuration to it. Here is an example of our "release" configuration.
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> ... <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <DefineDebug>false</DefineDebug> <DefineTrace>true</DefineTrace> <Optimize>true</Optimize> <OutputPath>bin\</OutputPath> <DocumentationFile>Documentation.xml</DocumentationFile> <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022,42353,42354,42355</NoWarn> <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> <DeployIisAppPath>IISAppPath</DeployIisAppPath> <AutoParameterizationWebConfigConnectionStrings>False</AutoParameterizationWebConfigConnectionStrings> </PropertyGroup> ... </Project>
So, now msbduild will build the project and store these additional files in place, rather than perform replacements. Now you have to manually execute them.
We wrote a .net application that will keep track of these new zip files. I wrote code that will scroll through the entire zip package and find any configs that match {configname}. {Env} .config. He will remove, replace and return them. To perform a real replacement, we use the same DLL as MSDeploy. I also use Ionic.Zip to work with the zip file.
So add a link to:
Microsoft.Build.dll Microsoft.Build.Engine.dll Microsoft.Web.Publishing.Tasks (possibly, not sure if you need this or not)
Import:
Imports System.IO Imports System.Text.RegularExpressions Imports Microsoft.Build.BuildEngine Imports Microsoft.Build
Here is the code that rotates through the zip file
specificpackage = "mypackagedsite.zip" configenvironment = "DEV" 'stupid i had to pass this in, but it the environment in web.dev.config Directory.CreateDirectory(tempdir) Dim fi As New FileInfo(specificpackage) 'copy zip file to temp dir Dim tempzip As String = tempdir & fi.Name File.Copy(specificpackage, tempzip) ''extract configs to merge from file into temp dir 'regex for the web.config 'regex for the web.env.config '(?<site>\w+)\.(?<env>\w+)\.config$ Dim strMainConfigRegex As String = "/(?<configtype>\w+)\.config$" Dim strsubconfigregex As String = "(?<site>\w+)\.(?<env>\w+)\.config$" Dim strsubconfigregex2 As String = "(?<site>\w+)\.(?<env>\w+)\.config2$" Dim MainConfigRegex As New Regex(strMainConfigRegex, RegexOptions.Compiled Or RegexOptions.IgnoreCase) Dim SubConfigRegex As New Regex(strsubconfigregex, RegexOptions.Compiled Or RegexOptions.IgnoreCase) Dim SubConfigRegex2 As New Regex(strsubconfigregex2, RegexOptions.Compiled Or RegexOptions.IgnoreCase) Dim filetoadd As New Dictionary(Of String, String) Dim filestoremove As New List(Of ZipEntry) Using zip As ZipFile = ZipFile.Read(tempzip) For Each entry As ZipEntry In From a In zip.Entries Where a.IsDirectory = False For Each myMatch As Match In MainConfigRegex.Matches(entry.FileName) If myMatch.Success Then 'found main config. 're-loop through, find any that are in the same dir as this, and also match the config name Dim currentdir As String = Path.GetDirectoryName(entry.FileName) Dim conifgmatchname As String = myMatch.Groups.Item("configtype").Value For Each subentry In From b In zip.Entries Where b.IsDirectory = False _ And UCase(Path.GetDirectoryName(b.FileName)) = UCase(currentdir) _ And (UCase(Path.GetFileName(b.FileName)) = UCase(conifgmatchname & "." & configenvironment & ".config") Or UCase(Path.GetFileName(b.FileName)) = UCase(conifgmatchname & "." & configenvironment & ".config2")) entry.Extract(tempdir) subentry.Extract(tempdir) 'Go ahead and do the transormation on these configs Dim newtransform As New doTransform newtransform.tempdir = tempdir newtransform.filename = entry.FileName newtransform.subfilename = subentry.FileName Dim t1 As New Threading.Tasks.Task(AddressOf newtransform.doTransform) t1.Start() t1.Wait() GC.Collect() 'sleep here because the build engine takes a while. Threading.Thread.Sleep(2000) GC.Collect() File.Delete(tempdir & entry.FileName) File.Move(tempdir & Path.GetDirectoryName(entry.FileName) & "/transformed.config", tempdir & entry.FileName) 'put them back into the zip file filetoadd.Add(tempdir & entry.FileName, Path.GetDirectoryName(entry.FileName)) filestoremove.Add(entry) Next End If Next Next 'loop through, remove all the "extra configs" For Each entry As ZipEntry In From a In zip.Entries Where a.IsDirectory = False Dim removed As Boolean = False For Each myMatch As Match In SubConfigRegex.Matches(entry.FileName) If myMatch.Success Then filestoremove.Add(entry) removed = True End If Next If removed = False Then For Each myMatch As Match In SubConfigRegex2.Matches(entry.FileName) If myMatch.Success Then filestoremove.Add(entry) End If Next End If Next 'delete them For Each File In filestoremove zip.RemoveEntry(File) Next For Each f In filetoadd zip.AddFile(f.Key, f.Value) Next zip.Save() End Using
Finally, but most importantly, we are actually doing a replacement for web.configs.
Public Class doTransform Property tempdir As String Property filename As String Property subfilename As String Public Function doTransform() 'do the config swap using msbuild Dim be As New Engine Dim BuildProject As New BuildEngine.Project(be) BuildProject.AddNewUsingTaskFromAssemblyFile("TransformXml", "$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll") BuildProject.Targets.AddNewTarget("null") BuildProject.AddNewPropertyGroup(True) DirectCast(BuildProject.PropertyGroups(0), Microsoft.Build.BuildEngine.BuildPropertyGroup).AddNewProperty("GenerateResourceNeverLockTypeAssemblies", "true") Dim bt As BuildTask bt = BuildProject.Targets("null").AddNewTask("TransformXml") bt.SetParameterValue("Source", tempdir & filename) bt.SetParameterValue("Transform", tempdir & subfilename) bt.SetParameterValue("Destination", tempdir & Path.GetDirectoryName(filename) & "/transformed.config") 'bt.Execute() BuildProject.Build() be.Shutdown() End Function End Class
Like I said ... it's hard, but it can be done.