Using SlowCheetah's app.config transformations with Setup projects - visual-studio-2010

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>

Related

dll.refresh with devenv vs msbuild (Website Project, not application)

I am creating a CI server for our application(s) and have run into an issue with msbuild for our Website Project. It builds off a solution, but no proj file (as it is not a website application and cannot be). MSBuild won't pull in the dll.refresh files into the bin folder. (Not specific to a CI issue, but that's the goal) If I run it against devenv.com (CLI attempts) to build then it does pull in the dll.refresh and appears to work just fine.
From what I can find on MsBuild logs, it appears the the Copy task (which is just the default rule from msbuild itself) doesn't target the /bin folder when looking for the DLL files, but the root of the solution (/www in this case).
Just looking for some more information, as all other research points have seeming run dry at this point. (Does it have to be Msbuild? No, but I would like to make the CI configuration very simple for anyone else to re-produce and a custom build script/batch file and VS install on the CI server would make it much more complex).
Thanks.
I cannot reproduce the behavior you describe using VS2013 / MSBuild 12.0.31101.0.
Repro:
Create new website in VS
Add Reference to assembly on disk
Clean the bin directory of all files except *.refresh
Save all
Run msbuild WebsiteSolution.sln
Result: DLLs referenced in the *.refresh are re-created.
A read-through of the generated MSBuild file shows that the paths in the *.refresh files are resolved relative to the base directory of the website. I will note that this only occurs on the Build target, so I don't know what you mean when you say "the Copy task [...] is the default rule". Perhaps you are using some custom MSBuild target which needs to include the default target?
The relevant parts of the generated MSBuild (produced with MSBuildEmitSolution=1):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<!-- Edit: Snipped -->
<!-- The website location is stored relative to the .sln -->
<!-- (which is the same as the location of the temporary msbuild file) -->
<Project_[...]_AspNetPhysicalPath>..\..\WebSites\WebSite1\</Project_[...]_AspNetPhysicalPath>
<!-- Edit: Snipped -->
</PropertyGroup>
<ItemDefinitionGroup />
<!-- Edit: Snipped -->
<Target Name="Build" Condition=" ('$(CurrentSolutionConfigurationContents)' != '') and (false or ( ('$(Configuration)' == 'Debug') and ('$(Platform)' == 'Any CPU') ) or ( ('$(Configuration)' == 'Release') and ('$(Platform)' == 'Any CPU') )) " DependsOnTargets="GetFrameworkPathAndRedistList">
<!-- Edit: Snipped -->
<!-- *.refresh items are discovered and saved in [...]_References_RefreshFile -->
<CreateItem Include="$(Project_[...]_AspNetPhysicalPath)\Bin\*.refresh">
<Output TaskParameter="Include" ItemName="Project_[...]_References_RefreshFile" />
</CreateItem>
<!-- The contents of the *.refresh are read to [...]_References_ReferenceRelPath -->
<ReadLinesFromFile Condition=" '%(Project_[...]_References_RefreshFile.Identity)' != '' " File="%(Project_[...]_References_RefreshFile.Identity)">
<Output TaskParameter="Lines" ItemName="Project_[...]_References_ReferenceRelPath" />
</ReadLinesFromFile>
<!-- Those contents are relative to [...]_AspNetPhysicalPath -->
<CombinePath BasePath="$(Project_[...]_AspNetPhysicalPath)" Paths="#(Project_[...]_References_ReferenceRelPath)">
<Output TaskParameter="CombinedPaths" ItemName="Project_[...]_References" />
</CombinePath>
<!-- This seems to be a no-op, since you cannot copy if it doesn't exist -->
<Copy Condition="!Exists('%(Project_[...]_References.Identity)')" ContinueOnError="true" SourceFiles="#(Project_[...]_References->'%(FullPath)')" DestinationFolder="$(Project_[...]_AspNetPhysicalPath)\Bin\" />
<!-- Edit: Snipped -->
<!-- This will copy [...]_References to [...]_References_CopyLocalFiles and add references -->
<ResolveAssemblyReference Condition="Exists('%(Project_[...]_References.Identity)')" Assemblies="#(Project_[...]_References->'%(FullPath)')" TargetFrameworkDirectories="$(Project_[...]__TargetFrameworkDirectories)" FullFrameworkFolders="$(Project_[...]__FullFrameworkReferenceAssemblyPaths)" SearchPaths="{RawFileName};{TargetFrameworkDirectory};{GAC}" FindDependencies="true" FindSatellites="true" FindSerializationAssemblies="true" FindRelatedFiles="true" TargetFrameworkMoniker=".NETFramework,Version=v4.5">
<Output TaskParameter="CopyLocalFiles" ItemName="Project_[...]_References_CopyLocalFiles" />
</ResolveAssemblyReference>
<!-- [...]_References_CopyLocalFiles is copied to the bin directory -->
<Copy Condition="(false) or ('$(AspNetConfiguration)' == 'Debug') or ('$(AspNetConfiguration)' == 'Release')" SourceFiles="#(Project_[...]_References_CopyLocalFiles)" DestinationFiles="#(Project_[...]_References_CopyLocalFiles->'$(Project_[...]_AspNetPhysicalPath)\Bin\%(DestinationSubDirectory)%(Filename)%(Extension)')" />
<!-- Edit: Snipped -->
</Target>
<!-- Edit: Snipped -->
</Project>
I have not tried this on a machine which does not have VS installed, so it may not apply directly, but you should definitely be able to build with the generated metaproj file even without Visual Studio installed.
I had this problem as well; the problem was actually that our build configuration was neither Debug nor Release, so msbuild was actually skipping the compile (and thus the restore with *.refresh):
Skipping because the "Dev" configuration is not supported for this web
project. You can use the AspNetConfiguration property to override the
configuration used for building web projects, by adding
/p:AspNetConfiguration= to the command line. Currently web
projects only support Debug and Release configurations.

