How to I perform a web.config transform with MSBuild or MSDeploy? - asp.net-mvc

I've tried a number of different configurations with this and I haven't achieved my result.
TL;DR
I'm trying to add config transforms into my build process and am looking for the right way to do it from MSBuild so that it shows up in my deployments via MSDeploy.
Background
I have an WebApp (MVC3), a Core app (CS Class Lib), and two test class libs, one for each.
I have a build script in my solution that uses MSBuild to compile.
One of those MSBuild targets deploys to an IIS server using MSDeploy
This process is working so far both manually and via CruiseControl.NET
Goal
I would like to add Web.Config transforms to this process. I figured I would do something simple at first, like an app setting called "PEAppsEnvironmentName", which I would make Dev, Test, or Prod based on the current environment.
Theory So Far
To me, it appears that when packaging with MSDeploy, I'm not transforming the config file.
When I run MSBuild with the DeployOnBuild option set to true, it creates another package that has the appropriately transformed config. It just seems like somehow I can't get it all to match up. The end result is that the web page displays "None" (the initial setting) instead of the transformed "Development" string.
I think if I could find out how to use MSDeploy during the packaging phase to transform the MSConfig, I'd be good to go.
Code
My web.config file
<appSettings>
<add key ="PEAppsEnvironmentName" value="None"/>
...
</appSettings>
My Web.Dev.config file
<appSettings>
<add key ="PEAppsEnvironmentName" xdt:Transform="Replace" xdt:Locator="Match(key)" value="Development" />
</appSettings>
My MSBuild Targets
Property group showing default config is "Dev"
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Dev</Configuration>
</PropertyGroup>
My MSBuild "Compile" Target
<Target Name="Compile" DependsOnTargets="Init">
<MSBuild Projects="#(SolutionFile)" Targets="Rebuild" Properties="OutDir=%(BuildArtifacts.FullPath);DeployOnBuild=True"/>
</Target>
My MSBuild "Package" Target
<Target Name="Package" DependsOnTargets="Compile;Test">
<PropertyGroup>
<PackageDir>%(PackageFile.RootDir)%(PackageFile.Directory)</PackageDir>
<Source>%(WebSite.FullPath)</Source>
<Destination>%(PackageFile.FullPath)</Destination>
</PropertyGroup>
<MakeDir Directories="$(PackageDir)"/>
<Exec Command='"#(MSDeploy)" -verb:sync -source:iisApp="$(Source)" -dest:package="$(Destination)" '/>
</Target>
My MSBuild "Deploy" Target
(scrubbed for PWs, etc.)
<Target Name='Deploy' DependsOnTargets='Package'>
<PropertyGroup>
<Source>%(PackageFile.FullPath)</Source>
</PropertyGroup>
<Exec Command ='"#(MsDeploy)" -verb:sync -source:package="$(Source)" -dest:iisApp=PEApps,computerName=$(WebServerName),username=[User],password=[Password]'/>
</Target>

There was a lot to this question, I'm not sure if I'm fully on the same page as you but I'll summarize my impression of what you are asking. You have an existing web project which is in a solution with other projects. You need to be able to package the web project so that you can publish it to multiple destinations.
I have created a NuGet package which can be used for this exact purpose. It's called package-web. When you add it to your web project it will update the packaging process. When you create a package a few additional files will be included in the package, including all the web.config transform files. A .ps1 file will be created next to the package as well. You can use this script to publish the package. It will prompt you for which transform to run and for all the Web Deploy parameters. You can also save the responses to a file and then just pass them to the .ps1 file so that you can perform non-interactive publishes. I created a 5 minute video on it at http://nuget.org/packages/PackageWeb
package web: http://sedodream.com/2012/03/14/PackageWebUpdatedAndVideoBelow.aspx. FYI this is not yet working with VS 2012 but I'm working on the fix and should have it updated by the time VS 2012 is released.
If you don't find that useful you can see how I implemented the solution at https://github.com/sayedihashimi/package-web and you should see examples of everything that you need to do to roll your own.
FYI if you need to transform any files besides web.config on package create then you should take a look at my VS extension SlowCheetah. Here is a blog about how to integrate it into a build server.

Related

How to prevent msbuild config transforms in particular environments?

