Transform app.config for 3 different environment - c#

I need to be able to transform my app.config file using msbuild. I can transform the file if it is called app.DEBUG.config or app.Release.config, but I cannot if I add one called app.PROD.config.
Using regular XDT transforms msbuild recognizes different web.config files if I select a different PublishProfile
msbuild path.to.project.csproj Configuration=Release PublishProfile=DEV
Apparently app.config does not work with this same setup. I can always create a specific build configuration for a DEV.config setup but it seems useless to have a separate build confirm for one app. A hacky way to do so is to just copy the correct app.config per environment POST-BUILD.
I've attempted to use the SlowCheetah plugin, but this only seems to transform the default DEBUG and RELEASE config files, which is not appropriate since I have more than two environments. If I indeed am using this wrong, please let me know what parameter I should pass at to msbuild to choose my app.DEV.config.
The expected result would be that msbuild would transform the app.config according to the transform that is customized called app.DEV.config or the one customized for app.PROD.config. I would expect that there is a parameter that I can pass to msbuild that would allow me to use the Release configuration but a transform with a different name per environment.

I think what is confusing is that we have the ability to make compile-time config transformations and then we have deployment-time config transformations.
In general, you use compile-time config transformations to make changes to your locally-defaulted config file so that it is appropriate for a DEBUG or RELEASE configuration (or any custom configuration you define). For web.config, the tooling is built-in. For app.config, the SlowCheetah Visual Studio extension brings the same capability that we have for web.config to app.config. An example of a config transform for a RELEASE configuration is to remove the debug attribute on system.web compilation.
Deployment-time config transformations are manipulations of the config file while deploying to a specific environment (e.g. QA, PROD). Database connection strings need to change, service endpoints change, etc... For web.config, MSDEPLOY is the IIS tool of choice. For app.config, it seems we need to rely on installer technology. There are different tools for this, like WIX for example.
Anyway, I hope this short explanation of the distinction between compile-time and deployment-time config transformations helps explain why the toolset is fragmented. For more in-depth analysis, you can refer to a blog post I made on this subject: http://philippetruche.wordpress.com/2012/07/11/deploying-web-applications-to-multiple-environments-using-microsoft-web-deploy/
If choosing to use the WIX toolset to produce installers, refer to Creating Multi-Environment Windows Installers with Visual Studio 2012 and Wix.

App.config transforms
To test if transforms work you have to use real transforms. Insert-transform with appSettings block is maybe simplest one. I tested with following configuration files.
App.config:
<?xml version="1.0" encoding="utf-8" ?><configuration> <appSettings> <add key="FirstName" value="Gunnar"/> </appSettings></configuration>
App.Release.config
<?xml version="1.0"?><configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <appSettings> <add key="LastName" value="Peipman" xdt:Transform="Insert"/> </appSettings></configuration>
Configuration file after transform:
<?xml version="1.0" encoding="utf-8" ?><configuration> <appSettings> <add key="FirstName" value="Gunnar"/> <add key="LastName" value="Peipman"/> </appSettings></configuration>
Making App.config transforms work
Let’s see how to do it with console application.
Add App.config and App.Release.config to your project and fill them with content given above..
Unload console application project.
Right-click on project name and select "Edit <project file name>".
Project file is opened as XML-file and you can see what is inside
it.
Before closing tag of first property group add the following line:
<ProjectConfigFileName>App.Config</ProjectConfigFileName>
Find <ItemGroup> where App.Config is defined (<None
Include="App.Config" />) and add the following block after
App.Config node:
<None Include="App.Release.config">
<DependentUpon>App.Config</DependentUpon>
</None>
Find first <Import Project= node and add the following import as
last one to list:
<Import Project="$(VSToolsPath)\Web\Microsoft.Web.Publishing.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets" Condition="false" />
To the end of file, just before tag, paste the following
block of code:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
<ItemGroup>
<AppConfigWithTargetPath Remove="app.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetName).vshost$(TargetExt).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
Save project file, close it and reload it.
When loading project again Visual Studio may ask you about some modifications to file so all versions from Visual Studio 2010 to current are able to use your project file with no modifications to it. Agree with it because then you don’t have dependencies to Visual Studio versions.