Can I build multiple configurations of a project within one solution configuration?

I would like to build the same project twice in the same solution configuration, varying some #define flags to toggle features. Both binaries will be deployed with different names.
The solutions that I know could work:
Add a solution configuration - But I will then need to build the solution twice, which I would prefer to avoid. Both project configurations will always be built.
Copy the project - But then I have the overhead of maintaining a new project when I only want to maintain a different configuration.
Batch build - I avoid using batch build as I use both devenv for local development and msbuild for continuous integration.
Any other ideas or suggestions?
Just figured out a way to do what you asked for. Create one msbuild file (I named mine multiple.proj) and add the script below.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<ItemGroup>
<ProjectToBuild Include="$(MSBuildProjectName).csproj">
<Properties>Configuration=Release</Properties>
</ProjectToBuild>
</ItemGroup>
</When>
</Choose>
<Target Name="BeforeBuild">
<Message Text="Building configuration $(Configuration)..." />
</Target>
<Target Name="AfterBuild">
<MSBuild Projects="#(ProjectToBuild)"/>
</Target>
</Project>
</type>
</this>
Import the script on your projects (csproj or vbproj):
<Import Project="..\multiple.proj" />
This script tells msbuild to build again your project with another configuration as an AfterBuild event. I used Debug/Release to make the example, but you can easily change the script to support other configurations, or make the decision to build again based on other variables.
Be careful because you're running two builds at once, so build errors can be harder to understand.
Hope this helps.

VS 2010 Solution Configuration build for a project context

I have a Visual Studio solution containing 30+ projects. There are 2 build configurations, Debug and Release. Ten of the project files (.csproj) are generated reasonably frequently using an external tool and the templates for that generation include the Debug and Release configurations. Modifying the templates to include additional configurations is not really an option.
So the problem I have is that I have a new project that is hosted in AppFabric/IIS. I'm using Web.Config transformations to update the Web.Config for deployment to 4 different environments: Development, Test, Staging, Production. I've add those contexts to the specific project using the Configuration Manager. That works so I can update the Configuration Manager for e.g. the Release build to use the Production context for the project. Right-clicking and building a deployment package for the project results in the appropriate transformation being applied to the Web.Config.
Now I want to automate the process so I have an MSBuild script:
<ItemGroup>
<BuildMode Include="Dev"/>
<BuildMode Include="Test"/>
<BuildMode Include="Staging"/>
<BuildMode Include="Prod"/>
</ItemGroup>
<Target Name="Build" DependsOnTargets="Package"></Target>
<!--
Build deployment package for each target environment
-->
<Target Name="Package" Outputs="%(BuildMode.Identity)">
<Message Text="Building %(BuildMode.Identity)"/>
<MSBuild Projects="..\SynchWorkflow\SynchWorkflow.csproj"
Targets="Package"
Properties="Platform=AnyCPU;Configuration=%(BuildMode.Identity);"/>
</Target>
Unfortunately this errors because it is trying to build e.g. a Prod configuration when it doesn't exist - Prod is only a context for the SynchWorkflow project. What I want to do is have the Prod context package generated using the Release configuration. Is that possible using the MSBuild task? Is there an extra setting I can provide to the MSBuild task in the Properties attribute that would allow this?
I added new solution configurations for each environment (without adding new project configurations) and then used Configuration Manager to set the contexts to Release for the dependent projects. Unfortunately this didn't work because the MSBuild task was building against the project file and not the solution. Resolved using Julien Hoarau's SO answer. Adding the new solution configurations was the correct approach but the linked answer closed the loop for me. The build script has been updated to the following:
<ItemGroup>
<BuildMode Include="Development"/>
<BuildMode Include="Test"/>
<BuildMode Include="Staging"/>
<BuildMode Include="Production"/>
</ItemGroup>
<PropertyGroup>
<PackageLocation>$(MSBuildProjectDirectory)</PackageLocation>
</PropertyGroup>
<Target Name="Build" DependsOnTargets="Package"></Target>
<!--
Build deployment package for each target environment
-->
<Target Name="Package" Outputs="%(BuildMode.Identity)">
<Message Text="Building %(BuildMode.Identity)"/>
<MSBuild Projects="..\SynchWorkflow.sln"
Properties="Platform=Any CPU;
Configuration=%(BuildMode.Identity);
DeployOnBuild=true;
DeployTarget=Package;
PackageLocation=$(PackageLocation)\SynchWorkflow.%(BuildMode.Identity)Package.zip;"/>
</Target>
The script builds against the solution file and generates the correct package for each target environment. For the purposes of the example script I'm creating the packages in the MSBuildProjectDirectory which is a bit suboptimal.

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.