I have a azure webjob project that uses config transforms to create dev/test/release configuration. We are using TFS for CI/CD deployment to Azure. I want to have MSBuild apply the transforms for dev so we can debug locally. However, when we are building in TFS in the CI/CD pipeline I need to disable the config transforms during the build step.
TFS has an "apply XML transformations" checkbox in the release step, which is where we want the transforms applied since we have the environment variable set during release. Unfortunately, this is not working because the transforms are already applied during build so the release artifact only has the finished output file, not the separate transform files.
I have tried editing the .csproj file to disable the transforms. I assume the transforms are being performed by the following section of the project file:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="Exists('App.$(Configuration).config')">
<!--Generate transformed app config in the intermediate directory-->
<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>
<!--Override After Publish to support ClickOnce AfterPublish. Target replaces the untransformed config file copied to the deployment directory with the transformed one.-->
<Target Name="AfterPublish">
<PropertyGroup>
<DeployedConfig>$(_DeploymentApplicationDir)$(TargetName)$(TargetExt).config$(_DeploymentFileMappingExtension)</DeployedConfig>
</PropertyGroup>
<!--Publish copies the untransformed App.config to deployment directory so overwrite it-->
<Copy Condition="Exists('$(DeployedConfig)')" SourceFiles="$(IntermediateOutputPath)$(TargetFileName).config" DestinationFiles="$(DeployedConfig)" />
</Target>
I tried adding conditions like "$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" to these sections, and it did not help (the transforms still got applied in all three environments). I even commented this section out completely, and I still got the transforms. This leaves me with three questions:
How can the config transforms be disabled?
How can I conditionally
disable them so they are still applied when debugging in VS?
Is this
the correct approach, or is there a better way to get the correct
transform applied when using CI/CD in TFS 2017?
To disable the config transform during the build, you just need to add argument /p:TransformWebConfigEnabled=False in MSBuild Arguments section of your Build task. You also need to add /p:AutoParameterizationWebConfigConnectionStrings=False if you want to update the connection string during the release.
Besides, you need to update your project file so that the Web.XXX.Config file will be included in the package if you are generating msdeploy package for deployment.
Change the "Build Action" of the config file from "None" to "Content".
Unload your project file and remove <DependentUpon> tag for the config file.
With these "MSBuild Arguments" it works for me:
/p:AutoParameterizationWebConfigConnectionStrings=false
/p:DeployOnBuild=true
/p:MarkWebConfigAssistFilesAsExclude=false
/p:PackageAsSingleFile=false
/p:PackageLocation="$(build.artifactstagingdirectory)\\"
/p:ProfileTransformWebConfigEnabled=false
/p:SkipInvalidConfigurations=true
/p:TransformWebConfigEnabled=false
/p:WebPublishMethod=FileSystem
The important ones:
/p:AutoParameterizationWebConfigConnectionStrings=false
/p:MarkWebConfigAssistFilesAsExclude=false
/p:ProfileTransformWebConfigEnabled=false
/p:TransformWebConfigEnabled=false
It started to work when I also added:
/p:ProfileTransformWebConfigEnabled=false
Thanks to #Eddie Chen - MSFT & #Wessel T.
Regards Hans

sfproj - outpath Build error

