When you publish a .Net web app, you typically set up a publish profile, either to do an immediate build-and-publish, or to publish to a package for later deployment (eg. single-trunk / build-once-deploy-many-times scenarios).
So for building a package, the MsBuild syntax would look like:
msbuild WebApp\WebApp.csproj /t:Build /p:VisualStudioVersion=11.0;DeployOnBuild=True;PublishProfile="DeploymentPackage.pubxml";ProfileTransformWebConfigEnabled=False;Configuration=Release;
You just use MsBuild and ask your project to build itself. The good bit is that your project knows what it contains, and therefore what needs deployed. The bad bit is that your project knows what it contains, and therefore what needs deployed… Any files generated by an external tool, or any other files you didn’t explicitly tell Visual Studio (listed as content in the .csproj file) are excluded. Thankfully, you can include extra stuff into your deployment package with a few lines of MsBuild commands. While you can probably hack the .csproj, the easiest way I’ve found is just to plug some MsBuild directly into the publish profile.
This location does make sense – you’re specifying what gets published after all. Note that if you have several publish profiles (maybe you have branch-based deployment and per-environment build-and-publish) then things get more complicated. I’m going to stick with the simple case – a single publish profile for creating a generic deployment package that we can then deploy (with appropriate configuration) to any target environment.
I recently had to solve this problem for handling some generated CSS, but you can use it for any non-solution files you want to include as part of the web application deployment package zip.
A quick warning – this does involve editing a generated file (the .pubxml file that you probably only create once).
Tweaking The Publish Profile
So assume I have some tools generating CSS (in “/webproj/content/styles”) and extra text files (in “/webproj/content/documentation”). I don’t even need to set up tools to generate these files – I can actually just simulate by manually adding some dummy files. I’m assuming that the files get generated within the project directory (because you want to deploy them to IIS or Azure when you deploy your site).
Anyway, create the extra files within the project directory, don’t tell Visual Studio or your project about them, and then build the deployment package. You’ll see they get omitted. We can fix that.
Go edit your publish profile, and add these lines inside the “PropertyGroup” element – we need to add a “CopyAllFilesToSingleFolderForMsdeployDependsOn” property.
<CopyAllFilesToSingleFolderForMsdeployDependsOn> IncludeCustomFilesInPackage; $(CopyAllFilesToSingleFolderForMsdeployDependsOn); </CopyAllFilesToSingleFolderForMsdeployDependsOn> </PropertyGroup>
This goes off and calls a target called “IncludeCustomFilesInPackage” that we’ll create in a minute. The name of the “CopyAllFilesToSingleFolderForMsdeployDependsOn” property is important (MsBuild will look for it), the name of the custom “IncludeCustomFilesInPackage” target can be whatever we want (change the names and see what happens).
There is also a “CopyAllFilesToSingleFolderForPackageDependsOn” property, but this seems to get ignored even when building in package mode. For completeness, it would look like this:
<CopyAllFilesToSingleFolderForPackageDependsOn> IncludeCustomFilesInPackage; $(CopyAllFilesToSingleFolderForPackageDependsOn); </CopyAllFilesToSingleFolderForPackageDependsOn>
But you don’t seem to need it – just add the “CopyAllFilesToSingleFolderForMsdeployDependsOn”.
Now we can go define our “IncludeCustomFilesInPackage” target, so add this to the .pubxml file, inside the “Project” element:
<Target Name="IncludeCustomFilesInPackage"> <Message Text="Collecting custom files..."/> <ItemGroup> <CustomFiles Include="Content\styles\**\*" /> <CustomFiles Include="Content\documentation\**\*" /> <FilesForPackagingFromProject Include="%(CustomFiles.Identity)"> <DestinationRelativePath>%(CustomFiles.Identity)</DestinationRelativePath> </FilesForPackagingFromProject> </ItemGroup> <Message Text="Add to package %(FilesForPackagingFromProject.Identity)"/> </Target>
Add as many entries as you need into the ItemGroup. I added a couple of “message” calls to output progress.
So when you run your package build and inspect the final .zip package that gets created (check in the “obj” directory eg. “projectName/obj/projectName/projectName.zip”) then it should contain all those extra files that your .csproj didn’t know about.
When you’re setting this up, you might run into problems getting the paths correct. You can always add in a load of Message calls, and redirect the output of your MsBuild run to a text file.
You can add the following in your custom target for debugging purposes:
<ItemGroup> <GeneratedIncludeFiles Include="Content\documentation\**\*" /> </ItemGroup> <Message Text="Generated files to include: %(GeneratedIncludeFiles.Identity)"/>
A note about “DestinationRelativePath” – I have seen this specified as “%(RecursiveDir)%(Filename)%(Extension)” instead of using the “CustomFiles” item group, but I had trouble getting this to actually include the custom files.