This is what I use for this scenario:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This target will run right before you run your app in Visual Studio -->
<Target Name="UpdateWebConfigBeforeRun" BeforeTargets="Build">
<Message Text="Configuration: $(Configuration) update from web.template.$(Configuration).config"/>
<TransformXml Source="web.template.config"
Transform="web.template.$(Configuration).config"
Destination="web.config" />
</Target>
<!-- Exclude the config template files from the created package -->
<Target Name="ExcludeCustomConfigTransformFiles" BeforeTargets="ExcludeFilesFromPackage">
<ItemGroup>
<ExcludeFromPackageFiles Include="web.template.config;web.template.*.config"/>
</ItemGroup>
<Message Text="ExcludeFromPackageFiles: #(ExcludeFromPackageFiles)" Importance="high"/>
</Target>
</Project>
I have the following setup:
web.template.config
- web.template.debug.config
- web.template.production.config
- web.template.release.config etc
Should work cross pc without the need for extra plugins etc. In your scenario, you need to edit the contents to say app. instead of web.

You can transform the app.config the same as the web.config.
Use this nuget package located here https://github.com/acottais/msbuild.xdt
There are several other Nuget packages that will allow you to do this. I used this one the other day and it worked.
After installing it is as simple as Add-Transform from the VS Window.

Related

When using MSBuild to build a WPF project which files should I specify for compilation? does order matter?

I'm attempting to automate the build process of my WPF project and set up a CI system. I'm following details found in Continuous Integration in .NET by Marcin Kawalerowicz and Craig Bernston. Specifically, details in chapter 3 pertaining to build automation in MSBuild. The authors provide a sample build file for a project consisting of a single C# file, which looks similar to
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build;Deploy;Execute"
xmlns="http://schemas.microsoft.com/developer/msuild/2003">
<PropertyGroup>
<Debug Condition="'$(Delete)'==''">false</Debug>
<OutputFile>SampleMSBuild.exe</OutputFile>
<OutputDirectory>Output</OutputDirectory>
</PropertyGroup>
<ItemGroup>
**<CompileFile Include="SampleMSBuild.cs" />**
<DeleteFiles Include="SampleMSBuild.exe;SampleMSBuild.pdb" />
</ItemGroup>
<Target Name="Clean">
...
</Target>
<Target Name="Build" DependsOnTarget="Clean">
**<Csc Sources="#(CompileFiles)"**
OutputAssembly="$(OutputFile)"
EmitDebugInformation="$(Debug)" />
<Target Name="Deploy">
...
</Target>
<Target Name="Execute">
...
</Target>
</Project>
My confusion arises from the two lines of code I've surrounded in asterisks. Although I now know how to build a project with a single source file, I am unsure how to scale that up to a WPF project where there is a predefined template with many source files that are placed in different directories. Is it simply a matter of including every source file in the project like so
...
<CompileFile Include="source1.cs;source2.cs;source3.cs;...;sourceN.cs"
...
What about files in a project that are NOT source code? For example, files found in the References folder of a WPF project. I can't imagine all these are to be compiled as well. Does the complied source code communicate with the rest of the uncompiled files in the project? What exactly is the relationship between source files and other non-source files when it comes to build automation in MSBuild?

Bamboo does not transform Web.config file for any custom build configuration