MSBuild "Debug" configuration not working in VS 2010 Beta 2

I'm trying to set up my environment for developing, debugging and deploying Windows Desktop Gadgets. I've hit a bit of a roadblock in my project, where I can't run a build on my gadget when the configuration is set to "Debug". If the configuration is set to "Release", the build goes through the following custom tasks:
Copy gadget contents to a seperate folder.
Minify/obfuscate the javascript files, removing comments and whitespace.
Package the files into a CAB file.
Sign the CAB file with a digital certificate.
This runs just fine, my "Debug" configuration has the following tasks defined
Copy gadget folder to AppData\Local\Microsoft\Windows Sidebar\Gadgets\.
Start the gadget using the IDesktopGadget interface.
If I copy those two tasks to the "Release" configuration, they run just fine - no problems whatsoever. I've tried creating a seperate configuration called "Test", copied from the "Release" configuration.
If I try to build any configuration other than "Release", I get an instant message saying "Build succeeded" but no tasks have run at all.
EDIT: I've started a bounty because I still have the same problem with VS 2010 RC and it's very frustrating.
FURTHER EDIT:
Thanks to John I was able to debug the build process. It led me to realize that the <Target> element with condition for debugging was being completely ignored (not even processed). When I swapped the position of my <Target> elements, it worked:
<Target Name="Build" Condition="'$(Configuration)' == 'Release'">
<!--
<Obfuscate PathToJasob="C:\Program Files (x86)\Jasob.com\Jasob 3.5" Path="$(GadgetFolder)" Output="$(GadgetName)_obf" log="jasob_log.txt" />
-->
<BuildGadget BuildFormat="CAB" Path="$(GadgetFolder)" Target="$(GadgetName).gadget" />
<SignGadget CertName="Cert1" TimestampURL="http://timestamp.comodoca.com/authenticode" Target="$(GadgetName).gadget" />
</Target>
<Target Name="Build" Condition="'$(Configuration)' == 'Debug'">
<CopyToGadgets GadgetFolder="$(GadgetFolder)" GadgetName="$(GadgetName)" />
<RunGadget GadgetName="$(GadgetName)" />
</Target>
So it looks like the second <Target Name="Build"> element overrides the first, despite the Condition attribute being present. What can I do?
As Joe suggests:
Change your output path like this, and see if that fixes the issue:
<OutputPath>bin\Debug\</OutputPath>
Update
Have you tried running msbuild /verbosity:diagnostic ?
Can you try that and show the output?
Second Update
Make one target 'build', and then make two tasks in that target:
<Target Name="Build">
<CallTarget Targets="BuildRelease" Condition="'$(Configuration)' == 'Release'" />
<CallTarget Targets="BuildDebug" Condition="'$(Configuration)' == 'Debug'" />
</Target>
<Target Name="BuildRelease">
<!--
<Obfuscate PathToJasob="C:\Program Files (x86)\Jasob.com\Jasob 3.5" Path="$(GadgetFolder)" Output="$(GadgetName)_obf" log="jasob_log.txt" />
-->
<BuildGadget BuildFormat="CAB" Path="$(GadgetFolder)" Target="$(GadgetName).gadget" />
<SignGadget CertName="Cert1" TimestampURL="http://timestamp.comodoca.com/authenticode" Target="$(GadgetName).gadget" />
</Target>
<Target Name="BuildDebug">
<CopyToGadgets GadgetFolder="$(GadgetFolder)" GadgetName="$(GadgetName)" />
<RunGadget GadgetName="$(GadgetName)" />
</Target>
Just a guess:
Your Debug build has its output path set to bin\Release\.
The timestamps of the files in bin\Release\ are probably causing MSBuild to conclude that the debug build is already up to date. Try changing the the output path to bin\Debug\ for debug builds.

Resources