views:

3520

answers:

2

In order to handle settings for different deployment targets, I moved application settings from app.config to its own file and included that file in app.config via configSource. I also created a settings file for each target.Here is an illustration:

Project A
   app.config (references settings.config)
   settings.config 
   settings.Release.config
   settings.Debug.config

During post-build, I copy the appropriate settings.{configuration}.config to the output directory. This is working fine so far and I can see settings.config file in the project output directory containing settings for the current build configuration: Release, Debug, etc.

However, I am having a problem with the setup project that I have for this project (Project A). Initially, it was not including settings.config file. So I set the build action for settings.config file as Content and I added content files from Project A to the setup project. This ensured that settings.config file was included in the setup. However, since the setup project appears to be picking settings.config file from the project directory instead of the output directory, settings.config file included in the setup is not what it should be. I want the one from the output directory to be included in the setup program since that one is the correct one for the current build configuration. I tried the following:

  • Added settings.config as a file to the setup project. However, it seems like I can only specify absolute path. So when I add it from the output directory of a particular build configuration (..bin\debug\settings.config), it does not work in other build configuration since (..bin\debug\settings.config) does exist in the directory specified. I looked into using relative paths or dynamic paths in the setup project where the build configuration could be specifed as part of the path but I could not find anything.

I considered using pre-build event to actually modify settings.config file in the project directory and then have it copied over the output directory by setting its 'Copy to Output Directory' to copy always or copy if newer. This should ensure that the appropriate settings.config is copied to the output directory just like the post-build based solution and should also ensure that the contents of settings.config file is updated before the setup project includes it. However, I don't like this solution because I would have to make sure settings.config file is writeable before I can make any changes since it is source controlled. If it is readonly, then I need to flip it to writeable, make changes, and then set it to readonly again. It is adding extra complexity.

I was wondering if anyone has a better idea or knows a setup project trick that allows me to include settings.config file appropriate for the current build configuration in the setup program.

Thanks

+1  A: 

If I had to approach this problem, I'd start by asking the following question:

Why does settings.config have to be under source code control if settings.Debug.config or settings.Release.config provide the same information?

The answer, if I read your question correctly, is because you needed to force a settings.config file to appear as part of the build output. I'm guessing this is because your setup project is using the built in "Primary output" choice.

What you can do instead is add that file to your setup project as an explicit file reference. Right-click on the setup project and choose add / file, then select the file you want to include. As you'll notice (unless it's been fixed in VS2008 which sadly I'm not yet allowed to use at work), there is a very annoying limitation placed on manually added files - there is no way to make the path build configuration aware. You can work around that by copying the appropriate settings.config file to a common location (e.g. bin/Configuration) and picking it up from there. This does limit you to building Debug and Release versions sequentially, rather than in parallel, but for many this probably isn't a huge problem.

If you aren't required to use VS setup projects, I strongly encourage you to take a look at WiX (Windows Installer XML - see http://wix.sourceforge.net/ for more information). That will easily allow you to accomplish what is necessary, although if you are unfamiliar with the internal workings of Microsoft Installer the initial learning curve could be a little steep. Microsoft use WiX themselves for some pretty significant setup tasks (e.g. Office 2007, SQL Server, etc.). It had been hoped that WiX would become part of Visual Studio (for VS 2010), but sadly that is no longer the case.

Richard J Foster
Thanks for the answer. settings.config does not need to be under source control. I was including it so that it is part of the output and also can be included in the installer as a resource. The problem with adding it as a file reference from a common location is that the path of the file is absolute in the installer (C:\MyWorkspace\Solution\ProjectA\Config\settings.config). However, developers have different workspaces and the build machine (TeamBuild) has its own directory structure. So that path unless relative will be invalid and that's the problem I was having. Any way to make it relative?
Mehmet Aras
Yuck! It's been so long since I used VS Setup projects that I'd forgotten that problem. In the deployment project properties, there is a "Search Path". If you add the appropriate relative path there it should find it... at least it did on my machine when I renamed the parent folder.
Richard J Foster
+1  A: 

I decided to go about achieving the same result (being able to have different configuration settings for different target environments) in a different way. So here is how I implemented it and it is working great. I read some of the posts here at SO about XmlMassUpdate task from MSBuild Community Tasks and decided to utilize it. Here is what I did:

1) For each project that needs to have different settings depending on the target environment, I added an xml file called app.config.substitutions.xml or web.config.substitutions.xml to the project. So, the project looked like

Project A   
 app.config 
 app.config.substitutions.xml

app.config.substitutions.xml file has the settings substitutions that XmlMassUpdate will process and apply to app.config file. Below is a sample substitution file that I use:

<configuration xmlns:xmu="urn:msbuildcommunitytasks-xmlmassupdate">
  <substitutions>
    <Development>
      <appSettings>
        <add xmu:key="key" key="SomeSetting" value="DevValue" />
      </appSettings>
    </Development>
    <Test>
      <appSettings>
        <add xmu:key="key" key="SomeSetting" value="TestValue" />
      </appSettings>
    </Test>
    <Release>
      <appSettings>
        <add xmu:key="key" key="SomeSetting" value="ReleaseValue" />
      </appSettings>
    </Release>
  </substitutions>
</configuration>

For details on how to specify substitutions, take a look at the documentation for XmlMassUpdate or just do a search on it.

2) Now I need to run XmlMassUpdate as part of build automation (TeamBuild/MSBuild). So in BeforeCompile in TeamBuild build definition file (basically a proj file), I added the following to run XmlMassUpdate on config files that have a corresponding .substitution.xml file

  <PropertyGroup>
    <SubstitutionFileExtension>.substitutions.xml</SubstitutionFileExtension>
    <TargetEnvironment>Test</TargetEnvironment>
  </PropertyGroup>

  <Target Name="BeforeCompile" Condition="'$(IsDesktopBuild)'!='true'">
    <CreateItem Include="$(SolutionRoot)\**\app.config;$(SolutionRoot)\**\web.config">
      <Output ItemName="ConfigurationFiles" TaskParameter="Include"/>
    </CreateItem>
    <CreateItem Include="@(ConfigurationFiles)" Condition="Exists('%(FullPath)$(SubstitutionFileExtension)')">
      <Output ItemName="ConfigFilesWithSubstitutions" TaskParameter="Include"/>
    </CreateItem>
    <Message Text="Updating configuration files with deployment target specific settings..."/>
    <XmlMassUpdate
      ContentFile="%(ConfigFilesWithSubstitutions.FullPath)"
      SubstitutionsFile="%(ConfigFilesWithSubstitutions.FullPath)$(SubstitutionFileExtension)"
      ContentRoot="/configuration"
      SubstitutionsRoot="/configuration/substitutions/$(TargetEnvironment)"/>   
  </Target>

Note that config files are read-only during the build, I make sure to set them writeable before running this task. I actually have another custom MSBuild task that runs before XmlMassUpdate that handles common settings throughout all of the config files such as connection strings. That task makes the config files writeable. I also don't check modified config files back to the source control. They're (appropriate config file for the deployment target) included in the installer.

Mehmet Aras