Here is the scenario:
I have a website which has a web.config file along with many other environment specific config files like Web.Staging.config/Web.Release.config/Web.OnPrem.config
Now, I have configured the BeforeBuild Target in the csproj file of my website project:
<Target Name="BeforeBuild">
<TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.config" />
</Target>
This works well when I setup bamboo to build and create artifacts in Release mode (so the deployed application has the transformed web.config file from web.Release.config But, when I change the bamboo to build and create artifacts using OnPrem configuration, it does not transform the web.config file correctly.
When I say setup bamboo to build in OnPrem config, I have actually change the configuration option to below:
/p:Configuration=OnPrem and also, I have changed the BambooBuild.proj to have
<ConfigurationToBuild Include="OnPrem|Any CPU">
<FlavorToBuild>OnPrem</FlavorToBuild>
<PlatformToBuild>Any CPU</PlatformToBuild>
</ConfigurationToBuild>
What am I missing here?
Because you can only build for one active configuration at a time, whichever configuration you're building is the one that will get substituted into the $(Configuration) variable.
What I normally do is:
build (with no transforms) to produce my DLL & other software artifacts
then I have a separate build process which runs transforms only, and produces separate (in your case) Web.Staging.config, Web.Release.config & Web.OnPrem.config files
in my deployment plans for separate environments, I pick the Web.xxx.config file I need for that environment and rename it to Web.config
You can use MSBuild from the command line just to run transformations. (Note the only difference here is Web.Staging.config or Web.Release.config):
Msbuild.exe /target:Transform "/property:TransformInputFile=path\to\Web.config;TransformFile=path\to\Web.Staging.config;TransformOutputFile=path\to\artefacts\Web.Staging.config" MSBuild.xml
Msbuild.exe /target:Transform "/property:TransformInputFile=path\to\Web.config;TransformFile=path\to\Web.Release.config;TransformOutputFile=path\to\artefacts\Web.Release.config" MSBuild.xml
A MSBuild.xml file suitable for use like this would contain something like the following:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="Deploy"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<Target Name="Transform">
<TransformXml Source="$(TransformInputFile)"
Transform="$(TransformFile)"
Destination="$(TransformOutputFile)"
StackTrace="$(StackTraceEnabled)" />
</Target>
</Project>

How does Visual Studio determine whether it has to start MSBuild or not?

I've done a lot of MSBuild customization for my C++ projects in the past. The Input and Output attributes of a MSBuild target are used to determine whether the target has to be executed or not. Additionally Visual Studio uses the .tlog files (located in the intermediate directory) to determine whether MSBuild has to be invoked at all.
Now I'm working on a C# project. I wrote a simple MSBuild target which copies a file to the output directory:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyMyFile" BeforeTargets="AfterBuild" Inputs="$(ProjectDir)File.dat" Outputs="$(TargetDir)FileRenamed.dat">
<Copy SourceFiles="$(ProjectDir)File.dat" DestinationFiles="$(TargetDir)FileRenamed.dat" OverwriteReadOnlyFiles="true">
</Copy>
</Target>
</Project>
The target works as expected if the build is invoked through MSBuild.exe. The file is copied if the target file does not exist or the source file has been modified.
If I invoke the build inside Visual Studio it does not work as expected. Visual Studio does not invoke MSBuild if I delete the file from the output directory. On the other hand MSBuild is invoked every time I build the project after modifiying the source file even if there are no other changes made.
It seems that Visual Studio just compares every file from a project to the output files (.exe, .dll or .pdb). If any file in the project is newer than the output files, MSBuild is invoked. In my case MSBuild does not update the .exe file, so MSBuild is invoked again and again.
In a C++ project this behaviour is controlled by the .tlog files. Is there anything similar in a C# project?
Thanks a lot!
The answer might be no, nothing similar to the tlog mechanism. I am not 100% sure though, also because it's strange you cannot do something quite basic as this as that would mean MS basically ditched the tracker stuff for C# (and similar) projects but didn't replace it with something which can be hooked into by users.
Using procmon you can see VS getting timestamps of output and input files, but nowhere I found a way to interfere with what it treats as input and output files. It looks like VS gets a list of everything included directly in the project file (i.e. Reference/Content/Compile/.. item groups of what is shown in VS), not what is listed in Taget's Inputs/Outputs, and at the start of a build compares timstamps for just those items. If everything (well, everything as far as VS is considered) is up to date no msbuild process is launched for the build.
There is a workaround, though not super nice: if you add a 'dummy' Content item (e.g. Right-click project->Add New Item->Text File) and set it to always be copied (Right-clik text file just added->Properties->Copy to Output Directory->Copy always) then VS will always start a build and hence check your target's Inputs vs the Outputs and run if if you deleted FileRenamed.dat.
It looks like this is just poorly documented. This site shows you can easily hook up a command line tool, while lifting on the incremental features of tlog files.
To make sure the information doesn't get lost, I'll just copy over their use case, but looking at that, I think it's easy to transform into your needs. Every occurrence of dcx can be replaced by e.g. data
1. Create a definition .xml file
Define an ItemType
Link a ContentType to the ItemType
Hook up a FileExtension
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties">
<!-- Associate DXCShader item type with .hlsl files -->
<ItemType Name="DXCShader" DisplayName="DXC Shader" />
<ContentType Name="DXCShader" ItemType="DXCShader" DisplayName="DXC Shader" />
<FileExtension Name=".hlsl" ContentType="DXCShader" />
</ProjectSchemaDefinitions>
2. Create a .targets file
Include the .xml definitions file
Create a Target that depends on one of your build hooks (here: ClCompile)
Create an ItemGroup in your Target that will serve as the argument to your CustomBuild. Message, Command, AdditionalInputs and Output are meta-attributes that are relevant.
Invoke CustomBuild with MinimalRebuildFromTracking="true" and a TrackerLogDirectory to contain the tlog files. This part is the magic ingredient that makes MSBuild skip the build if your dependencies are up-to-date.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<!-- Include definitions from dxc.xml, which defines the DXCShader item. -->
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)dxc.xml" />
<!-- Hook up DXCShader items to be built by the DXC target. -->
<AvailableItemName Include="DXCShader">
<Targets>DXC</Targets>
</AvailableItemName>
</ItemGroup>
<Target
Name="DXC"
Condition="'#(DXCShader)' != ''"
BeforeTargets="ClCompile">
<Message Importance="High" Text="Building shaders!!!" />
<!-- Find all shader headers (.hlsli files) -->
<ItemGroup>
<ShaderHeader Include="*.hlsli" />
</ItemGroup>
<PropertyGroup>
<ShaderHeaders>#(ShaderHeader)</ShaderHeaders>
</PropertyGroup>
<!-- Setup metadata for custom build tool -->
<ItemGroup>
<DXCShader>
<Message>%(Filename)%(Extension)</Message>
<Command>
"$(WDKBinRoot)\x86\dxc.exe" -T vs_6_0 -E vs_main %(Identity) -Fh %(Filename).vs.h -Vn %(Filename)_vs
"$(WDKBinRoot)\x86\dxc.exe" -T ps_6_0 -E ps_main %(Identity) -Fh %(Filename).ps.h -Vn %(Filename)_ps
</Command>
<AdditionalInputs>$(ShaderHeaders)</AdditionalInputs>
<Outputs>%(Filename).vs.h;%(Filename).ps.h</Outputs>
</DXCShader>
</ItemGroup>
<!-- Compile by forwarding to the Custom Build Tool infrastructure,
so it will take care of .tlogs and error/warning parsing -->
<CustomBuild
Sources="#(DXCShader)"
MinimalRebuildFromTracking="true"
TrackerLogDirectory="$(TLogLocation)"
ErrorListRegex="(?'FILENAME'.+):(?'LINE'\d+):(?'COLUMN'\d+): (?'CATEGORY'error|warning): (?'TEXT'.*)" />
</Target>
</Project>

