Xml file transforms using One-click publish, but not Web Deploy - tfs

I have two different ways to deploy my web app - one via IIS Web Deploy, hooked up via MSBuild arguments in a custom build template, and using the One-click publish built into Visual Studio. I have also installed the Slow Cheetah transforms, and can successfully transform Web.config files.
However, I'm stuck on the Nlog.config files. If I use One-click publish, the webapp deploys fine with the updated config file. However, using the actual build process, nothing gets transformed. The source Nlog.config file is copied to the drop locations, the deployment package, and the output folder on the remote server.
The .csproj file has Nlog set up the same way as web.config, i.e.
<Content Include="NLog.config">
<TransformOnBuild>true</TransformOnBuild>
<Content Include="Web.config">
<SubType>Designer</SubType>
<TransformOnBuild>true</TransformOnBuild>
<Content Include="NLog.Debug.config">
<DependentUpon>NLog.config</DependentUpon>
<IsTransformFile>True</IsTransformFile>
The SlowCheetah preview function lets me know that my transform files are well-formed, as well. Not sure what I could be missing.

Well, I really biffed that one. After much poking, I found that I needed to include the following -
<Import Project="TransformsFiles.targets" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>
right above the final Project tag, as Sayed mentions here. This will actually transform the files, but they still won't deploy successfully. I'll have to add in some post-build events or something to take care of that. Not the best solution, but it's working, at least.
UPDATE: Just for comprehension, to deploy the files, I had to edit the .csproj file to include a new target and dump them manually to the remote server, but only after the transformation had completed. Take a look at your log file to see what's going on, then just pick up the transformed file and move it to the remote server. That part of the code looks like this -
<Target Name="PostTransformNLogConfig" AfterTargets="TransformAllFiles">
<Copy Condition="Exists('d:\Builds\Binaries\NLog.config' )"
SourceFiles="d:\Builds\NLog.config"
DestinationFiles="\\remoteserver\NLog.config" />
</Target>

Related

Always remove Angular dist folder contents with WebDeploy on Destination