Iam trying to build the sfproj using msbuild on my build machine , This is what Iam doing.
<target ="package">
<foreach item="File" property="sfproj">
<in>
<items refid="servicefabric.files.sfproj" />
</in>
<do>
<exec program="${msbuild14.exe}">
<arg value="${sfproj}" />
<arg value="/p:Configuration=${config}" />
<arg value="/p:Platform=x64" />
<arg value="/target:Package" />
</exec>
</do>
</foreach>
</target>
The error Iam getting on the build machine is
(_CheckForInvalidConfigurationAndPlatform target) ->
10:25:10 [exec] C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(724,5): error : The OutputPath property is not set for project 'App.sfproj'. Please check to make sure that you have specified a valid combination of Configuration and Platform for this project. Configuration='Debug' Platform='x64'. You may be seeing this message because you are trying to build a project without a solution file, and have specified a non-default Configuration or Platform that doesn't exist for this project.
It appears that the Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.1.0 Nuget package contains an MSBuild target that skips building of the sfproj itself (that or makes certain options present in order for it to succeed).
In my case I had accidentally checked in some Nuget package folders into source control that were incomplete. Visual Studio saw the presence of the folder so it didn't try to redownload it. You need to make sure that the build folder is present and it contains an MSBuild target file. Your best bet is just to delete the packages folder entirely to ensure you're starting off with a fresh start.
https://ijustwrite.software/2016/07/20/ouputpath-property-not-set/
I ran into a problem with the same symptoms. It happened to me after upgrading the Azure SDK, but it seems to me that it could easily happen in a few ways. The trouble is that there are two paths in the sfproj file that lead into the directory where nuget packages are stored, oddly enough on the first and last line of the file. This causes problems because you may have overridden where nuget packages are stored (this is not at all uncommon). Since these are relative paths, it's easy for nuget to get the packages to where you have specified, but for the lines in the project to point to "..\packages" instead, which makes Visual Studio want to restore the packages, but to never think they have been restored as it's looking for them in the wrong place.
To fix it all you need to do is modify the paths in the sfproj file to point to wherever you have your nuget packages set to download (this setting is in the nuget.config file, which can be in any directory above your project directory).
These are the two broken lines in my sfproj file:
<Import Project="..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props" Condition="Exists('..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props')" />
<Import Project="..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets" Condition="Exists('..\packages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets')" />
Since I have the value ThirdPartyLibraries\NuGetPackages in my nuget.config file (which is two directories above my project file), these lines had to be modified to be the following in order to work again :
<Import Project="..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props" Condition="Exists('..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.props')" />
<Import Project="..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets" Condition="Exists('..\..\ThirdPartyLibraries\NuGetPackages\Microsoft.VisualStudio.Azure.Fabric.MSBuild.1.3.0\build\Microsoft.VisualStudio.Azure.Fabric.Application.targets')" />
And that fixes it right up. I was lucky: I had two sfproj files, one with this issue, and one without, so all I had to do was diff to see the trouble.

How to generate document output from text files in TFS

We store various design documents within TFS in multimarkdown format. We also have an EXE process that can run to take those MMD files and generate PDF's from them - but just by getting the files from a local folder.
What we'd like to do is to have a process run "on-checkin", just as if you'd run an automatic build on checkin (i.e., ultimately calling msbuild to compile an application) but in our case we'd like it to be able to get a list of the checked in files and to process and generate an output of them. The result doesn't need to be in TFS because they're a build output, not the source.
I'm sure this should be somehow possible by taking the same approach as must be taken by the workflow for a "normal" build.
Has anybody done anything like this or can point me in a suitable direction please ?
You could use the exec task in MSBuild to invoke the exe and "build" your output. Create a file called something like buildDocs.proj and check it in to TFS possibly in a folder under the things you want to build. Use the MSbuild below as a guide.
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build">
<Exec Command='"My.exe" -My Paramiters' />
<ItemGroup>
<CopyItems Include="[path to output]\*.*" />
</ItemGroup>
<Copy SourceFiles="#(CopyItems)" DestinationFolder="$(OutDir)\SomeDir" />
</Target>
</Project>
The trick will be in identifying the various paths involved.
Use the default template to build the proj, just as you would a c# project. If you need to pass in additional Parameters to MSBuild you can do this from within the advanced section of the build definition process tab.

MSBuild Publish and Compass Generated Sprites

I'm using compass to generate stylesheets and image sprites for my C# MVC .NET project. Mostly this is great and everything works seamlessly. However, I'd like to be able to use the MSBuild "Publish" functionality as part of my automated build. The problem is that compass generated sprites change names all the time, and so I get errors like this one when I try to build:
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.targets(2972,5): error : Copying file images\icons-s88f86b4a16.png to obj\Release\Package\PackageTmp\images\icons-s88f86b4a16.png failed. Could not find file 'images\icons-s88f86b4a16.png'.
I'm not sure how to work around this. Is there a way to automatically add new images to the csproject and remove old ones? Has anyone run into something similar?
From my personal experience, Web deploy or Publish from Visual Studio will pick up files that are not part of your solution as long as they as they are part of the web application on the file system.
For Example:
MVCSite
-- images/spirtes.png
If you are publishing from this copy of the MVC Site, the contents of the images folder will be replicated on your web server even if they are not included in the project file.
---Edited
The above solution will work with a website and not a web application. The following will work with a web application.
Add this to the end of the Publishing profile (Production.pubxml)
<Target Name="CustomCollectFiles">
<ItemGroup>
<_CustomFiles Include="Test\**\*" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>%(RecursiveDir)%(Filename)%(Extension) </DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
</Project>
Example Build Script
echo 'Hello, world.' > "%WORKSPACE%\TestMVC\Test\fo1.txt"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" "%WORKSPACE%\TestMVC.sln" /p:Configuration=Release /p:Platform="Any CPU" /p:PublishProfile=Production
Output from MSBuild
Copying Test\fo1.txt to obj\Release\Package\PackageTmp\fo1.txt.
Copying Test\foo.txt to obj\Release\Package\PackageTmp\foo.txt.