Replacing <appname>.exe.config with an AfterBuild doesn't apply to <appname>.vshost.exe.config

We're using an AfterBuild target in the vbproj to replace the config file depending on the selected configuration :
<Target Name="AfterBuild" Condition="'$(Configuration)' != 'Release'">
<Delete Files="$(TargetDir)$(TargetFileName).config" />
<Copy SourceFiles="$(ProjectDir)$(Configuration).config" DestinationFiles="$(TargetDir)$(TargetFileName).config" />
</Target>
As an example, let's say we have 3 configurations : Debug, Test, Release. Debug is the local debugging configuration. Test is a pre-production environment used for user-acceptance tests. Release is our production environment.
In the App.config file, we store the configuration for our Release environment. In the Debug.config file, we store the configuration for our local debugging needs. In the Test.config file, we store the configuration for our user-acceptance environment.
The goal of the AfterBuild target is to replace the Release configuration (App.config) when building/executing with the Debug configuration (Debug.config) or the Test configuration (Test.config).
Everything works as intended when we publish the application (Release, App.config) or if we build the application and launch the bin\<appname>.exe (Debug or Test).
However, if we Start the application from Visual Studio, with the Visual Studio hosting process, the correct configuration is copied to bin\<appname>.exe.config, but it seems that Visual Studio doesn't copy the correct configuration to bin\<appname>.vshost.exe.config. We tried cleaning the solution, performing a Rebuild before debugging, manually deleting the bin\<appname>.vshost.exe.config file before launching, but it seems that the hosting process always copies the configuration from the default App.config file. The same problem happens whether we try to Start with the Debug configuration or the Test configuration.
To add to the confusion, we created multiple test projects, using the same AfterBuild target, and some of them work correctly, others don't. All projects are using .Net Framework 4.5.1, but we also reproduced the problem with .Net 4.5. It doesn't seem to be caused by the project type, since we're able to reproduce the issue with a Console Application as well as a Windows Forms Application.
What could be causing the problem?
Alternatively, could we use another solution to manage the configuration for each of our environment?
Notes
We use the default App.config file for our Release environment
because ClickOnce doesn't seem to support AfterBuild targets.
We use Visual Studio Express 2013 for Windows Desktop, so we can't use any plugin like SlowCheetah.
Following Steve's suggestion, we moved the logic to the BeforeCompile target, specifying to replace the App.config depending on the selected configuration :
<Target Name="BeforeCompile">
<Delete Files="$(ProjectDir)App.config" />
<Copy SourceFiles="$(ProjectDir)$(Configuration).config" DestinationFiles="$(ProjectDir)App.config" />
<Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>
</Copy>
</Target>
It wouldn't replace the file every time, and when it did, it didn't necessarily apply to the <appname>.vshost.exe.config file. We stumbled on another answer which guided us on the right path : Can Visual Studio automatically adjust the name of other file as it does with app.config?
Adding the following <Copy> command, we achieved the desired behavior :
<!--
Copy the application's .config file, if any.
Not using SkipUnchangedFiles="true" because the application may want to change
the app.config and not have an incremental build replace it.
-->
<Copy
SourceFiles="#(AppConfigWithTargetPath)"
DestinationFiles="#(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForAdditionalFilesIfPossible)"
>
<Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>
</Copy>
We now have a config file for each configuration (Debug.config, Test.config, Release.config). Visual Studio replaces the App.config file with the correct one on each build/launch. The resulting <appname>.vshost.exe.config file contains the correct parameters.
Bonus
The underlying benefit with this solution is that we have one config file per configuration, so we can update the .vbproj file and replace, for example,
<None Include="Debug.config" />
<None Include="Release.config" />
with
<None Include="Debug.config">
<DependentUpon>App.config</DependentUpon>
</None>
<None Include="Release.config">
<DependentUpon>App.config</DependentUpon>
</None>
All config files will be grouped under the main App.config file in Visual Studio :
For me it helped to disable VisualStudio hosting process.
Remove the check mark in:
Project -> Preferences -> Debug -> Enable Visual Studio hosting process
This stops Visual Studio from overwriting the *config file.

