log4net config transform in VS2013 - is it possible? - visual-studio-2013

is it achievable to have log4net.config to be transformed in VS2013? I'm getting the following error:
Error 31 "App.config;obj\Release\app.exe.config" is an invalid value for the "ConfigFile" parameter of the "GenerateApplicationManifest" task. Multiple items cannot be passed into a parameter of type "Microsoft.Build.Framework.ITaskItem".

just found the answer.. manually edit the project file
<Target Name="ApplyLoggingConfiguration" BeforeTargets="BeforeBuild" Condition="Exists('log4net.$(Configuration).config')">
<TransformXml Source="log4net.config" Transform="log4net.$(Configuration).config" Destination="log4net.config" />
</Target>
How to transform log4net config like web.config?

Related

Why does overriding the Build target in msbuild work for C++ projects but fail for C# projects?

I am overriding the Build target like this in my file OverrideBuild.targets:
<Target Name="OriginalBuild" DependsOnTargets="$(BuildDependsOn)">
<Message Text="Finished running target OriginalBuild" Importance="High" />
</Target>
<Target Name="Build" >
<CheckArtifacts ProjectGuid = "$(ProjectGuid)" SolutionPath = "$(SolutionPath)" >
<Output PropertyName = "ArtifactsHaveChanged" TaskParameter = "Result" />
</CheckArtifacts>
<Message Text="ArtifactsHaveChanged = $(ArtifactsHaveChanged)" Importance="high" />
<!-- if the artifacts.props file has not just been updated then we can run the original build target -->
<Message Condition="'$(ArtifactsHaveChanged)' == 'false'" Text="Running target OriginalBuild" Importance="High" />
<CallTarget Condition="'$(ArtifactsHaveChanged)' == 'false'" Targets="OriginalBuild" />
<!-- Otherwise we need to run a new msbuild to avoid using an out-of-date cached version of the artifacts.props file.
To force the msbuild process not to use the cached values from this process we must pass at least one property.
-->
<Message Condition="'$(ArtifactsHaveChanged)' == 'true'" Text="Running target OriginalBuild in nested msbuild" Importance="High" />
<MSBuild Condition="'$(ArtifactsHaveChanged)' == 'true'" Targets="OriginalBuild"
Projects="$(MSBuildProjectFullPath)" Properties="InNestedMsbuild=true" />
<!-- Visual Studio doesn't pick up on the modified artifacts.props file unless we force it to reload the solution -->
<Touch Condition="'$(ArtifactsHaveChanged)' == 'true' and '$(BuildingInsideVisualStudio)' == 'true'" Files = "$(SolutionPath)" />
<Message Text="Finished running build target override" Importance="High" />
</Target>
and each of my .vcxproj or .csproj files includes this file at the end:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\OverrideBuild.targets" />
</Project>
This works as I want it to for the C++ projects but fails with the C# projects. When building a C# project via msbuild it fails because the command line to the C# compiler is missing reference arguments for local assemblies. For example, a C# file that has a line like this at the top of the file:
using My.Utils.Common;
fails with the following error message:
error CS0234: The type or namespace name 'Common' does not exist in the namespace 'My.Utils' (are you missing an assembly reference?)
And looking at the compiler command used it is missing this line:
/reference:C:\Code\scratch\Build\My.Utils.Common\Bin\Release\My.Utils.Common.dll
That missing line is present when I comment out my override of the Build target. And weirdly enough it will build fine from within Visual Studio even with my Build override in place. It only fails when building using msbuild from the command line and only for C# projects.
I thought that the way I had overriden the Build target would be completely transparent but apparently it isn't. Can anybody shed some light on what is going wrong ?
It seems that when project A depends on project B with a project reference, the outputs of the Build target of B are used to deduce what should be passed as a reference to the compiler when building A. This is presumably somewhere in the ResolveAssemblyReferences logic.
Therefore to get your replacement Build target working, you need to make its outputs match those of the standard Build.
Here is how you can achieve this:
<Target
Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="GetTargetPathWithTargetPlatformMoniker"
Returns="#(TargetPathWithTargetPlatformMoniker)" >
</Target>
Here Returns="#(TargetPathWithTargetPlatformMoniker)" is what the Returns of the standard Build in the SDK is. But the item array #(TargetPathWithTargetPlatformMoniker) is initially empty, so you need to run the Target GetTargetPathWithTargetPlatformMoniker to populate it before hand.
These are implementation details of the build system, so they may vary by SDK version, but you can always inspect the logic in C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.target or equivalent.
Note that this cannot be used directly with C++ projects, their default Build target is a bit different. You may need to vary by the project type to support both. The Condition on a Target does not stop it from overwriting the existing one, it only stops it from executing, so if you need a target overwrite to differ, you need to put the alternatives in files and import them conditionally. I don't know of a more convenient way, but that at least works.
Why does overriding the Build target in msbuild work for C++ projects but fail for C# projects?
After test your sample, I found the error not comes from the overriden the Build target, it should be related to the project type which you referenced.
Because I have tried comment the import line in the HelloWorld project file:
<Import Project="..\..\OverrideBuild.targets" />
Then MSBuild command line still throw that error.
Besides, I found your referenced project HelloWorldHelper is a Console application project, which output type is Class library.
To resolve this issue, I have created a new Class library instead of Console application, then build it from MSBuild command line, it works fine.
So, please try to convert your referenced project to Class library.
Hope this helps.

