MSBuild Publish and Compass Generated Sprites - asp.net-mvc

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.

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 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.

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>

Avoid deleting folder on Web Publish

I'm deploying my application to an Azure Website. I've configured the Publishing Profile succesfuly and setup tfspreview.com to publish automatically using continuous integration on each code commit.
I have a folder on the path "/media". This folder has pictures and documents uploaded through the CMS (umbraco). This folder gets deleted on each web deploy.
From this answer, I learned how to add a SkipDelete rule on either the .csproj or on the wpp.targets file, but everytime I publish the site the whole folder gets deleted anyway.
Here is the code I'm currently using inside wpp.targets:
<PropertyGroup>
<AfterAddIisSettingAndFileContentsToSourceManifest>
AddCustomSkipRules
</AfterAddIisSettingAndFileContentsToSourceManifest>
</PropertyGroup>
<Target Name="AddCustomSkipRules">
<Message Text="Adding Custom Skip Rules" />
<ItemGroup>
<MsDeploySkipRules Include="SkipMediaFolder">
<SkipAction>Delete</SkipAction>
<ObjectName>filePath</ObjectName>
<AbsolutePath>media</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
</Target>
<PropertyGroup>
<UseMsDeployExe>true</UseMsDeployExe>
</PropertyGroup>
Is this not just an issue of unchecking the box in the publish wizard (settings step) that says "Delete all existing files prior to publish"? I know that option is available when setting up publishing from the Visual Studio side - it seems to me the Azure publishing credentials just give you the connection, and not the settings which you make through the wizard.
Looking over the question you are linking to and the code you have supplied above, it seems that you need to change the line:
<AbsolutePath>ErrorLog</AbsolutePath>
to
<AbsolutePath>media</AbsolutePath>
as this refers to the folder you do not want to delete. ErrorLog was the folder the other question's author did not want to delete.

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.

Resources