App.config replacements for unit tests

my continuous integration server (TeamCity) is configured to run all the unit tests in our app on build. Prior to running those tests, i need to change some of the appSettings to make them valid for our CI server. I'm achieving something similar for my web project by using the deployment project provided with Visual Studio. Can i do the same for a Test project?
Thanks, Gonzalo
It's possible to use Web.config Transformations for App.config files through a workaround.
You simply have to invoke the appropriate MSBuild tasks at the right stage in your build process.Add this code snippet to your project file:
<UsingTask
TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('App.$(Configuration).config')">
<!-- Generates the transformed App.config in the intermediate directory -->
<TransformXml
Source="App.config"
Destination="$(IntermediateOutputPath)$(TargetFileName).config"
Transform="App.$(Configuration).config" />
<!-- Forces the build process to use the transformed configuration file -->
<ItemGroup>
<AppConfigWithTargetPath Remove="App.config"/>
<AppConfigWithTargetPath
Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
Then add additional App.config files to your project for each build configuration where you wish to apply a transformation. For example:
<ItemGroup>
<None Include="App.config" />
<None Include="App.Release.config">
<DependentUpon>App.config</DependentUpon>
</None>
</ItemGroup>
Related resources:
Web.config Transformation Syntax for Web Application Project Deployment
.Config File Transformation
I have created a Visual Studio add in which can be used to transform app.config in the same way that web.config is transformed. You can find the add-in, SlowCheetah, at http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5.
I have posted a blog about how to get this working on a build server as well.
I suggest that you wrap your config lookups, extract an interface and stub this when testing.

Categories

Resources