Build exe and webapp using msbuild commandline

I am trying to build a solution using ms-build command line, which contains multiple projects.
Four of them are creating an exe file as output and rest are creating a single web application.
Now when I try to build them together using msbuild, it throws out error-
/p:WebPublishMethod=Package /p:DeployOnBuild=true /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:outdir="$(build.artifactstagingdirectory)\\"
Error -
error MSB4057: The target "ResolveWebJobFiles" does not exist in the
project.
Note - If I remove property "/p:WebPublishMethod=Package" then it runs well but doesn't create the zip file.
Can anyone please suggest me any property by which I can create the zip file?
Can anyone please suggest me any property by which I can create the zip file?
Are you deployed the web application as Azure WebJobs with the Azure Website? If yes, you should make sure the you have installed the below package in your web project. The package id="Microsoft.Web.WebJobs.Publish" version="1.0.2".
If not, I am not very sure the reason why you got this error "The target "ResolveWebJobFiles" does not exist in the
project" before I can access your web project. But I would like provide a workaround to you, you can check if it works for you:
Edit the *.csproj file, add zip target in your project file:
<ItemGroup>
<Link Include="bin\*.dll" />
<Link Include="bin\*.exe" />
<Link Include="bin\*.exe.config" />
</ItemGroup>
<Import Project="..\.build\MSBuild.Community.Tasks.Targets" />
<Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'">
<PropertyGroup>
<ReleasePath>bin</ReleasePath>
</PropertyGroup>
<Zip Files="#(Content);#(Link)" WorkingDirectory="$(ReleasePath)" ZipFileName="output\$(AssemblyName).zip" ZipLevel="9" />
</Target>
Note: You need add the nuget package "MSBuildTasks" and you can change the ReleasePath to other folder.
See this document for more detail info.
The issue was with Webjob-List.json file that can be found in properties.
I was using an old reference for the project that was missing from there, deleting the path solved the problem.

How do I use msbuild in Visual Studio's post build event?

