Managing Multiple Configuration File Environments with Pre-Build Events
ScottGu mentioned an idea to me last week that he'd had for managing configuration files like web.config depending on what the current build config is. Bil Simser mentioned one part of this in January and Rob Chartier offered batch file help on a mailing list in June. Since ScottGu is busy Managing Generally (he IS the General Manager) so I said I'd prove the concept for him.
Here's the general idea. It's not too hard. I'll use an ASP.NET Web Site and web.config as an example, but this will work with most any kind of project, exe's or .dll's.
1. From Visual Studio, go File | New Project, and select ASP.NET Web Application.
Note: Do NOT "New Web Site" as we want a .csproj and we're going to use a Pre-Build Event, not supported by Web Sites. I've named mine FooFoo.
2. Right click in the Toolbars and ensure that the "Standard" toolbar is showing. You'll know if you see a dropdown that says "Debug" next to one that says "Any CPU."
Click the dropdown and select "Configuration Manager."
You'll probably have Debug and Release configurations, but you can also make custom ones and base them on existing configuration. In this dialog I've made a new "Deploy" and I'll base it on the "Release" configuration.
Make sure to create a Solution Configuration AND a Project Configuration, as they are different. Here I've made one called Deploy for the Project also. If you get an error message, be aware of the "Create new project configurations" checkbox. You might get a warning if you are making a new configuration and the dialog tries to make another configuration with the same name; uncheck the checkbox if that happens.
Of course, you can have as many Configurations as you'd like.
3. Add some custom configuration stuff in web.config, like connectionStrings:
<connectionStrings> <add name="Foo" connectionString="Data Source=localhost;Initial Catalog=DatabaseName; User Id=sa;Password=debug;" providerName="System.Data.SqlClient" /> </connectionStrings>
See now I've made the password in my nonsense connectionString = "debug"? Now, create three new web.config's by CTRL-dragging the web.config on top of the project. Name them web.config.debug, web.config.deploy, and web.config.release. Make the password equal to "deploy" and "release" respectively.
4. Ok, now we've got different configuration and different configuration files. Let's create a batch file called "copyifnewer.bat" and here's the contents:
@echo off echo Comparing two files: %1 with %2 if not exist %1 goto File1NotFound if not exist %2 goto File2NotFound fc %1 %2 if %ERRORLEVEL%==0 GOTO NoCopy echo Files are not the same. Copying %1 over %2 copy %1 %2 /y & goto END :NoCopy echo Files are the same. Did nothing goto END :File1NotFound echo %1 not found. goto END :File2NotFound copy %1 %2 /y goto END :END echo Done.
Basically this batch file will copy a file over another if the files don't match. It's not strictly "copyifnewer" (like, not at all) but it does the job.
Why bother with a batch file to check for changes and not just copy over the file every time? Well, each time you copy over a web.config it restarts all the designers and running AppDomains that are watching that file. No need to copy over a file if it hasn't changed...everything will churn less.
Put this copyifnewer.bat file in the root of your project.
Why not use PowerShell? One word - speed. Batch files are fast. Full stop. This is a build, so it needs to be fast.
5. Create a Pre-build Event. Right-click on your Project and select Properties. Click Build Events and in the "Pre-build event command line" and enter this value:
"$(ProjectDir)copyifnewer.bat" "$(ProjectDir)web.config.$(ConfigurationName)" "$(ProjectDir)web.config"
Notice the magic dust, the $(ConfigurationName) project variable, that contains "Debug" or "Release" or "Deploy."
6. Build. Now if you build, you'll see in the Build Output the batch file being run and the files being copied. Because it's a Pre-Build Event it'll be seen in both the Build Output in Visual Studio .NET.
When you build within Visual Studio the currently selected item in the drop-down list is the current configuration.
The configuration value can also be passed in on the command line when building with MSBUILD.
msbuild FooFoo.sln /p:Configuration=Deploy
And there you go. The connection string in the web.config now contains deployment-specific configuration data.
Bad things are that you've got to keep web.config's in sync if there's lots of settings, but you could totally break it apart via "include files."