We've got a single page application on Angular 5 with an ASP.NET backend, and when we compile it, the release contents for Angular are output to a folder "Project\dist".
This works great on local dev machines, but all of the dist files are randomized with different names such as:
polyfills.dc7175a7225af84b3c9b.bundle.js
styles.dc7175a7225af84b3c9b.bundle.js
inline.dc7175a7225af84b3c9b.bundle.js
When we use Web Publishing to deploy to staging or production, everything transfers great and our custom folder in the publish profiles is included and published.
However, on the destination server (staging or production) these old, randomly named files and old (no longer used) folders persist. This results in hundreds and hundreds of old files (from old web deploys) that have accumulated on the staging and production servers. I need a method to automatically delete these every time we push updates with webdeploy.
Ideally, the workflow is:
Select publish profile, click Publish
Enter my credentials
Application builds successfully
If app built successfully, we go delete "Project\dist" folder on the destination server. "Project" could be in c:\inetpub\www\project or d:\websites\Project, for example.
Updated files are copied
Web deploy executes and copies the custom files in dist folder (already working).
Here's a redacted version of our current publish profile:
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit http://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<MSDeployServiceURL>staging.example.com</MSDeployServiceURL>
<DeployIisAppPath>Project</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>True</SkipExtraFilesOnServer>
<MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
<EnableMSDeployBackup>False</EnableMSDeployBackup>
<UserName>WebDeployUser</UserName>
<PublishDatabaseSettings>
<Objects xmlns="">
</Objects>
</PublishDatabaseSettings>
<ADUsesOwinOrOpenIdConnect>False</ADUsesOwinOrOpenIdConnect>
</PropertyGroup>
<Target Name="CustomCollectFiles">
<ItemGroup>
<_CustomFiles Include="..\Project\dist\**\*" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>dist\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
<CopyAllFilesToSingleFolderForMsdeployDependsOn>
CustomCollectFiles;
$(CopyAllFilesToSingleFolderForMsdeployDependsOn);
</CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>
</Project>
I've tried a few accepted answer solutions already and can't get this to work:
https://stackoverflow.com/a/45538847/559988 (I tried this in the csproj and in the publish profile pubxml file.)
https://stackoverflow.com/a/5080942/559988
Any ideas? I have essentially zero knowledge of web deploy aside from setting it up in IIS.
Best,
Chris
EDIT I've also tried this: (Based on this: https://stackoverflow.com/a/15113445/559988)
<Target Name="CleanFolder">
<PropertyGroup>
<TargetFolder>$(_MSDeployDirPath_FullPath)\dist</TargetFolder>
</PropertyGroup>
<ItemGroup>
<FilesToClean Include="$(TargetFolder)\**\*"/>
<Directories Include="$([System.IO.Directory]::GetDirectories('$(TargetFolder)', '*', System.IO.SearchOption.AllDirectories))"
Exclude="$(TargetFolder)"/>
</ItemGroup>
<Delete Files="#(FilesToClean)" ContinueOnError="true"/>
<RemoveDir Directories="#(Directories)" />
</Target>
Update
This is specifically what we're doing: https://learn.microsoft.com/en-us/aspnet/web-forms/overview/deployment/visual-studio-web-deployment/deploying-extra-files
The first comment from there is the same problem we're experiencing:
This comes very handy in deploying Angular distribution files along
with ASP.Net backend, whenever both SPA and the backend share the same
single virtual application. Unfortunately, due to browser cache
busting techniques, the bundle files for Angular deployment will
always ship with unique names and, therefore, an msbuild
command/attribute or other possibility to wipe the folder clean on the
IIS side before sending the updated files would be very welcomed. If
anyone has found a way to do that, please share.
"Sync" functionality described here for msdeploy is exactly what we need to be doing but I don't know how to hook into this:
https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd569034(v=ws.10)#sync
In a sync operation, if the source file or folder does not exist on
the destination, the provider creates the folder and any subfolders
that have the corresponding files and attributes. If the destination
folder already exists, the provider updates only those objects that do
not match the source. This means that in some cases only one file or
folder will be updated. Files on the destination that do not exist on
the source will be deleted. The source and destination folders for
contentPath do not have to have the same name. If the name of the
destination folder differs from that of the source, the name of the
destination folder will remain the same, but the contents of the
folder will be updated to those of the source.
If I understood you correctly, then resolve this problem is help DeleteExistingFiles property in publish profile.
<DeleteExistingFiles>True</DeleteExistingFiles>
If set to True the output directory (publishUrl) will be purged
before output is written to it, it's good to start out with a clean
slate.
How can I apply this to a specific folder, ex. only delete the
"Project\dist" folder?
As I know result of this property is removing all files. For specify directories to remove you can try verb delete from MSDeploy, that can be wrap in <Exec> task of custom script:
<Exec Command="$(MSDeploy) -verb:delete -dest:"ContentPath=D:\TestDir\Test.txt&quot"/>
/*
* $(MSDeploy) is path to MSDeploy binary that you passed to script.
*/
This example show removing file on local machine. You should customize own call.
Exec Task | How to create a Web Deploy package when publishing a ClickOnce project (Some snippets for using targets)
Try please setting this in publish profile
<SkipExtraFilesOnServer>False</SkipExtraFilesOnServer>
in angular with nodejs, we will handle this problem with 'ng build --output-hashing=false'. Maybe you can search in this scope.

How to include config transform files in web application filesystem publish output