I'm trying to configure the YUICompressor.NET in my Visual Studio project.
As I've understood, I have to create a .proj file and add it to my solution. After that, I need to create a post-build event that will build this .proj file and I'll get my desired output (minified js/css files).
So, I have:
This .proj contains:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/MsBuild/2003">
<UsingTask TaskName="CssCompressorTask" AssemblyFile="\..\packages\YUICompressor.NET.MSBuild.2.7.0.0\lib\NET20\Yahoo.Yui.Compressor.Build.MsBuild.dll" />
<UsingTask TaskName="JavaScriptCompressorTask" AssemblyFile="\..\packages\YUICompressor.NET.MSBuild.2.7.0.0\lib\NET20\Yahoo.Yui.Compressor.Build.MsBuild.dll" />
<Target Name="Minify">
<ItemGroup>
<!-- Single files, listed in order of dependency -->
<CssFiles Include="Content\*.css"/>
<JavaScriptFiles Include="Scripts\*.js"/>
</ItemGroup>
<CssCompressorTask
SourceFiles="#(CssFiles)"
OutputFile="Content\min.css"
/>
<JavaScriptCompressorTask
SourceFiles="#(JavaScriptFiles)"
OutputFile="Scripts\min.js"
/>
</Target>
</Project>
I'm trying to build it as the following:
I'm getting the following error:
The command "msbuild C:\Users\Me\Desktop\MvcApplicationExample\MvcApplicationExample\YuiCompressorMsBuild.proj" exited with code 9009.
This error suggests that "msbuild" is not a valid command. So, how should I build this type of project? (I've followed this tutorial: youtube)
Thanks for any help.
As you said maybe the visual studio cannot find the MSBuild command, try with the following command instead
"$(MSBuildBinPath)\msbuild.exe"
That uses the complete path to msbuild.
Update (For futures references)
As the comment by Steve Medley, you should not forget the encapsulating quotes.
vfabre is correct to use $(MSBuildBinPath)\msbuild.exe but missing one thing. you should encapsulate that in quotes as there will 99.99% of the time be a space in the file path to msbuild
Well, one thing that might be a problem is that you want it as a Post-build event but it's set for the Pre-build command line.

Using SlowCheetah's app.config transformations with Setup projects

I'm using the SlowCheetah XML Transforms extension to handle web.config-like transformations with app.config. That part works great.
I added a setup project and configured it to include the project output of the first project. I noticed that when I ran the installer, it installed the non-transformed app.config. Looking at the Primary output Outputs (say that 10 times fast), I noticed that its finding the binary in Project\bin\Debug\Project.exe, but Project.exe.config comes from Project\app.config instead of Project\bin\Debug\Project.exe.config.
I could exclude app.config from the Primary output, and hard-code the path to a specific configuration's app.config (Project\bin\Debug\Project.exe.config), but then I'd get the same app.config regardless of which configuration I used to build it.
Is there a workaround for getting the appropriate transformed app.config in a Setup project?
Hi we are planning on releasing a new version which has ClickOnce support in the next few days. If you need a build of the add in before than which has the fix please contact me and I can get that out to you.
This may not be exactly the answer you're looking for but I have previously wrestled with how to get the correct app.config file into a setup project. I have a TFSBuild.proj msbuild file that uses transforms. The SlowCheetah transforms I think use the same msbuild task but I may be incorrect. SlowCheetah certainly provides a more useful user experience when working with transform files. My build file takes a slightly different approach. At the end of the automated build I wanted to generate installers for each of the target deployment environments. I use a number of msbuild extensions, including the TransformXml build task - not all required for the following but FWIW these are the imports:
<!-- import extensions -->
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
I have the following environments defined:
<ItemGroup>
<!-- target deployment environments -->
<Configs Include="Prod" />
<Configs Include="Staging" />
<Configs Include="Test" />
</ItemGroup>
Then the standard AfterCompileSolution target contains a call to the target that generates the installer for each environment:
<Target Name="AfterCompileSolution">
<!-- Create installers for target deployment environments -->
<CallTarget Targets="MyProject" />
</Target>
<Target Name="MyProject" Outputs="%(Configs.Identity)">
<ItemGroup>
<MyProjectTempConfig Include="$(SolutionRoot)\MyProjectService\Temp.config" />
<MyProjectConfigFrom Include="$(SolutionRoot)\MyProjectService\App.%(Configs.Identity).config" />
<MyProjectConfigTo Include="$(SolutionRoot)\MyProjectService\App.config">
<Attributes>ReadOnly</Attributes>
</MyProjectConfigTo>
</ItemGroup>
<Message Text="MyProject - Target environment: %(Configs.Identity)" />
<!-- transform app.config using appropriate -->
<Copy SourceFiles="#(MyProjectConfigTo)"
DestinationFiles="#(MyProjectTempConfig)"
OverwriteReadOnlyFiles="true"
ContinueOnError="true"
Condition="!Exists(#(MyProjectTempConfig))"/>
<File TaskAction="RemoveAttributes" Files="#(MyProjectConfigTo)"/>
<TransformXml Source="#(MyProjectTempConfig)"
Transform="#(MyProjectConfigFrom)"
Destination="#(MyProjectConfigTo)" />
<!-- run setup -->
<Exec Command=""$(ProgramFiles)\Microsoft Visual Studio 10.0\Common7\IDE\devenv" "$(SolutionRoot)\MyProject.sln" /build Release /project MyProjectService.Setup"/>
<!-- rename output for target deployment environment -->
<Copy SourceFiles="$(SolutionRoot)\MyProjectService.Setup\Release\MyProjectService.msi"
DestinationFiles="$(OutDir)\%(Configs.Identity)_MyProjectService.msi"
OverwriteReadOnlyFiles="true"
ContinueOnError="true"/>
</Target>

App.Config Transformation for projects which are not Web Projects in Visual Studio?

For Visual Studio 2010 Web based application we have Config Transformation features by which we can maintain multiple configuration files for different environments. But the same feature is not available for App.Config files for Windows Services/WinForms or Console Application.
There is a workaround available as suggested here: Applying XDT magic to App.Config.
However it is not straightforward and requires a number of steps. Is there an easier way to achieve the same for app.config files?
I tried several solutions and here is the simplest I personally found.
Dan pointed out in the comments that the original post belongs to Oleg Sych—thanks, Oleg!
Here are the instructions:
1. Add an XML file for each configuration to the project.
Typically you will have Debug and Release configurations so name your files App.Debug.config and App.Release.config. In my project, I created a configuration for each kind of environment, so you might want to experiment with that.
2. Unload project and open .csproj file for editing
Visual Studio allows you to edit .csproj files right in the editor—you just need to unload the project first. Then right-click on it and select Edit <ProjectName>.csproj.
3. Bind App.*.config files to main App.config
Find the project file section that contains all App.config and App.*.config references. You'll notice their build actions are set to None and that's okay:
<None Include="App.config" />
<None Include="App.Debug.config" />
<None Include="App.Release.config" />
Next, make all configuration-specific files dependant on the main App.config so Visual Studio groups them like it does designer and code-behind files.
Replace XML above with the one below:
<None Include="App.config" />
<None Include="App.Debug.config" >
<DependentUpon>App.config</DependentUpon>
</None>
<None Include="App.Release.config" >
<DependentUpon>App.config</DependentUpon>
</None>
4. Activate transformations magic (still necessary for Visual Studio versions such as VS2019)
In the end of file after
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
and before final
</Project>
insert the following XML -- please note there are two steps for the proper transformation to occur:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeBuild" Condition="Exists('App.$(Configuration).config')">
<!-- Generate transformed app config and replace it: will get the <runtime> node and assembly bindings properly populated -->
<TransformXml Source="App.config" Destination="App.config" Transform="App.$(Configuration).config" />
</Target>
<Target Name="AfterBuild" Condition="Exists('App.$(Configuration).config')">
<!-- Generate transformed app config in the intermediate directory: this will transform sections such as appSettings -->
<TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" />
<!-- Force build process to use the transformed configuration file from now on.-->
<ItemGroup>
<AppConfigWithTargetPath Remove="App.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
Now you can reload the project, build it and enjoy App.config transformations!
FYI
Make sure that your App.*.config files have the right setup like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--magic transformations here-->
</configuration>
This works now with the Visual Studio AddIn treated in this article: SlowCheetah - Web.config Transformation Syntax now generalized for any XML configuration file.
You can right-click on your web.config and click "Add Config
Transforms." When you do this, you'll get a web.debug.config and a
web.release.config. You can make a web.whatever.config if you like, as
long as the name lines up with a configuration profile. These files
are just the changes you want made, not a complete copy of your
web.config.
You might think you'd want to use XSLT to transform a web.config, but
while they feels intuitively right it's actually very verbose.
Here's two transforms, one using XSLT and the same one using the XML
Document Transform syntax/namespace. As with all things there's
multiple ways in XSLT to do this, but you get the general idea. XSLT
is a generalized tree transformation language, while this deployment
one is optimized for a specific subset of common scenarios. But, the
cool part is that each XDT transform is a .NET plugin, so you can make
your own.
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/configuration/appSettings">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
<xsl:element name="add">
<xsl:attribute name="key">NewSetting</xsl:attribute>
<xsl:attribute name="value">New Setting Value</xsl:attribute>
</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Or the same thing via the deployment transform:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add name="NewSetting" value="New Setting Value" xdt:Transform="Insert"/>
</appSettings>
</configuration>
Another solution I've found is NOT to use the transformations but just have a separate config file, e.g. app.Release.config. Then add this line to your csproj file.
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<AppConfig>App.Release.config</AppConfig>
</PropertyGroup>
This will not only generate the right myprogram.exe.config file but if you're using Setup and Deployment Project in Visual Studio to generate MSI, it'll force the deployment project to use the correct config file when packaging.
Inspired by Oleg and others in this question, I took the solution https://stackoverflow.com/a/5109530/2286801 a step further to enable the following.
Works with ClickOnce
Works with Setup and Deployment projects in VS 2010
Works with VS2010, 2013, 2015 (didn't test 2012 although should work as well).
Works with Team Build. (You must install either A) Visual Studio or B) Microsoft.Web.Publishing.targets and Microsoft.Web.Publishing.Tasks.dll)
This solution works by performing the app.config transformation before the app.config is referenced for the first time in the MSBuild process. It uses an external targets file for easier management across multiple projects.
Instructions:
Similar steps to the other solution. I've quoted what remains the same and included it for completeness and easier comparison.
0. Add a new file to your project called AppConfigTransformation.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Transform the app config per project configuration.-->
<PropertyGroup>
<!-- This ensures compatibility across multiple versions of Visual Studio when using a solution file.
However, when using MSBuild directly you may need to override this property to 11.0 or 12.0
accordingly as part of the MSBuild script, ie /p:VisualStudioVersion=11.0;
See http://blogs.msdn.com/b/webdev/archive/2012/08/22/visual-studio-project-compatability-and-visualstudioversion.aspx -->
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.targets" />
<Target Name="SetTransformAppConfigDestination" BeforeTargets="PrepareForBuild"
Condition="exists('app.$(Configuration).config')">
<PropertyGroup>
<!-- Force build process to use the transformed configuration file from now on. -->
<AppConfig>$(IntermediateOutputPath)$(TargetFileName).config</AppConfig>
</PropertyGroup>
<Message Text="AppConfig transformation destination: = $(AppConfig)" />
</Target>
<!-- Transform the app.config after the prepare for build completes. -->
<Target Name="TransformAppConfig" AfterTargets="PrepareForBuild" Condition="exists('app.$(Configuration).config')">
<!-- Generate transformed app config in the intermediate directory -->
<TransformXml Source="app.config" Destination="$(AppConfig)" Transform="app.$(Configuration).config" />
</Target>
</Project>
1. Add an XML file for each configuration to the project.
Typically you will have Debug and Release configurations so name your files App.Debug.config and App.Release.config. In my project, I created a configuration for each kind of enironment so you might want to experiment with that.
2. Unload project and open .csproj file for editing
Visual Studio allows you to edit .csproj right in the editor—you just need to unload the project first. Then right-click on it and select Edit .csproj.
3. Bind App.*.config files to main App.config
Find the project file section that contains all App.config and App.*.config references and replace as follows. You'll notice we use None instead of Content.
<ItemGroup>
<None Include="app.config"/>
<None Include="app.Production.config">
<DependentUpon>app.config</DependentUpon>
</None>
<None Include="app.QA.config">
<DependentUpon>app.config</DependentUpon>
</None>
<None Include="app.Development.config">
<DependentUpon>app.config</DependentUpon>
</None>
</ItemGroup>
4. Activate transformations magic
In the end of file after
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
and before final
</Project>
insert the following XML:
<Import Project="AppConfigTransformation.targets" />
Done!
In my experience, the things I need to make environment-specific are things like connection strings, appsettings and often smpt settings. The config system allows to specify these things in separate files. So you can use this in your app.config/web.config:
<appSettings configSource="appsettings.config" />
<connectionStrings configSource="connection.config" />
<system.net>
<mailSettings>
<smtp configSource="smtp.config"/>
</mailSettings>
</system.net>
What I typically do is to put these config-specific sections in separate files, in a subfolder called ConfigFiles (either in the solution root or at the project level, depends). I define a file per configuration, e.g. smtp.config.Debug and smtp.config.Release.
Then you can define a pre-build event like so:
copy $(ProjectDir)ConfigFiles\smtp.config.$(ConfigurationName) $(TargetDir)smtp.config
In team development, you can tweak this further by including the %COMPUTERNAME% and/or %USERNAME% in the convention.
Of course, this implies that the target files (x.config) should NOT be put in source control (since they are generated). You should still add them to the project file and set their output type property to 'copy always' or 'copy if newer' though.
Simple, extensible, and it works for all types of Visual Studio projects (console, winforms, wpf, web).
You can use a separate config file per configuration, e.g. app.Debug.config, app.Release.config and then use the configuration variable in your project file:
<PropertyGroup>
<AppConfig>App.$(Configuration).config</AppConfig>
</PropertyGroup>
This will then create the correct ProjectName.exe.config file depending on the configuration you are building in.
I wrote nice extension to automate app.config transformation like the one built in Web Application Project Configuration Transform
The biggest advantage of this extension is that you don’t need to install it on all build machines
Install "Configuration Transform Tool" in Visual Studio from Marketplace and restart VS. You will be able to see menu preview transform for app.config as well.
https://marketplace.visualstudio.com/items?itemName=GolanAvraham.ConfigurationTransform
Just a little improvement to the solution that seems to be posted everywhere now:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
that is, unless you are planning to stay with your current VS version forever
So I ended up taking a slightly different approach. I followed Dan's steps through step 3, but added another file: App.Base.Config. This file contains the configuration settings you want in every generated App.Config. Then I use BeforeBuild (with Yuri's addition to TransformXml) to transform the current configuration with the Base config into the App.config. The build process then uses the transformed App.config as normal. However, one annoyance is you kind of want to exclude the ever-changing App.config from source control afterwards, but the other config files are now dependent upon it.
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeBuild" Condition="exists('app.$(Configuration).config')">
<TransformXml Source="App.Base.config" Transform="App.$(Configuration).config" Destination="App.config" />
</Target>
I have created another alternative to the one posted by Vishal Joshi where the requirement to change the build action to Content is removed and also implemented basic support for ClickOnce deployment. I say basic, because I didn't test it thoroughly but it should work in the typical ClickOnce deployment scenario.
The solution consists of a single MSBuild project that once imported to an existent windows application project (*.csproj) extends the build process to contemplate app.config transformation.
You can read a more detailed explanation at Visual Studio App.config XML Transformation and the MSBuild project file can be downloaded from GitHub.
If you use a TFS online(Cloud version) and you want to transform the App.Config in a project, you can do the following without installing any extra tools.
From VS => Unload the project => Edit project file => Go to the bottom of the file and add the following:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterBuild" Condition="Exists('App.$(Configuration).config')">
<TransformXml Source="App.config" Transform="App.$(Configuration).config" Destination="$(OutDir)\$(AssemblyName).dll.config" />
AssemblyFile and Destination works for local use and TFS online(Cloud) server.
proposed solution will not work when a class library with config file is referenced from another project (in my case it was Azure worker project library). It will not copy correct transformed file from obj folder into bin\##configuration-name## folder. To make it work with minimal changes, you need to change AfterCompile target to BeforeCompile:
<Target Name="BeforeCompile" Condition="exists('app.$(Configuration).config')">
Note: Due to reputation I cannot comment on bdeem's post. I'm posting my findings as an answer instead.
Following bdeem's post, I did the following (in order):
1. I modified the [project].csproj file. Added the <Content Include="" /> tags to the ItemGroup for the different config files and made them dependent on the original config file.
Note: Using <None Include="" /> will not work with the transformation.
<!-- App.config Settings -->
<!-- Create App.($Configuration).config files here. -->
<Content Include="App.config" />
<Content Include="App.Debug.config">
<DependentUpon>App.config</DependentUpon>
</Content>
<Content Include="App.Release.config">
<DependentUpon>App.config</DependentUpon>
</Content>
2. At the bottom of the [project].csproj file (before the closing </Project> tag), I imported the ${MSBuildToolsPath\Microsoft.CSharp.targets file, added the UsingTask to transform the XML and added the Target to copy the transformed App.config file to the output location.
Note: The Target will also overwrite the App.Config in the local directory to see immediate changes working locally. The Target also uses the Name="Afterbuild" property to ensure the config files can be transformed after the executables are generated. For reasons I do not understand, when using WCF endpoints, if I use Name="CoreCompile", I will get warnings about the service attributes. Name="Afterbuild" resolved this.
<!-- Task to transform the App.config using the App.($Configuration).config file. -->
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<!-- Only compile the App.config if the App.($Configuration).config file exists. -->
<!-- Make sure to use the AfterBuild name instead of CoreCompile to avoid first time build errors and WCF endpoint errors. -->
<Target Name="AfterBuild" Condition="exists('App.$(Configuration).config')">
<!-- Generate transformed App.config in the intermediate output directory -->
<TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" />
<!-- Modify the original App.config file with the transformed version. -->
<TransformXml Source="App.config" Destination="App.config" Transform="App.$(Configuration).config" />
<!-- Force build process to use the transformed configuration file from now on. -->
<ItemGroup>
<AppConfigWithTargetPath Remove="App.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
</Project>
3. Went back into Visual Studio and reloaded the modified files.
4. Manually added the App.*.config files to the project. This allowed them to group under the original App.config file.
Note: Make sure the App.*.config files have the proper XML structure.
<?xml version="1.0" encoding="utf-8"?>
<!-- For more information on using web.config transformation visit https://go.microsoft.com/fwlink/?LinkId=125889 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<add name="myConn" connectionString=""; Initial Catalog=; User ID=; Password=;" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
</connectionStrings>
</configuration>
5. Re-built the project.
Yet another variation on #bdeem's answer using Visual Studio 2019 and 2022. My issue was that using that solution, App.config was getting overwritten, and since it's in source control that's not really an option.
The solution for me was to transform the config file directly into the output directory.
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterBuild" Condition="Exists('App.$(Configuration).config')">
<!-- Generate transformed app config to the output directory -->
<TransformXml Source="App.config" Destination="$(OutDir)\$(TargetFileName).config" Transform="App.$(Configuration).config" />
</Target>
It has the added benefit of being quite a bit shorter than the original solution.

Resources