When automating web publishing for Visual Studio projects in many cases your first step will be to create a publish profile for the project in VS. There are many cases in which you cannot, or would not like to do this. In this post I’ll show you how you can take an existing project and use an MSBuild file to drive the publish process. In that I’ll also show how you can extend the publish process without modifying either the project or any of its contents.
Before we get too far into this, if you are not familiar with how to publish your VS web projects from the command line you can read our docs at ttp://www.asp.net/mvc/tutorials/deployment/visual-studio-web-deployment/command-line-deployment.
When you publish a project from the command line using a publish profile you typically use the syntax below.
msbuild .\MyProject.csproj /p:VisualStudioVersion=11.0 /p:DeployOnBuild=true /p:PublishProfile=<profile-name-or-path>
In this snippet we are passing in a handful of properties. VisualStudioVersion dictates which version of MSBuild targets are used during the build. See http://sedodream.com/2012/08/19/VisualStudioProjectCompatabilityAndVisualStudioVersion.aspx for more details on that. DeployOnBuild=true injects the publish process at the end of build. PublishProfile can either be the name of a publish profile which the project contains or it can be the full path to a publish profile. We will use PublishProfile with that second option, the full path.
So we need to pass in the full path to a publish profile, which typically is a .pubxml file. A publish profile is just an MSBuild file. When you pass in PublishProfile and DeployOnBuild=true, then the publish profile is Imported into the build/publish process. It will supply the publish properties needed to perform the publish.
Let’s see how that works. I have a sample project, MySite, which does not have any publish profiles created for it. I have created a publish profile, ToFileSys.pubxml, in another folder that will be used though. The contents of that file are below.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <WebPublishMethod>FileSystem</WebPublishMethod> <ExcludeApp_Data>False</ExcludeApp_Data> <publishUrl>C:\temp\Publish\01</publishUrl> <DeleteExistingFiles>False</DeleteExistingFiles> </PropertyGroup> </Project>
This publish profile will publish to a local folder. I just created this file in VS with a different project and then just copied it to the folder that I needed, and removed properties which are only used for the inside of VS experience. We can publish the MySite project using this profile with the command below.
msbuild MySite.csproj /p:VisualStudioVersion=11.0 /p:DeployOnBuild=true /p:PublishProfile=C:\data\my-code\publish-samples\publish-injection\build\ToFileSys.pubxml
When you execute this the file specified in PublishProfile will be included into the build process.
Taking it up a level
Now let’s see how we can take this to the next level by having a single script that will be used to publish more than one project using this technique.
In the sample files (which you can find links for at the end of the post). I have a solution with two web projects, MySite and MyOtherSite. Neither of these projects have any publish profiles created. I have created a script which will build/publish these projects which you can find at build\publish.proj in the samples. The contents of the file are shown below.
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="BuildProjects"> <!-- This file is used in two ways. 1. Drive the build and publish process 2. It is used by the publish process during the build of MySite to configure/extend publish Note: 1. Is kicked off by the use on the cmd line/build server. 2. Is invoked by this script itself. This file is injected into the publish process via the PublishProfile property. --> <PropertyGroup> <VisualStudioVersion Condition=" '$(VisualStudioVersion)'=='' ">11.0</VisualStudioVersion> <Configuration Condition=" '$(Configuration)'=='' ">Release</Configuration> <!-- Location for build output of the project --> <OutputRoot Condition=" '$(OutputRoot)'=='' ">$(MSBuildThisFileDirectory)..\BuildOutput\</OutputRoot> <!-- Root for the publish output --> <PublishFolder Condition=" '$(PublishFolder)'==''">C:\temp\Publish\Output\</PublishFolder> </PropertyGroup> <ItemGroup> <ProjectsToBuild Include="$(MSBuildThisFileDirectory)..\MySite\MySite.csproj"> <AdditionalProperties> VisualStudioVersion=$(VisualStudioVersion); Configuration=$(Configuration); OutputPath=$(OutputRoot); WebPublishMethod=FileSystem; publishUrl=$(PublishFolder)MySite\; DeployOnBuild=true; DeployTarget=WebPublish; PublishProfile=$(MSBuildThisFileFullPath) </AdditionalProperties> </ProjectsToBuild> <ProjectsToBuild Include="$(MSBuildThisFileDirectory)..\MyOtherSite\MyOtherSite.csproj"> <AdditionalProperties> VisualStudioVersion=$(VisualStudioVersion); Configuration=$(Configuration); OutputPath=$(OutputRoot); WebPublishMethod=FileSystem; publishUrl=$(PublishFolder)MyOtherSite\; DeployOnBuild=true; DeployTarget=WebPublish; PublishProfile=$(MSBuildThisFileFullPath) </AdditionalProperties> </ProjectsToBuild> </ItemGroup> <Target Name="BuildProjects"> <MSBuild Projects="@(ProjectsToBuild)" /> </Target> <!-- *************************************************************************************** The targets below will be called during the publish process. These targets are injected into the publish process for each web project. These targets will not have access to any new values for properties/items from the targets above this. *************************************************************************************** --> <Target Name="AfterWebPublish" AfterTargets="WebPublish"> <Message Text="Inside AfterWebPublish" Importance="high"/> </Target> </Project>
This file is pretty simple, it declares some properties which will be used for the build/publish process. Then it declares the projects to be built with an item list named ProjectsToBuild. When declaring ProjectsToBuild I use the AdditionalProperties metadata to specify MSBuild properties to be used during the build process for each project. Let’s take a closer look at those properties.
<AdditionalProperties> VisualStudioVersion=$(VisualStudioVersion); Configuration=$(Configuration); OutputPath=$(OutputRoot); WebPublishMethod=FileSystem; publishUrl=$(PublishFolder)MySite\; DeployOnBuild=true; DeployTarget=WebPublish; PublishProfile=$(MSBuildThisFileFullPath) </AdditionalProperties>
I’ll explain all the properties now. VisualStudioVersion, Configuration and OutputPath are all used for the build process. The other properties are related to publishing. If you want to publish from the file system those properties (WebPublishMethod, publishUrl, DeployOnBuild, and DeployTarget) must be set. The most important property there is PublishProfile.
PublishProfile is set to $(MSBuildThisFileFullPath) which is the full path to publish.proj. This will instruct the build process of that project to import publish.proj when its build/publish process is started. It’s important to note that a “new instance” of the file will be imported. What that means is that the imported version of publish.proj won’t have access to any dynamic properties/items created in publish.proj.
The reason why PublishProfile is specified there is so that we can extend the publish process from within publish.proj itself. publish.proj has a target, AfterWebPublish, which will be executed after each project is executed. Let’s see how this works.
We can execute the publish process with the command below.
msbuild .\build\publish.proj /p:VisualStudioVersion=11.0
After executing this command the tail end of the result is shown in the image below.
In the image above you can see that the MyOtherSite project is being publish to the specified location in publish.proj and the AfterWebPublish target is executed as well.
In this post we’ve seen how we can use an MSBuild file as a publish profile, and how to extend the publish process using that same file.
You can download the samples at https://dl.dropboxusercontent.com/u/40134810/blog/publish-injection.zip. You can find the latest version in my publish-samples repository at publish-injection.