I've been pulling my hair out on this for a while now. I'm trying to implement a continuous integration and deployment pipeline using TeamCity and Octopus Deploy. I am 99% there, except for one problem. I am using the standard msbuild runner of teamcity, configured to use the version 12 of msbuild.
I need to include the web.config transforms in the published output so they can be packaged into a nuget package for octopus deploy. I do not want the transforms to be applied by msbuild.
I am not using Octopack to create packages. I'm using the built-in teamcity nuget packager. So I'm publishing the website to a filesystem folder and then creating the package from the files in this folder. However, no matter what I do I cannot get msbuild to include the web.config transform files in the publish (I am using Octopus Deploy to perform the transforms, so I don't want msbuild to perform them).
I have verified that all the transform files (Web.Release.config, etc..) are marked as "Content". I have NOT marked them to copy always, because doing this copies them to the bin folder, not the root folder where they belong.
I have removed the /p:Configuration= property from the msbuild command line as I've read that is required for transforms to be applied. my parameters to msbuild look like this:
/p:DeployOnBuild=true /p:PublishProfile=Deployment
There is nothing in the publish profile that seems to relate to transforms. The publish profile contains the filesystem location to publish to.
Any suggestions here?
Note: I've given up and found a different solution, but I'm leaving this open in case anyone has any input.
You could create a custom .nuspec file and reference the files that you want to include from there.
My suggestion would be to have the .nuspec file in the same directory as the web.config / web.release.config files, and make the paths relative from there.
So if you publish to a directory called /output you could use rules like this
<files>
<file src="*.config" target="\" />
<file src="publish\*.*" target="\" />
</files>
So nuget pack nuspecPath would become the way to pack the project
NuSpec Reference
Hope this helps

TFSBuild.proj import common targets after getting from source control

I am currently rewriting/cleaning up our TFS Builds, and I have noticed that we have a lot of duplication. I was wanting to create a "Common.targets" file which all our tfs builds (about 30) would then import.
I have tried a few things along these lines of:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft.Sdc.Common.Tasks"/>
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Import Project="$(BuildDefinition).proj"/>
<PropertyGroup>
<ProjectName>Felix.LincsPublishingHandlers</ProjectName>
</PropertyGroup>
<Import Project="$(BuildDirectory)/src/BuildScripts/Common/CommonTargets.targets"/>
</Project>
Where:
$(BuildDefinition).proj -> contains project/environment specific properties (eg db connection strings, IP addresses)
CommonTargets.targets -> used across all the various projects. Defined in the Build Definition > Source Settings
However when I try to run on the TFS Build agent (version 2010 if anyone needs to know), it tries to run the statements before it has copied down the source files. So I get an error saying that the CommonTargets.targets file doesn't exist.
I've had a look around the web, and the solutions I have come across so far have suggestions that I would prefer not to use:
Copy the Common.targets file under into the same folder that has the TFSBuild.proj file
I have 20+ builds in different folders
I would like them all to use a single common.targets file (not 20 copies of the same file)
Copy the Common.targets file to a location on the build server(s) that the TFS Build Agents can access
I want to have it in source control with the rest of the build scripts and code.
As we are doing a sort of migration project, the Common.targets will change a bit over time, so best to have in source control
Has come across any similar issue of trying to import a Common.targets file that is kept in TFS source control?
Thanks for any help
First, you should consider upgrading TFS as mainstream support for TFS 2010 ends in a few months.
http://nakedalm.com/its-that-time-again-get-ready-to-upgrade-to-tfs-2015/
Second, you should consider updating your build system to use a modern method. Use the TFS 2013 template and PowerShell.

How to I perform a web.config transform with MSBuild or MSDeploy?

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.

Overriding the PostBuildEvent in TFSBuild.proj

I'm using currently VS 2010 and TFS 2008. In one of our solutions we have a .csproj file with the following:
<Target Name="BeforeBuild">
<TransformXml Source="..\..\..\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.config" Transform="..\..\..\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.$(Configuration).config" Destination="..\..\..\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.$(Configuration).config.transformed" />
</Target>
<PropertyGroup>
<PostBuildEvent>xcopy "C:\Source\Projects\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.$(Configuration).config.transformed" "C:\Source\Projects\ProjectX\ProjectXWebSite\ProjectXWebSite\Web.config" /R /Y</PostBuildEvent>
</PropertyGroup>
This works fine when building locally, but TFS is failing when it reaches <PostBuildEvent>, because this path is not available on the build machine. How can I get TFS to either skip the entire "BeforeBuild" or just set the <PostBuildEvent> to an empty string, so that the build will work successfully when built locally in VS2010 and via TFS 2008? I've tried within TFSBuild.proj to set <Target Name="BeforeBuild" />, but this doesn't work.
I'd recommend against using absolute pathnames in your projects - relative paths will make them relocatable and headaches like this will disappear.
i.e. In the post-build xcopy, just change C:\Source\Projects\ProjectX to ..\..\..\ProjectX
(You can't rely on always being able to put your code on C: - I've had to move my code from C: to D: or E: due to lack of disk space or installation of a new drive on several occasions, and using relative paths has made this process very easy. It's also very handy at times to be able to have multiple copies (different versions or branches) of your source code on your PC at once and still be able to build them all)

Resources