Including files outside of the project in MSBuild

I have found other posts here on StackOverflow that deal with my issue I am experiencing, for example:
MSBuild: Deploying files that are not included in the project as well as Include Files in MSBuild that are not a part of the project
I wanted to share the code that I was able to create after reading these posts and ask for some help as to why it might not be working?
To elaborate on what exactly is not wrong and what I intend to do. I am using Visual Studio 2012, and TFS 2012.
I have a batch file called CreateMyFiles.bat, and what I would like to do is execute this and then take the files it outputs (it outputs them to my Includes/Javascript/Bundled folder) and include them in part of the build in MSBuild (so that they are deployed to the target IIS server).
When I edited my local .csproj in my local Visual Studio and added the code below to the bottom of the file and reloaded, I was able to right click on my web projbect, select 'publish', and then select my local file-based publishing profile which did indeed deploy my files to the correct location. It worked!
I then checked in my code to TFS, and went to 'builds' on TFS, and queued a new build. Sure enough, I was able to see the files output to the same directory on the build server. Now, i'm not 100% sure about MSBuild but I noticed that just like when I hit publish locally, it created a _publishedWebsite folder on the build server as well (a directory above the source). The thing is, within this publishedwebsite folder, my manually created files were not present. Furthermore, going to the target web server after the build was done unfortunately did not have the files I wanted.
So it seems like if I were to manually select publish, the code below works, but if I were to queue a build with TFS, it does not work. Does MSBuild use publish? Could that be the reason it does not work below?
Here is the code I've placed in my .csproj file:
<Target Name="CustomCollectFiles">
<Exec Command="CreateMyFiles.bat" /> <!-- Generate Files -->
<ItemGroup>
<!-- Create an identity called _CustomFiles, and associate it to the files I created -->
<_CustomFiles Include="Includes\JavaScript\Bundled\*" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>Includes\JavaScript\Bundled\*%(Filename)%(Extension) </DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<!-- Hook into the pipeline responsible for gathering files and tell it to add my files -->
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
I'm really stuck on this and wanted to ask for some help as to why the files might not be going. I suspect MSBuild doesn't use publish and that's why it works locally (because i'm selecting publish)?
Thanks so much for your help
UPDATE
Tried this as per comments below, but this time the files didn't even appear (so it seemed to not even run the tasks now). Any idea why? Did I type this right?
<Target Name="CustomCollectFiles">
<Exec Command="CreateMyFiles.bat" />
<!-- Generate Files -->
<ItemGroup>
<!-- Create an identity called _CustomFiles, and associate it to the files I created -->
<_CustomFiles Include="Includes\JavaScript\Bundled\*" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>Includes\JavaScript\Bundled\*%(Filename)%(Extension) </DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<!-- Hook into the pipeline responsible for gathering files and tell it to add my files -->
<PropertyGroup>
<PipelineCollectFilesPhaseDependsOn>
CustomCollectFiles;
$(PipelineCollectFilesPhaseDependsOn);
</PipelineCollectFilesPhaseDependsOn>
</PropertyGroup>
UPDATE 2
When I take the above code, and place it into my pubxml file and then execute an actual publish, it works, but as far as I know our process is to just queue a build from TFS. Is it possible to hook into the above code block when simply queuing a build? Or do I need to publish?
to do a publish from TFS build you need to add the following arguments
/p:DeployOnBuild=true;PublishProfile=Release
obviously using your own PublishProfile name
In VS2012, the target was renamed from:
CopyAllFilesToSingleFolderForPackageDependsOn
to:
CopyAllFilesToSingleFolderForMsdeployDependsOn
Update: looks like the above Targets are not getting called from within VS2012 targets, can you replace it with a call to the Target "PipelineCollectFilesPhaseDependsOn"? That should fix it.
<PropertyGroup>
<PipelineCollectFilesPhaseDependsOn>
CustomCollectFiles;
$(PipelineCollectFilesPhaseDependsOn);
</PipelineCollectFilesPhaseDependsOn>
</PropertyGroup>

Resources