- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Saturday, September 21, 2013

How to extend the web publish process without modifying project contents

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.

ToFileSys.pubxml

<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

steps

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.

publish.proj

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

image

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.

Sayed Ibrahim Hashimi | http://msbuildbook.com | @SayedIHashimi

msbuild | Visual Studio | Visual Studio 2012 | web | Web Publishing Pipeline Saturday, September 21, 2013 7:57:03 PM (GMT Daylight Time, UTC+01:00)  #     | 
Wednesday, June 05, 2013

How to publish a VS web project with a .publishSettings file

The easiest way to publish a Visual Studio web project from the command line is to follow the steps below.

  1. Open VS
  2. Right click on the Web project and select Publish
  3. Import your .publishSettings file (or manually configure the settings)
  4. Save the profile (i.e. .pubxml file)
  5. From the command line publish with the command line passing in PublishProfile

For more details on this you can see ASP.NET Command Line Deployment. This is pretty simple and very easy, but it does require that you manually create the .pubxml file. In some cases you’d just like to download the .publishsettings file from your hosting provider and use that from the command line. This post will show you how to do this.

In order to achieve the goal we will need to extend the build/publish process. There are two simple ways to do this; 1. Place a .wpp.targets file in the same directory as the web project or 2. Pass an additional property indicating the location of the .wpp.targets file. I’ll first go over the technique where you place the file directly inside of the directory where the project is. After that I’ll show you how to use this file from a well known location.

One way to do this is to create a .wpp.targets file. This .wpp.targets file will be imported into the build/publish process automatically. This .targets file will enable us to pass in PublishSettingsFile as an MSBuild property. It will then read the .publishsettings file and output the properties needed to execute the publish process.

.wpp.targets in the project directory

Let’s take a look at the .targets file and then we will discuss it’s contents. Below you will find the contents of the full file.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
  
  <!--
  When using this file you must supply /p:PublishSettingsFile as a parameter and /p:DeployOnBuild=true
  -->  
  <PropertyGroup Condition=" Exists('$(PublishSettingsFile)')">
    <!-- These must be declared outside of a Target because they impact the Import Project flow -->
    <WebPublishMethod>MSDeploy</WebPublishMethod>
    <DeployTarget>WebPublish</DeployTarget>
  
    <PipelineDependsOn>
      GetPublishPropertiesFromPublishSettings;
      $(PipelineDependsOn);
    </PipelineDependsOn>
  </PropertyGroup>

  <Target Name="GetPublishPropertiesFromPublishSettings" BeforeTargets="Build" Condition=" Exists('$(PublishSettingsFile)')">
    <PropertyGroup>
      <_BaseQuery>/publishData/publishProfile[@publishMethod='MSDeploy'][1]/</_BaseQuery>
      <!-- This value is not in the .publishSettings file and needs to be specified, it can be overridden with a cmd line parameter -->
      <!-- If you are using the Remote Agent then specify this as RemoteAgent -->
      <MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
    </PropertyGroup>

    <ItemGroup>
      <_MSDeployXPath Include="WebPublishMethod">
        <Query>$(_BaseQuery)@publishMethod</Query>
      </_MSDeployXPath>

      <_MSDeployXPath Include="MSDeployServiceURL">
        <Query>$(_BaseQuery)@publishUrl</Query>
      </_MSDeployXPath>

      <_MSDeployXPath Include="SiteUrlToLaunchAfterPublish">
        <Query>$(_BaseQuery)@destinationAppUrl</Query>
      </_MSDeployXPath>

      <_MSDeployXPath Include="DeployIisAppPath">
        <Query>$(_BaseQuery)@msdeploySite</Query>
      </_MSDeployXPath>

      <_MSDeployXPath Include="UserName">
        <Query>$(_BaseQuery)@userName</Query>
      </_MSDeployXPath>

      <_MSDeployXPath Include="Password">
        <Query>$(_BaseQuery)@userPWD</Query>
      </_MSDeployXPath>
    </ItemGroup>

    <XmlPeek XmlInputPath="$(PublishSettingsFile)"
             Query="%(_MSDeployXPath.Query)"
             Condition=" Exists('$(PublishSettingsFile)')">
      <Output TaskParameter="Result" PropertyName="%(_MSDeployXPath.Identity)"/>
    </XmlPeek>
  </Target>
</Project>

You can place this file in the root of your project (next to the .csproj/.vbproj file) with the name {ProjectName}.wpp.targets.

This .targets file is pretty simple. It defines a couple properties and a single target, GetPublishPropertiesFromPublishSettings. In order to publish your project from the command line you would execute the command below.

msbuild.exe MyProject /p:VisualStudioVersion=11.0 /p:DeployOnBuild=true /p:PublishSettingsFile=<path-to-.publishsettings>

Here is some info on the properties that are being passed in.

The VisualStudioVersion property indicates that we are using the VS 2012 targets. More info on this at http://sedodream.com/2012/08/19/VisualStudioProjectCompatabilityAndVisualStudioVersion.aspx.

DeployOnBuild, when true it indicates that we want to publish the project. This is the same property that you would normally pass in.

PublishSettingsFile, this is a new property which the .targets file recognizes. It should be set to the path of the .publishSettings file.

 

The properties at the top of the .targets file (WebPublishMethod and DeployTarget) indicate what type of publish operation is happening. The default values for those are MSDeploy and WebPublish respectively. You shouldn’t need to change those, but if you do you can pass them in on the command line.

The GetPublishPropertiesFromPublishSettings target uses the XmlPeek task to read the .publishsettings file. It then emits the properties required for publishing.

OK this is great an all, but it still requires that an additional file (the .wpp.targets file) be placed in the directory of project. It is possible to avoid this as well. Let’s move to that

.wpp.targets from a well known location

If you’d like to avoid having to place the .wpp.targets file in the directory of the project you can easily do this. Place the file in a well known location and then execute the same msbuild.exe call adding an additional property. See the full command below.

msbuild.exe MyProject /p:VisualStudioVersion=11.0 /p:DeployOnBuild=true /p:PublishSettingsFile=<path-to-.publishsettings> /p:WebPublishPipelineCustomizeTargetFile=<path-to.targets-file>

Once you do this you no longer need to create the Publish Profile in VS if you want to publish from the command line with a .publishsettings file.

 

FYI you can find the complete sample at https://github.com/sayedihashimi/publish-samples/tree/master/PubWithPublishSettings.

 

Sayed Ibrahim Hashimi | http://msbuildbook.com | @SayedIHashimi

msbuild | web | Web Publishing Pipeline Wednesday, June 05, 2013 6:01:34 PM (GMT Daylight Time, UTC+01:00)  #     | 
Thursday, May 09, 2013

Book published and now in stock!

I’m happy to say that my Supplement to Inside the Microsoft Build Engine book (co-author William Bartholomew) has now been published. In fact it’s already in stock and ready to be shipped by Amazon.com.

This book is a small addition (118 pages) to the previous book, Inside the Microsoft Build Engine 2nd edition. It has a small price too, MSRP is $12.99 but it’s selling on Amazon.com for $8.99! In this book we cover the updates to MSBuild, Team Build and Web Publishing in Visual Studio 2012. The foreword was written by Scott Hanselman, and you can read the entire foreword online.

Check out how thin the supplement is in comparison to the 2nd edition #ThinIsIn.

945231_10103483509401051_968543011_n

 

If you already own the 2nd edition then you’ll love this update.

 

Table of Contents

Chapter 1: What's new in MSBuild

  1. Visual Studio project compatibility between 2010 and 2012
  2. Out of Process Tasks
  3. NuGet
  4. XML Updates with SlowCheetah
  5. Cookbook
Chapter 2: What's new in Team Build 2012
  1. Installation
  2. Team Foundation Service
  3. User interface (UI) enhancements
  4. Visual Studio Test Runner
  5. Pausing build definitions
  6. Batching
  7. Logging
  8. Windows Workflow Foundation 4.5
  9. Cookbook
Chapter 3: What's new in Web Publishing
  1. Overview of the new Publish Web Dialog
  2. Building web packages
  3. Publish profiles
  4. Database publishing support
  5. Profile-specific web.config transforms
  6. Cookbook

 

The book has been available in e-book form for a few weeks. Just long enough for us to get our first review. It was 5 stars :).

image

 

Please let us know what you think of this book!

 

You can download all the samples and learn more at msbuildbook.com.

 

Sayed Ibrahim Hashimi | http://msbuildbook.com | @SayedIHashimi

msbuild | MSDeploy | Team Build | web | Web Publishing Pipeline Thursday, May 09, 2013 6:33:20 AM (GMT Daylight Time, UTC+01:00)  #     | 
Wednesday, March 06, 2013

How to publish one web project from a solution

Today on twitter @nunofcosta asked me roughly the question “How do I publish one web project from a solution that contains many?

The issue that he is running into is that he is building from the command line and passing the following properties to msbuild.exe.

    /p:DeployOnBuild=true
    /p:PublishProfile='siteone - Web Deploy'
    /p:Password=%password%

You can read more about how to automate publishing at http://sedodream.com/2013/01/06/CommandLineWebProjectPublishing.aspx.

When you pass these properties to msbuild.exe they are known as global properties. These properties are difficult to override and are passed to every project that is built. Because of this if you have a solution with multiple web projects, when each web project is built it is passed in the same set of properties. Because of this when each project is built the publish process for that project will start and it will expect to find a file named siteone – Web Deploy.pubxml in the folder Properties\PublishProfiles\. If the file doesn’t exist the operation may fail.

Note: If you are interested in using this technique for an orchestrated publish see my comments at http://stackoverflow.com/a/14231729/105999 before doing so.

So how can we resolve this?

Let’s take a look at a sample (see links below). I have a solution, PublishOnlyOne, with the following projects.

  1. ProjA
  2. ProjB

ProjA has a publish profile named ‘siteone – Web Deploy’, ProjB does not. When trying to publish this you may try the following command line.

    msbuild.exe PublishOnlyOne.sln /p:DeployOnBuild=true /p:PublishProfile=’siteone – Web Deploy’ /p:Password=%password%

See publish-sln.cmd in the samples.

If you do this, when its time for ProjB to build it will fail because there’s no siteone – Web Deploy profile for that project. Because of this, we cannot pass DeployOnBuild. Instead here is what we need to do.

  1. Edit ProjA.csproj to define another property which will conditionally set DeployOnBuild
  2. From the command line pass in that property

 

I edited ProjA and added the following property group before the Import statements in the .csproj file.

<PropertyGroup>
  <DeployOnBuild Condition=" '$(DeployProjA)'!='' ">$(DeployProjA)</DeployOnBuild>
</PropertyGroup>

 

Here you can see that DeployOnBuild is set to whatever value DeployProjA is as long as it’s not empty. Now the revised command is:

    msbuild.exe PublishOnlyOne.sln /p:DeployProjA=true /p:PublishProfile=’siteone – Web Deploy’ /p:Password=%password%

Here instead of passing DeployOnBuild, I pass in DeployProjA which will then set DeployOnBuild. Since DeployOnBuild wasn’t passed to ProjB it will not attempt to publish.

 

You can find the complete sample at https://github.com/sayedihashimi/sayed-samples/tree/master/PublishOnlyOne.

 

Sayed Ibrahim Hashimi | @SayedIHashimi | http://msbuildbook.com/

MSDeploy | web | Web Deployment Tool | Web Development | Web Publishing Pipeline Wednesday, March 06, 2013 2:48:41 AM (GMT Standard Time, UTC+00:00)  #     | 
Sunday, August 19, 2012

Visual Studio project compatability and VisualStudioVersion

One of the most requested features of Visual Studio 2012 was the ability to open projects in both VS 2012 as well as VS 2010 (requires VS 2010 SP1). In case you haven’t heard we did implement that feature. You may be wondering how we were able to do this and how this may impact you.

If you open the .csproj/.vbproj for a Web Project created in VS2010 you will see the following import statement.

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\
v10.0\WebApplications\Microsoft.WebApplication.targets" />

When you open this project in VS 2012 there are a few changes made to your project file to ensure that it can be opened in both VS 2010 SP1 and VS 2012. One of the changes made to the project when it is first loaded in VS 2012 is to add the following to replace that import statement.

<PropertyGroup>
  <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
  <VSToolsPath Condition="'$(VSToolsPath)' == ''">
    $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />

We removed the hard-coded 10.0 and instead used the property VisualStudioVersion. When building in Visual Studio 2012 this value will always be 11.0, but for VS 2010 it doesn’t exist. That is why we defaulted it to 10.0 above.

There are some scenarios where building from the command line will require to set this property explicitly. Before we get there let me explain how this property gets set (in this order)

  1. If VisualStudioVersion is defined as an environment variable/global MSBuild property, that is used.
    • This is how VS and the VS developer command prompt set this value
  2. Based on the file format version of the .sln file (toolset used is sln file format –1)
    • To simplify this statement, the .sln file will build with specifying VisualStudioVersion to the value of the version of VS which created the .sln file.
  3. Choose default
    • 10.0 if VS 2010 is installed
    • Highest-versioned sub-toolset version installed

For #2 when you are building a .sln file the value of VisulStudioVersion will be –1 of the Format Version found in the .sln file. The important thing to note here is that if you build a .sln file it will build with the value of VisulStudioVersion corresponding to the version of VS which created the .sln file. So if you create a .sln file in VS2012 and you always build that .sln file the value for VisualStudioVersion will be 11.0. In many cases if you build the .sln file you are good.

If you are building .csproj/.vbproj files w/o going through a .sln file? If you build a web project from the command line (not the developer prompt) then the value for VisualStudioVersion used will be 10.0. That is an artifact of the properties which I showed above. In this case you should pass this in as an MSBuild property. For example

msbuild.exe MyAwesomeWeb.csproj /p:VisualStudioVersion=11.0

In this case I’m passing in the property explicitly. This will always override any other mechanism to determine the value for VisualStudioVersion. If you are using the MSBuild task in a build script, then you can specify the property either in the Properties attribute or the AdditionalProperties attribute. See my previous blog post on the difference between Properties and AdditionalProperties.

If you encounter any funny behavior when building/publishing and you notice that the wrong .targets files are being imported then you may need to specify this property.

Sayed Ibrahim Hashimi | @SayedIHashimi

msbuild | Visual Studio | Visual Studio 11 | Visual Studio 2010 | Visual Studio 2012 | web | Web Publishing Pipeline Sunday, August 19, 2012 10:06:56 PM (GMT Daylight Time, UTC+01:00)  #     | 
Thursday, August 09, 2012

How to create Web Deploy packages in Visual Studio 2012

When building Visual Studio 2012 we made an effort to reduce the amount of menu options which are shown on toolbars as well as context menus. If you have used any of the pre-release versions of VS 2012 then you might have noticed that the Build Deployment Package and Package/Publish Settings context menu options are gone from Web Application Project.

In VS2010 when creating a Web Deploy package the publish dialog was not used because there were no relevant settings. Now that we have enabled features like integration to enable Entity Framework Code First migrations, incremental database updates (coming in the final release), connection string updates, etc. we needed to find a way that you could leverage these great features when creating Web Deploy packages. The solution that we decided to go with was to have first class support for creating packages directly from the publish dialog.

After that, we felt that it would be better to have a single way to create a package than two different ways with pros and cons. And we would benefit by being able to simplify our context menu. To create a package for your Web Application Project in VS 2012 (or in VS 2010 if you have the Azure SDK 1.7+) you can follow the steps below.

  1. Right click on your project and select Publish
  2. Create a new profile for your Web Deploy package
  3. Specify the path where the package should go (must end with a .zip)
  4. Click Publish

After the initial create for the profile, creating additional packages is even easier. You just right click on your project select Publish and then click the Publish button.

The reason why we remove the context menu for the Package/Publish Settings is that most of the package and publish related settings are on the publish dialog. The existing settings from VS 2010 are still available on the property pages if you need to get to them.

One other change which we made was to hide the One Click toolbar. This is a toolbar which can be used to publish your web project in one click after the web publish profile has been created. After looking at the number of times that the button was used we determined that it did not meet the bar to be shown by default. Don’t worry if that was your favorite button (I know it was mine), you can bring it back quickly. The easiest way to turn it on is to press CTRL + Q (to bring up the quick launch), type in ‘one click’ and then click on the single result. That should show the One Click toolbar.

FYI if you want to learn more we have a great walk through on publishing a web project with an EF Code First model at Deploying an ASP.NET Web Application to a Windows Azure Web Site and SQL Database.

If you have any questions feel free to email me at sayedha@microsoft.com.

Sayed Ibrahim Hashimi | @SayedIHashimi

Visual Studio | Visual Studio 11 | Visual Studio 2012 | Web Publishing Pipeline Thursday, August 09, 2012 10:39:46 PM (GMT Daylight Time, UTC+01:00)  #     | 
Monday, August 06, 2012

Plans regarding Website projects and Web Deployment Projects

The release of Visual Studio 2012 is right around the corner. If you’ve been following our blog/twitter then you may know that many of the Web related components of Visual Studio are now“out-of-band”, meaning that we can update them independently of Visual Studio itself. Because of this we are planning to ship updates a few times a year. We are currently in the process for planning our first update for VS 2012 and we wanted to share some of the items that we are planning to work on. We would like to get your feedback on these ideas to ensure that we are doing the right things. If you have comments please do let us know. FYI the updates that I describe below will be made available for both VS 2012 as well as VS 2010.

Website Project Updates

When you use Visual Studio there are two types of web projects you can create: a Web Application Project (WAP, this includes MVC) and a Website Project (WSP). In VS 2012 we did a lot of work to update the publish experience for WAPs. When we set out for these updates we planned to take these enhancements and make the available to Website projects as well. As we progressed it became evident that we did not have the resources to implement the features for both WAP and WSP in time for VS 2012. So we focused on creating the right experience for WAPs and we also added an extensibility point into WSP which will allow us to replace the existing publish dialog. The reason why we focused on WAP first was because WAPs already have support for Web Deploy (MSDeploy) based publishing as well as a formal build/publish process captured in MSBuild. WSP does not have either of these so it would have taken more time to start with WSP.

When VS 2012 is released the publish experience for WSP will be the same which was available in VS2010 but we will have a release a few months after the release which will bring all the publish goodness to Website Projects! This will include all the features which are available to Web Application Projects including; the ability to publish using.

Since both project systems will be using the exact same code whenever we add a feature it will immediately be available to both. In today’s world they are two entirely different code bases (the WSP publishing experience is currently native code where as the WAP publish dialog is managed). This will allow us to maintain a consistent publish experience and also enable us to deliver features more quickly.

Since WSP does not have a “build” process you might be wondering how we are planning to hook this all together since our entire publish pipeline is captured in MSBuild. Here is a rough idea of what we are currently thinking. After you use the new publish dialog to create a publish profile for the first time we will do a number of things:

  1. Create a new folder to contain needed artifacts
    1. The folder may be in the root of the Website project, but we are considering placing it somewhere outside as well
    2. The name of this folder is not yet finalized, but it could be something like _Publish or _Project
  2. Create an MSBuild file for the Website in that folder, this will capture information like; included files, references, properties to control publish process
    1. The primary purpose of dropping this file is to facilitate command line publish scenarios, without this we could publish from Visual Studio but not from the command line
    2. When you make changes in Visual Studio like adding a reference we will automatically keep this file up to date
  3. Create the publish profile in that folder (this is a .pubxml file)
  4. When the site is being published the working directory (i.e. obj\ folder) will be outside the root of the website, most likely in the same folder which contains the WSP root

After we have those things in place for the most part the publish dialog will be able to treat both projects in the same way. These files will by default be stored in version control and can be shared with other team members. Sensitive information such as the MSDeploy password will be stored in another file and encrypted per-user/per-machine in a .user file which is not checked in.

Web Deployment Projects

A few months after Visual Studio 2005 shipped we released the first version of Web Deployment Projects, and we updated WDP for both VS 2008 and VS 2010 and released them a few months after each of those releases as well. WDPs are used for a few different scenarios including the following.

  1. Publishing a Website project using MSDeploy
  2. Command line publish support
  3. Customizing the publish process for a WAP
  4. Running ASP.NET precompile/merge for a WSP or WAP

When we looked at the scenarios that WDPs are typically used for and then compared that to features which we have for WAP and WSP (after the updates mentioned above) we noticed that most (if not all) scenarios where WDP is used can be covered without requiring a WDP. Our new publish experience already has first class support for MSDeploy, command line publishing, and for extensibility so that covers #1-#3. Regarding #4 for WAPs we have already added ASP.NET precompile/merge as a publish option (you can find it in the Package/Publish Web tab). Since WSP does not have any property pages we are likely to move that option to being on the publish dialog itself or we will expose those options in another way for WSP. We have not yet settled on that design.

It may not seem like it but updating WDP to support VS2012 is a significant effort. Knowing that, and that most of the scenarios where WDP are used can now be transitioned to the new experience, we have decided to invest in giving WSP projects first class publishing support instead of updating VS 2012 to support WDP. We think that this is the right decision because we are unifying the publish experience for both project systems and it allows us to create a deeper set of features going forward instead of investing in different things and wasting effort. If you have a scenario that you think we have missed please do not hesitate to reach out to me by email at sayedha@microsoft.com and let me know.


Sayed Ibrahim Hashimi | @SayedIHashimi

asp.net | MSDeploy | Visual Studio | Visual Studio 2010 | Visual Studio 2012 | Web Publishing Pipeline Monday, August 06, 2012 10:16:47 PM (GMT Daylight Time, UTC+01:00)  #     | 
Thursday, July 12, 2012

Visual Studio 2012 RC Web Tooling Extensions update

If you have been following our blog and twitter accounts then you most likely have heard that some of the Web components of Visual Studio 2012 are now “Out of Band”. What that means is that we can update those components separately from Visual Studio itself. Because of this we will be posting updates on a regular basis. Today marks the first of these updates, and its targeting Visual Studio 2012 RC. In this post I will describe how to get the update and then what the update contains.

How to get the update

If you already have Visual Studio 2012 RC installed when you launch VS you will see a notification in the task tray like the image below.

image

Note: VS checks for updates once a day so if you don’t see this notification today its likely that the check has already been performed

After you click on the notification you will be brought to the Extension and Updates dialog. To get the web updates you should go to the Visual Studio Gallery tab and then click Update on the Web Tooling Extensions item.

image

After installing the update you will need to restart VS.

What is contained in the update

The goals that we had when creating this update mainly consisted of.

  1. Fix key customer reported bugs which we didn’t have time to complete for the RC release
  2. Test out the update mechanism publicly

Since the intent of this release was not to light up any new features you won’t find any new functionality, but you may discover that an issue you are running into has been fixed. Most of the bugs which we fixed were filed by customer on Microsoft Connect, though some came in through other channels. For the Connect bugs you will see a link to the bug details, for Connect bugs filed as private there will not be a link. Below you will find a list of the fixed bugs as well as a brief description of the bug.

Web publishing raises an exception due to <connectionStrings> content

If the root web.config contained elements under <connectionStrings> that did not have a name attribute an exception was raised.

Reported Bugs

 

Web publish to the File system doesn’t always include all files

When publishing a web project to the file system there are cases where files are not getting updated on publish.

Reported Bugs

Web publish doesn’t handle read-only profiles correctly

In certain cases if a web publish profile is read-only it can cause VS to crash.

Reported Bugs

Web publish doesn’t include all files if there is no project configuration matching the solution configuration

The drop down for Configuration on the publish dialog was mapped to Solution Configurations instead of Project Configurations. Because of this if a Solution Configuration was selected which did not have a corresponding Project Configuration the files would not be published. We have updated the dialog to show the correct value.

Reported Bugs

Wrap up

We are really excited to be able to publish updates on a more regular basis and we hope that you will find that helpful as well. In case you guys were wondering if we listen to feedback/Connect bugs I hope that this post helps to show that we are listening to feedback and working to resolve bugs which get filed on Connect. Keep filing the bugs so that we can make our product even better. If you have already filed bugs on Connect then Thank You!


Sayed Ibrahim Hashimi | @SayedIHashimi

Visual Studio | Visual Studio 2012 | Web Development | Web Publishing Pipeline Thursday, July 12, 2012 8:01:03 PM (GMT Daylight Time, UTC+01:00)  #     | 
Friday, June 15, 2012

Downloading the Visual Studio Web Publish Updates

I have written a few posts recently describing out updated web publish experience. These new experience is available for both Visual Studio 2010 as well as Visual Studio 2012 RC. You can use the links below to download these updates in the Azure SDK download. Below are links for both versions.

The Web Publish experience is chained into VS 2012 RC so if you have installed VS 2012 RC with the Web features then you already have these features.

Thanks,
Sayed Ibrahim Hashimi @SayedIHashimi

asp.net | Deployment | Visual Studio | Visual Studio 11 | Visual Studio 2010 | web | Web Deployment Tool | Web Publishing Pipeline Friday, June 15, 2012 8:30:40 PM (GMT Daylight Time, UTC+01:00)  #     | 

Visual Studio 2010 Web Publish Updates

Last week we rolled out some updates for our Visual Studio 2010 Web Publishing Experience. This post will give you an overview of the new features which we released. In the coming weeks there will be more posts getting into more details regarding individual features.

You can get these updates in the Windows Azure SDK for Visual Studio 2010. When you download that package there you will also get the latest tools for Azure development.

The new high level features include the following.

Overview

When you right click on your Web Application Project (WAP) you will now see the new publish dialog.

image

On this tab you can import a .publishSettngs file, which many web hosts provide, and you can also manage your publish profiles. If you are hosting your site on Windows Azure Web Sites then you can download the publish profile on the dashboard of the site using the Download publish profile link. After you import this publish profile you will be brought to the Connection tab automatically.

image

On this tab you can see all the server configuration values which are needed for your client machine to connect to the server. Typically you don’t have to worry about the details of these values. Next you’ll go to the Settings tab.

image

On the Settings tab you can set the build configuration which should be used for the publish process, the default value here is Release. There is also a checkbox to enable you to delete any files on the server which do not exist in the project.

Below that checkbox you will see a section for databases. The sample project shown has an Entity Framework Code First model, named ContactsContext, and it uses Code First Migrations to manage the database schema. If you have any non-EF Code First connection strings in web.config then those databases will show up as well but the support for incrementally publishing the schema for those has not yet been finalized. We are currently working on that. You can visit my previous blog entry for more info on that.

If you imported a .publishSettings file with a connection string then that connection string would automatically be inserted in the textbox/dropdown for the connection string. If you did not then you can use the … button to create a connection string with the Connection String Builder dialog or you can simply type/paste in a connection string. For the EF Code First contexts you will see the Execute Code Frist Migrations checkbox. When you check this when your site is published the web.config will be transformed to enable the Code First migrations to be executed the first time that the context is accessed. Now you can move to the Preview tab.

When you first come to the Preview tab you will see a Start Preview button. Once you click this button you will see the file operations which would be performed once you publish. Since this site has never been published all the file operations are Add, as you can see in the image below. The other Action values include; Update and Delete.

image

Once you are ready to publish you can click the Publish button. You can monitor the progress of the publish process using the Output Window. If your publish profile had a value for the Destination URL then the site will automatically be opened in the default browser after the publish has successfully completed.

Publish Profiles

One of the other changes in the publish experience is that publish profiles are now stored as a part of your project. They are stored under the folder Properties\PublishProfiles (for VB projects its My Project\PublishProfiles) and the extension is .pubxml. You can see this in the image below.

image

These .pubxml files are MSBuild files and you can modify these files in order to customize the publish process. If you do not want the publish profile to be checked into version control you can simply exclude it from the project. The publish dialog will look at the files in the PublishProfiles folder so you will still be able to publish using that profile. You can also leverage these publish profiles to simply publishing from the command line. For example you can use the following syntax to publish from the command line.

msbuild.exe WebApplication2.csproj /p:DeployOnBuild=true;PublishProfile="pubdemo - Web Deploy";Password={INSERT-PASSWORD}

 

Resources

If you have any questions please feel free to directly reach out to me at sayedha(at){MicrosoftDOTCom}.

Sayed Ibrahim Hashimi @SayedIHashimi

asp.net | Microsoft | MSDeploy | Visual Studio 2010 | web | Web Deployment Tool | Web Publishing Pipeline Friday, June 15, 2012 8:07:30 PM (GMT Daylight Time, UTC+01:00)  #     | 
Thursday, June 07, 2012

ASP.NET providers and SQL Azure

We have two sets of ASP.NET providers which currently exist; the ASP.NET SQL providers, and the ASP.NET Universal Providers. In VS 2010 the SQL providers were in only providers used for our project templates. In VS 2012 we have switched to using the Universal Providers. One of the drawbacks of the SQL providers is that it leverages DB objects of SQL server which are not available in SQL Azure.

In our updated web publish experience we have an Update Database checkbox which can be used to incrementally publish the database to the destination database. In this case if the source connection string is used by the ASP.NET SQL providers and you are publishing to SQL Azure then you will see the following message on the dialog.

SNAGHTML48cbb8

Note: you may see the Update Database checkbox disabled, please visit http://sedodream.com/2012/06/07/VSPublishDialogUpdateDatabaseDialogDisabled.aspx for more info on why.

The publish dialog is letting you know that the SQL providers are not compatible with SQL Azure and helps you convert to using the Universal Providers. After you install the Universal Providers the web.config entry will be commented out and new entries will be inserted for the Universal Providers. Your existing database will not be impacted, we’ll create a new connection string pointing to a new database. If you had any data in the SQL Providers database you will have to re-create those objects in the new database.

If you have any questions please feel free to directly reach out to me at sayedha(at){MicrosoftDOTCom}.

Sayed Ibrahim Hashimi @SayedIHashimi

Visual Studio | Visual Studio 11 | Visual Studio 2010 | web | Web Publishing Pipeline Thursday, June 07, 2012 11:41:46 PM (GMT Daylight Time, UTC+01:00)  #     | 

Database settings in the VS Publish dialog

In Visual Studio 2010 we introduced a database publishing experience in the Package/Publish SQL (PP/SQL) properties page. This support relies on generating create scripts from the source database and then executing those scripts when you publish your web application. For more details regarding this feature take a look at Package/Publish SQL Tab, Project Properties. One of the negative aspects of the implementation that we have there is that the DB schema publishing is not incremental, it always executes create scripts. So in many cases you publish your app for the first time and things are great, but the next time you try to publish you receive errors because it tries to create DB objects which already exist.

In our improved publish dialog we have a new database publishing experience which is incremental, so you don’t run into the same issues when re-deploying your database. All of the configuration for this is within the publish dialog itself. If you have a project which already has DB publish settings on the Package/Publish SQL tab (under any build configuration/platform) then you will see the message shown in the image below.

SNAGHTML2bb135

The new incremental DB schema publish mechanism is not compatibly with the feature set on the PP/SQL tab. For each web publish profile you can either use the settings on the PP/SQL tab or you can opt-in to the new experience on the publish dialog. We implemented it this way in order to introduce the new feature set and to ensure that existing publish scenarios are not broken.

FYI there is a related post on why the Update database checkbox is disabled in the publish dialog which you may be interested in.

If you have any questions please feel free to directly reach out to me at sayedha(at){MicrosoftDOTCom}.

Sayed Ibrahim Hashimi @SayedIHashimi

Microsoft | Visual Studio | Visual Studio 2010 | Web Publishing Pipeline Thursday, June 07, 2012 11:09:13 PM (GMT Daylight Time, UTC+01:00)  #     | 

VS Publish dialog Update Database dialog disabled

If you have tried out our new Web Publish experience in Visual Studio you may have noticed that the Update Database checkbox is disabled. See the image below.

image

The intended behavior of this checkbox is to enable you to incrementally publish your database schema from the source (the connection string in web.config) to the destination (whatever connection string is in the text box). The difference between an incremental publish and a typical publish is that for incremental publishes only changes are transferred from source to destination. With a full publish the first time that you publish your DB schema everything is created, and the next time that you try to publish you will receive an error because it tries to re-create existing DB objects.

The functionality of the Update database checkbox leverages an MSDeploy provider. We were hoping to complete that provider and give it to hosters in time for the release but we were unable to do so. We are working on completing the provider and partnering with hosters to install these in time for the launch of Visual Studio 2012 RTM.

In the mean time if you need to publish your DB schema you can use the Package/Publish SQL tab (caution: the DB publishing here is not incremental). If you are going to use the PP/Sql tab to publish to SQL Azure then there are some special consideraions that you will need to take. You can learn more about those by visiting http://msdn.microsoft.com/en-us/library/dd465343.aspx and searching for “Azure” on that page.

If you have any questions please feel free to directly reach out to me at sayedha(at){MicrosoftDOTCom}.


Thanks,
Sayed Ibrahim Hashimi @SayedIHashimi

Visual Studio | Visual Studio 11 | Visual Studio 2010 | web | Web Development | Web Publishing Pipeline Thursday, June 07, 2012 10:44:26 PM (GMT Daylight Time, UTC+01:00)  #     | 
Sunday, May 13, 2012

VS Web Publish: How to parameterize connection strings outside of web.config

If you have used the Visual Studio web publish in either VS 2010 or VS 11 to create Web Deploy packages then you probably know that we parameterize connection strings in web.config automatically. In case you are not familiar with Web Deploy parameters, they are a way to declare that you want to easily be able to update a value of something when publishing the package later on. Connection strings are good examples of something which typically needs to be updated during publish.

As I previously stated if you create a Web Deploy package in Visual Studio we will automatically create Web Deploy parameters for all your connection strings in web.config. Earlier today I saw a question on StackOverflow asking how to parameterize connection strings in non-web.config files (question actually asked something else, but I think this is what he’s really wanting). I created a sample showing how to do this. Below is what the connectionStrings element looks like in web.config.

<connectionStrings configSource="connectionStrings.config" />

And here is connectionStrings.config

<?xml version="1.0" encoding="utf-8" ?>
<connectionStrings>
  <clear/>
  <add name="ApplicationServices"
           connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true"
           providerName="System.Data.SqlClient" />
  <add name="OtherConnectionString"
       connectionString="data source=.\SQLExpress;Integrated Security=SSPI;Initial Catalog=foo"
       providerName="System.Data.SqlClient"/>
</connectionStrings>

In order to parameterize these connection strings you will have to extend the Web Publish Pipeline. To do that create a file named {project-name}.wpp.targets in the root of the project in which you are working (for VS 11 projects you can place all this directly inside of the .pubxml files). This will be an MSBuild file which will get imported into the build/publish process. Below is the file which needs to be created.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <!-- Here we need to declare MSDeploy parameters for connection strings in connectionStrings.config -->
    <MsDeployDeclareParameters Include="ApplicationServices-ConnectionString" >
      <Kind>XmlFile</Kind>
      <Scope>connectionStrings.config$</Scope>
      <Match>/connectionStrings/add[@name='ApplicationServices']/@connectionString</Match>
      <Description>Connection string for ApplicationServices</Description>
      <DefaultValue>data source=(localhost);Initial Catalog=AppServices</DefaultValue>
      <Tags>SqlConnectionString</Tags>
    </MsDeployDeclareParameters>

    <MsDeployDeclareParameters Include="OtherConnectionString-ConnectionString" >
      <Kind>XmlFile</Kind>
      <Scope>connectionStrings.config$</Scope>
      <Match>/connectionStrings/add[@name='OtherConnectionString']/@connectionString</Match>
      <Description>Connection string for OtherConnectionString</Description>
      <DefaultValue>data source=(localhost);Initial Catalog=OtherDb</DefaultValue>
      <Tags>SqlConnectionString</Tags>
    </MsDeployDeclareParameters>
  </ItemGroup>

</Project>

Here you can see that I am creating values for MSDeployDeclareParameters. When you package/publish this item list is used to create the MSDeploy parameters. Below is an explanation of the metadata values each contain.

After you create this file you will need to close/re-open VS (it caches imported .targets files). Then you can create a web deploy package. When you do so these new parameters will be declared. In my case I then imported this in the IIS manager and here is the dialog which shows up for the parameters.

SNAGHTML94cce08

As you can see the Application Path parameter is shown there as well as my custom connection string values. When I update the values in the text box and opened connectionStrings.config on my web server they were the values I entered in the dialog box.

FYI I have uploaded this sample to my github account at ParameterizeConStringConfig.

Sayed Ibrahim Hashimi @SayedIHashimi

msbuild | MSBuild 4.0 | MSDeploy | Visual Studio | Web Deployment Tool | Web Publishing Pipeline Sunday, May 13, 2012 10:18:03 PM (GMT Daylight Time, UTC+01:00)  #     | 
Wednesday, March 14, 2012

Package web updated and video below

A couple months ago I blogged about a Package-Web which is a NuGet package that extends the web packaging process in Visual Studio to enable you to create a single package which can be published to multiple environments (it captures all of your web.config transforms and has the ability to transform on non-dev machines). Since that release I have updated the project and tonight I created a video which shows the features a bit you can check it out on Youtube. It’s embedded below.

You can install this via NuGet, the package name is PackageWeb.

image

Package-Web is an open source project and you can find it on my github account at https://github.com/sayedihashimi/package-web.

Thanks,
Sayed Ibrahim Hashimi @SayedIHashimi

msbuild | MSDeploy | Visual Studio | web | Web Deployment Tool | Web Development | Web Publishing Pipeline Wednesday, March 14, 2012 6:08:57 AM (GMT Standard Time, UTC+00:00)  #     | 
Tuesday, February 14, 2012

How to update a single file using Web Deploy (MSDeploy)

The other day I saw a question posted on StackOverflow (link to question below in resources section) asking if it was possible to update web.config using MSDeploy. I actually used a technique where I updated a single file in one of my previous posts at How to take your web app offline during publishing but it wasn’t called out too much. In any case I’ll show you how you can update a single file (in this case web.config) using MSDeploy.

You can use the contentPath provider to facilitate updating a single file. Using contentPath you can sync either a single file or an entire folder. You can also use IIS app paths to resolve where the file/folder resides. For example if I have a web.config file in a local folder named “C:\Data\Personal\My Repo\sayed-samples\UpdateWebConfig” and I want to update my IIS site UpdateWebCfg running in the Default Web Site on my folder I would use the command shown below.

%msdeploy% -verb:sync -source:contentPath="C:\Data\Personal\My Repo\sayed-samples\UpdateWebConfig\web.config" -dest:contentPath="Default Web Site/UpdateWebCfg/web.config"

From the command above you can see that I set the source content path to the local file and the dest content path using the IIS path {SiteName}/{AppName}/{file-path}. In this case I am updating a site running in IIS on my local machine. In order to update one that is running on a remote machine you will have to add ComputerName and possibly some other values to the –dest argument.

You can view the latest sources for this sample at my github repo, link is below.

 

Hope that helps!

Sayed Ibrahim Hashimi – @SayedIHashimi

Resources:

IIS | MSDeploy | web | Web Publishing Pipeline Tuesday, February 14, 2012 7:17:30 PM (GMT Standard Time, UTC+00:00)  #     | 
Sunday, January 08, 2012

How to take your web app offline during publishing

I received a customer email asking how they can take their web application/site offline for the entire duration that a publish is happening from Visual Studio. An easy way to take your site offline is to drop an app_offline.htm file in the sites root directory. For more info on that you can read ScottGu’s post, link in below in resources section. Unfortunately Web Deploy itself doesn’t support this Sad smile. If you want Web Deploy (aka MSDeploy) to natively support this feature please vote on it at http://aspnet.uservoice.com/forums/41199-general/suggestions/2499911-take-my-site-app-offline-during-publishing.

Since Web Deploy doesn’t support this it’s going to be a bit more difficult and it requires us to perform the following steps:

  1. Publish app_offline.htm
  2. Publish the app, and ensure that app_offline.htm is contained inside the payload being published
  3. Delete app_offline.htm

#1 will take the app offline before the publish process  begins.
#2 will ensure that when we publish that app_offline.htm is not deleted (and therefore keep the app offline)
#3 will delete the app_offline.htm and bring the site back online

Now that we know what needs to be done let’s look at the implementation. First for the easy part. Create a file in your Web Application Project (WAP) named app_offline-template.htm. This will be the file which will end up being the app_offline.htm file on your target server. If you leave it blank your users will get a generic message stating that the app is offline, but it would be better for you to place static HTML (no ASP.NET markup) inside of that file letting users know that the site will come back up and whatever other info you think is relevant to your users. When you add this file you should change the Build Action to None in the Properties grid. This will make sure that this file itself is not published/packaged. Since the file ends in .htm it will by default be published. See the image below.

image

Now for the hard part. For Web Application Projects we have a hook into the publish/package process which we refer to as “wpp.targets”. If you want to extend your publish/package process you can create a file named {ProjectName}.wpp.targets in the same folder as the project file itself. Here is the file which I created you can copy and paste the content into your wpp.targets file. I will explain the significant parts but wanted to post the entire file for your convince. Note: you can grab my latest version of this file from my github repo, the link is in the resource section below.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="InitalizeAppOffline">
    <!-- 
    This property needs to be declared inside of target because this is imported before
    the MSDeployPath property is defined as well as others -->
    <PropertyGroup>
      <MSDeployExe Condition=" '$(MSDeployExe)'=='' ">$(MSDeployPath)msdeploy.exe</MSDeployExe>
    </PropertyGroup>    
  </Target>

  <PropertyGroup>
    <PublishAppOfflineToDest>
      InitalizeAppOffline;
    </PublishAppOfflineToDest>
  </PropertyGroup>

  <!--
    %msdeploy% 
      -verb:sync 
      -source:contentPath="C:\path\to\app_offline-template.htm" 
      -dest:contentPath="Default Web Site/AppOfflineDemo/app_offline.htm"
  -->

  <!--***********************************************************************
  Make sure app_offline-template.htm gets published as app_offline.htm
  ***************************************************************************-->
  <Target Name="PublishAppOfflineToDest" 
          BeforeTargets="MSDeployPublish" 
          DependsOnTargets="$(PublishAppOfflineToDest)">
    <ItemGroup>
      <_AoPubAppOfflineSourceProviderSetting Include="contentPath">
        <Path>$(MSBuildProjectDirectory)\app_offline-template.htm</Path>
        <EncryptPassword>$(DeployEncryptKey)</EncryptPassword>
        <WebServerAppHostConfigDirectory>$(_MSDeploySourceWebServerAppHostConfigDirectory)</WebServerAppHostConfigDirectory>
        <WebServerManifest>$(_MSDeploySourceWebServerManifest)</WebServerManifest>
        <WebServerDirectory>$(_MSDeploySourceWebServerDirectory)</WebServerDirectory>
      </_AoPubAppOfflineSourceProviderSetting>

      <_AoPubAppOfflineDestProviderSetting Include="contentPath">
        <Path>"$(DeployIisAppPath)/app_offline.htm"</Path>
        <ComputerName>$(_PublishMsDeployServiceUrl)</ComputerName>
        <UserName>$(UserName)</UserName>
        <Password>$(Password)</Password>
        <EncryptPassword>$(DeployEncryptKey)</EncryptPassword>
        <IncludeAcls>False</IncludeAcls>
        <AuthType>$(AuthType)</AuthType>
        <WebServerAppHostConfigDirectory>$(_MSDeployDestinationWebServerAppHostConfigDirectory)</WebServerAppHostConfigDirectory>
        <WebServerManifest>$(_MSDeployDestinationWebServerManifest)</WebServerManifest>
        <WebServerDirectory>$(_MSDeployDestinationWebServerDirectory)</WebServerDirectory>
      </_AoPubAppOfflineDestProviderSetting>
    </ItemGroup>

    <MSdeploy
          MSDeployVersionsToTry="$(_MSDeployVersionsToTry)"
          Verb="sync"
          Source="@(_AoPubAppOfflineSourceProviderSetting)"
          Destination="@(_AoPubAppOfflineDestProviderSetting)"
          EnableRule="DoNotDeleteRule"
          AllowUntrusted="$(AllowUntrustedCertificate)"
          RetryAttempts="$(RetryAttemptsForDeployment)"
          SimpleSetParameterItems="@(_AoArchivePublishSetParam)"
          ExePath="$(MSDeployPath)" />
  </Target>

  <!--***********************************************************************
  Make sure app_offline-template.htm gets published as app_offline.htm
  ***************************************************************************-->
  <!-- We need to create a replace rule for app_offline-template.htm->app_offline.htm for when the app get's published -->
  <ItemGroup>
    <!-- Make sure not to include this file if a package is being created, so condition this on publishing -->
    <FilesForPackagingFromProject Include="app_offline-template.htm" Condition=" '$(DeployTarget)'=='MSDeployPublish' ">
      <DestinationRelativePath>app_offline.htm</DestinationRelativePath>
    </FilesForPackagingFromProject>

    <!-- This will prevent app_offline-template.htm from being published -->
    <MsDeploySkipRules Include="SkipAppOfflineTemplate">
      <ObjectName>filePath</ObjectName>
      <AbsolutePath>app_offline-template.htm</AbsolutePath>
    </MsDeploySkipRules>
  </ItemGroup>

  <!--***********************************************************************
  When publish is completed we need to delete the app_offline.htm
  ***************************************************************************-->
  <Target Name="DeleteAppOffline" AfterTargets="MSDeployPublish">
    <!--
    %msdeploy% 
      -verb:delete 
      -dest:contentPath="{IIS-Path}/app_offline.htm",computerName="...",username="...",password="..."
    -->
    <Message Text="************************************************************************" />
    <Message Text="Calling MSDeploy to delete the app_offline.htm file" Importance="high" />
    <Message Text="************************************************************************" />

    <ItemGroup>
      <_AoDeleteAppOfflineDestProviderSetting Include="contentPath">
        <Path>$(DeployIisAppPath)/app_offline.htm</Path>
        <ComputerName>$(_PublishMsDeployServiceUrl)</ComputerName>
        <UserName>$(UserName)</UserName>
        <Password>$(Password)</Password>
        <EncryptPassword>$(DeployEncryptKey)</EncryptPassword>
        <AuthType>$(AuthType)</AuthType>
        <WebServerAppHostConfigDirectory>$(_MSDeployDestinationWebServerAppHostConfigDirectory)</WebServerAppHostConfigDirectory>
        <WebServerManifest>$(_MSDeployDestinationWebServerManifest)</WebServerManifest>
        <WebServerDirectory>$(_MSDeployDestinationWebServerDirectory)</WebServerDirectory>
      </_AoDeleteAppOfflineDestProviderSetting>
    </ItemGroup>
    
    <!-- 
    We cannot use the MSDeploy/VSMSDeploy tasks for delete so we have to call msdeploy.exe directly.
    When they support delete we can just pass in @(_AoDeleteAppOfflineDestProviderSetting) as the dest
    -->
    <PropertyGroup>
      <_Cmd>"$(MSDeployExe)" -verb:delete -dest:contentPath="%(_AoDeleteAppOfflineDestProviderSetting.Path)"</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.ComputerName)' != '' ">$(_Cmd),computerName="%(_AoDeleteAppOfflineDestProviderSetting.ComputerName)"</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.UserName)' != '' ">$(_Cmd),username="%(_AoDeleteAppOfflineDestProviderSetting.UserName)"</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.Password)' != ''">$(_Cmd),password=$(Password)</_Cmd>
      <_Cmd Condition=" '%(_AoDeleteAppOfflineDestProviderSetting.AuthType)' != ''">$(_Cmd),authType="%(_AoDeleteAppOfflineDestProviderSetting.AuthType)"</_Cmd>
    </PropertyGroup>

    <Exec Command="$(_Cmd)"/>
  </Target>  
</Project>

#1 Publish app_offline.htm

The implementation for #1 is contained inside the target PublishAppOfflineToDest. The msdeploy.exe command that we need to get executed is.

msdeploy.exe
    -source:contentPath='C:\Data\Personal\My Repo\sayed-samples\AppOfflineDemo01\AppOfflineDemo01\app_offline-template.htm'
    -dest:contentPath='"Default Web Site/AppOfflineDemo/app_offline.htm"',UserName='sayedha',Password='password-here',ComputerName='computername-here',IncludeAcls='False',AuthType='NTLM' -verb:sync -enableRule:DoNotDeleteRule

In order to do this I will leverage the MSDeploy task. Inside of the PublishAppOfflineToDest target you can see how this is accomplished by creating an item for both the source and destination.

#2 Publish the app, and ensure that app_offline.htm is contained inside the payload being published

This part is accomplished by the fragment

  <!--***********************************************************************
  Make sure app_offline-template.htm gets published as app_offline.htm
  ***************************************************************************-->
  <!-- We need to create a replace rule for app_offline-template.htm->app_offline.htm for when the app get's published -->
  <ItemGroup>
    <!-- Make sure not to include this file if a package is being created, so condition this on publishing -->
    <FilesForPackagingFromProject Include="app_offline-template.htm" Condition=" '$(DeployTarget)'=='MSDeployPublish' ">
      <DestinationRelativePath>app_offline.htm</DestinationRelativePath>
    </FilesForPackagingFromProject>

    <!-- This will prevent app_offline-template.htm from being published -->
    <MsDeploySkipRules Include="SkipAppOfflineTemplate">
      <ObjectName>filePath</ObjectName>
      <AbsolutePath>app_offline-template.htm</AbsolutePath>
    </MsDeploySkipRules>
  </ItemGroup>

The item value for FilesForPackagingFromProject here will convert your app_offline-template.htm to app_offline.htm in the folder from where the publish will be processed. Also there is a condition on it so that it only happens during publish and not packaging. We do not want app_offline-template.htm to be in the package (but it’s not the end of the world if it does either).

The element for MsDeploySkiprules will make sure that app_offline-template.htm itself doesn’t get published. This may not be required but it shouldn’t hurt.

#3 Delete app_offline.htm

Now that our app is published we need to delete the app_offline.htm file from the dest web app. The msdeploy.exe command would be:

%msdeploy%
      -verb:delete
      -dest:contentPath="{IIS-Path}/app_offline.htm",computerName="...",username="...",password="..."

This is implemented inside of the DeleteAppOffline target. This target will automatically get executed after the publish because I have included the attribute AfterTargets=”MSDeployPublish”. In that target you can see that I am building up the msdeploy.exe command directly, it looks like the MSDeploy task doesn’t support the delete verb.

If you do try this out please let me know if you run into any issues. I am thinking to create a Nuget package from this so that you can just install that package. That would take a bit of work so please let me know if you are interested in that.

Resources

  1. The latest version of my AppOffline wpp.targets file.
  2. ScottGu’s blog on app_offline.htm

 

Sayed Ibrahim Hashimi @SayedIHashimi

IIS | Microsoft | msbuild | MSDeploy | Visual Studio 2010 | web | Web Deployment Tool | Web Publishing Pipeline Sunday, January 08, 2012 8:44:39 PM (GMT Standard Time, UTC+00:00)  #     | 
Tuesday, November 08, 2011

Using a Web Deploy package to deploy to IIS on the dev box and to a third party host

Note: I’d like to thank Tom Dykstra for helping me put this together

Overview

In this tutorial you'll see how to use a web deployment package package to deploy an application. A deployment package is a .zip file that includes all of the content and metadata that's required to deploy an application.

Deployment packages are often used in enterprise environments. This is because a developer or a continuous integration server can create the package without needing to know things like passwords that are stored in Web.config files. Only the server administrator who actually installs the package needs to know those passwords, and that person can enter the details at installation time.

In a smaller organization that doesn't have separate people for these roles, there's less need for deployment packages. But you can also use deployment packages as a way to back up and restore the state of an application. After you use a deployment package to deploy, you can save the package,. Then if a subsequent deployment has a problem, you can quickly and easily restore the application state to the earlier state by reinstalling the earlier package. (This scenario is more complicated if database changes are involved, however.)

This tutorial shows how to use Visual Studio to create a package and IIS Manager to install it. For information about how to create and install packages using the command line, see ASP.NET Deployment Content Map on the MSDN web site.

To keep things relatively simple, this example assumes you have already deployed the application and its databases, and you only need to deploy a code update. You have made the code update, and you are ready to deploy it first to your test environment (IIS on your local computer) and then to your hosting provider. You have a Test build configuration that you use for the test environment and you use the Release build configuration for the production environment. In the example, the name of the Visual Studio project is ContosoUniversity, and instructions for its initial deployment can be found in a series of tutorials that will be published in December on the ASP.NET web site.

The hosting provider shown, Cytanium.com, is one of many that are available, and its use here does not constitute an endorsement or recommendation.

Note The following example uses separate packages for the test and production environments, but you can also create a single deployment package that can be used for both environments. This would require that you use Web Deploy parameters instead of Web.config transformations for Web.config file changes that depend on deployment destination. For information about how to use Web Deploy parameters, see How to: Use Parameters to Configure Deployment Settings When a Package is Installed.

Configuring the Deployment Package

In this section, you'll configure settings for the deployment package. Some of these settings are the same ones that you set also for one-click publish, others are only for deployment packages.

Open the Package/Publish Web tab of the Project Properties window and select the Test build configuration.

For this deployment you aren't making any database changes, so clear Include all databases configured in Package/Publish SQL tab. Make sure Exclude files from the App_Data folder is selected.

Review the settings in the section labeled Web Deployment Package Settings:

The Package/Publish Web tab now looks like this:

Package_Publish_Web_tab_Test

You also need to configure settings for deploying to the production environment. Select the Release build configuration to do that.

Change IIS Web site/application name to use on the destination server to a string that will serve as a reminder of what you need to do later when this value is displayed in the IIS Manager UI: "[clear this field]". The text box on this page won't stay cleared even if you clear it, so entering this note to yourself will remind you to clear this value later when you deploy. When you deploy to your hosting provider, you will connect to a site, not to a server, and in this case you want to deploy to the root of the site.

Package_Publish_Web_tab_Release

Creating a Deployment Package for the Test Environment

To create a deployment package, first make sure you've selected the right build configuration. In the Solution Configurations drop-down box, select Test.

Solution_Configurations_dropdown

In Solution Explorer, right-click the project that you want to build the package for and then select Build Deployment Package.

The Output window reports successful a build and publish (package creation) and tells you where the package was created.

Output_Window_package_creation

Installing the Deployment Package in the Test Environment

The next step is to install the deployment package in IIS on your development computer.

Run IIS Manager. In the Connections pane of the IIS Manager window, expand the local server node, expand the Sites node, and select Default Web Site. Then in the Actions pane, click Import Application. (If you don't see an Import Application link, the most likely reason is that you have not installed Web Deploy. You can use the Web Platform Installer to install both IIS and Web Deploy.)

Default_Web_Site_in_inetmgr

In the Select the Package wizard step, navigate to the location of the package you just created. By default, that's the obj\Test\Package folder in your ContosoUniversity project folder. (A package created with the Release build configuration would be in obj\Release\Package.)

Select_the_Package_dialog_box

Click Next. The Select the Contents of the Package step is displayed.

Select_the_Contents_of_the_Package_dialog_box

Click Next.

The step that allows you to enter parameter values is displayed. The Application Path value defaults to "ContosoUniversity", because that's what you entered on the Package/Publish Web tab of the Project Properties window.

Enter_Application_Package_Information_dialog_box

Click Next.

The wizard asks if you want to delete files at the destination that aren't in the source.

Overwrite_Existing_Files_dialog_box

In this case you haven't deleted any files that you want to delete at the destination, so the default (no deletions) is okay. Click Next.

IIS Manager installs the package and reports its status.

Installation_Progress_and_Summary_dialog_box

Click Finish.

Open a browser and run the application in test by going to the URL http://localhost/ContosoUniversity.

Instructors_page_with_separate_name_fields_Test

Installing IIS Manager for Remote Administration

The process for deploying to production is similar except that you create the package using the Release build configuration, and you install it in IIS Manager using a remote connection to the hosting provider. But first you have to install the IIS Manager feature that facilitates remote connections.

Click the following link to use the Web Platform Installer for this task:

Connecting to Your Site at the Hosting Provider

After you install the IIS Manager for Remote Administration, run IIS Manager. You see a new Start Page in IIS Manager that has several Connect to ... links in a Connection tasks box. (These options are also available from the File menu.)

IIS_Manager_Remote_Admin_Start_Page

In IIS Manager, click Connect to a site. In the Specify Site Connection Details step, enter the Server name and Site name values that are assigned to you by your provider, and then click Next. For a hosting account at Cytanium.com, you get the server name from Service URL in the Visual Studio 2010 section of the welcome email. The site name is indicated by "Site/application" in the same section of the email.

Specify_Site_Connection_Details_dialog_box

In the Provide Credentials step, enter the user name and password assigned by the provider, and then click Next:

Provide_Credentials_dialog_box

You might see a Server Certificate Alert dialog box. If you're sure that you've entered the correct server and site name, click Connect.

Server_Certificate_Alert_dialog_box

In the Specify a Connection Name step, click Finish.

Specify_a_Connection_Name_dialog_box

After IIS Manager connects to the provider's server, a New Feature Available dialog box might appear that lists administration features available for download. Click Cancel — you've already installed everything you need for this deployment.

New_Feature_Available_dialog_box_Cytanium

After the New Feature Available box closes, the IIS Manager window appears. There's now a node in the Connections pane for the site at the hosting provider.

Creating a Package for the Production Site

The next step is to create a deployment package for the production environment. In the Visual Studio Solution Configurations drop-down box, select the Release build configuration.

Solution_Configurations_dropdown_Release

In Solution Explorer, right-click the ContosoUniversity project and then select Build Deployment Package.

The Output window reports a successful build and publish (package creation), and it tells you that the package is created in the obj\Release\Package folder in your project folder.

Installing the Package in the Production Environment

Now you can install the package in the production environment. In the IIS Manager Connections pane, select the new connection you added earlier. Then click Import Application, which will walk you through the same process you followed earlier when you deployed to the test environment.

IIS_Manager_with_provider_site_selected

In the Select the Package step, select the package that you just created:

Select_the_Package_dialog_box_Prod

In the Select the Contents of the Package step, leave all the check boxes selected and click Next:

Select_the_Contents_of_the_Package_dialog_box_Prod

In the Enter Application Package Information step, clear the Application Path and click Next:

Enter_Application_Package_Information_dialog_box_Prod

The wizard asks if you want to delete files at the destination that aren't in the source.

Overwrite_Existing_Files_dialog_box

You don't need to have anything deleted, so just click Next.

When you get the warning about installing to the root folder, click OK:

Installation_in_root_folder_warning

Package installation begins. When it's done, the Installation Progress and Summary dialog box is shown:

Installation_Progress_and_Summary_dialog_box

Click Finish. Your application has been deployed to the hosting provider's server, and you can test by browsing to your public site's URL.

Instructors_page_with_separate_name_fields_Prod

You've now seen how to deploy an application update by manually creating and installing a deployment package. For information about how to create and install packages from the command line in order to be able to integrate them into a continuous integration process, see the ASP.NET Deployment Content Map on the MSDN web site.

Sayed Ibrahim Hashimi – @SayedIHashimi

ddd

IIS | msbuild | MSDeploy | web | Web Deployment Tool | Web Development | Web Publishing Pipeline Tuesday, November 08, 2011 5:11:43 AM (GMT Standard Time, UTC+00:00)  #     | 
Friday, February 25, 2011

How to compress CSS/JavaScript before publish/package

Today I saw a post on stackoverflow.com asking Using Microsoft AJAX Minifier with Visual Studio 2010 1-click publish. This is a response to that question. The Web Publishing Pipeline is pretty extensive so it is easy for us to hook in to it in order to perform operation such as these. One of those extension points, as we’ve blogged about before, is creating a .wpp.targets file. If you create a file in the same directory of your project with the name {ProjectName}.wpp.targets then that file will automatically be imported and included in the build/publish process. This makes it easy to edit your build/publish process without always having to edit the project file itself. I will use this technique to demonstrate how to compress the CSS & JavaScript files a project contains before it is published/packaged.

Eventhough the question specifically states Microsoft AJAX Minifier I decided to use the compressor contained in Packer.NET (link in resources section). I did this because when I looked at the MSBuild task for the AJAX Minifier it didn’t look like I could control the output location of the compressed files. Instead it would simply write to the same folder with an extension like .min.cs or .min.js. In any case, when you publish/package your Web Application Project (WAP) the files are copied to a temporary location before the publish/package occurs. The default value for this location is obj\{Configuration}\Package\PackageTmp\ where {Configuration} is the build configuration that you are currently using for your WAP. So what we need to do is to allow the WPP to copy all the files to that location and then after that we can compress the CSS and JavaScript that goes in that folder. The target which copies the files to that location is CopyAllFilesToSingleFolderForPackage. (To learn more about these targets take a look at the file %Program Files (x86)%\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets.) To make our target run after this target we can use the MSBuild AfterTargets attribute. The project that I created to demonstrate this is called CompressBeforePublish, because of that I create a new file named CompressBeforePublish.wpp.targets to contain my changes.



  

  
  
    
    
      <_JavaScriptFiles Include="$(_PackageTempDir)\Scripts\**\*.js" />
      <_CssFiles Include="$(_PackageTempDir)\Content\**\*.css" />
    

    
    
    

    
    
    
  

Here I’ve created one target, CompressJsAndCss, and I have included AfterTargets=”CopyAllFilesToSingleFolderForPackage” which causes it to be executed after CopyAllFilesToSingleFolderForPackage. Inside this target I do two things, gather the files which need to be compressed and then I compress them.

1. Gather files to be compressed

  <_JavaScriptFiles Include="$(_PackageTempDir)\Scripts\**\*.js" />
  <_CssFiles Include="$(_PackageTempDir)\Content\**\*.css" />

Here I use an item list for both JavaScript files as well as CSS files. Notice that I am using the _PackageTempDir property to pickup .js & .css files inside the temporary folder where the files are written to be packaged. The reason that I’m doing that instead of picking up source files is because my build may be outputting other .js & .css files and which are going to be published. Note: since the property _PackageTempDir starts with an underscore it is not guaranteed to behave (or even exist) in future versions.

2. Compress files

I use the Packer task to compress the .js and .css files. For both sets of files the usage is pretty similar so I will only look at the first usage.

Here the task is fed all the .js files for compression. Take a note how I passed the files into the task using, %(_JavaScriptFiles.Identity), in this case what that does is to cause this task to be executed once per .js file. The %(abc.def) syntax invokes batching, if you are not familiar with batching please see below. For the value of the output file I  use the _PackageTempDir property again. In this case since the item already resides there I could have simplified that to be @(_JavaScriptFiles->’%(FullPath)’) but I thought you might find that expression helpful in the case that you are compressing files which do not already exist in the _PackageTempDir folder.

Now that we have added this target to the .wpp.targets file we can publish/package our web project and it the contained .js & .css files will be compressed. Note: Whenever you modify the .wpp.targets file you will have to unload/reload the web project so that the changes are picked up, Visual Studio caches your projects.

In the image below you can see the difference that compressing these files made.

image

You can download the entire project below, as well as take a look at some other resources that I have that you might be interested in.

Sayed Ibrahim Hashimi

Resources
Deployment | MSDeploy | Web Publishing Pipeline Friday, February 25, 2011 5:21:44 AM (GMT Standard Time, UTC+00:00)  #     | 
Saturday, January 08, 2011

Video on Web Deployment using Visual Studio 2010 and MSDeploy

Back in November I participated in Virtual Tech Days which is an online conference presented by Microsoft. In the session I discussed the enhancements to web deployment using Visual Studio 2010 and MSDeploy. Some of the topics which I covered includ:

You can download the video & all of my sample files at http://virtualtechdays.com/pastevents_2010november.aspx. In the samples you will find all of the scripts that I used and a bunch of others which I didn’t have time to cover. Enjoy!

Sayed Ibrahim Hashimi @sayedihashimi

Config-Transformation | IIS | msbuild | MSDeploy | speaking | Visual Studio | Visual Studio 2010 | web | Web Deployment Tool | Web Development | Web Publishing Pipeline Saturday, January 08, 2011 8:34:08 PM (GMT Standard Time, UTC+00:00)  #     | 
Thursday, November 18, 2010

XDT (web.config) Transforms in non-web projects

One of the really cool features that we shipped for Visual Studio 2010 was web.config (XDT) transformations. Because the transformations are so simple and straightforward one of the first questions that someone asks after using it is “how can I use this in my other projects?” Unfortunately this feature is only built into the Web Application Projects (WAP). But it is very easy to reuse this because we just rely on an MSBuild task to do the heavy lifting for us. I received an email from that basically went like this

“Hi, I would like to use XDT transformations on my WPF project for both the app.config file as well as my unity.xml file. How can I do this?”

So one answer is to modify your project file to use the TransformXml task as I have blogged previously about (link below). But I thought that since this was such a common problem that I should go ahead and create a .targets file which would solve the above problem and could be re-used by anyone.

Let me clarify the scenario a bit before we dive into the details of the solution. I have create a sample Wpf project, named Wpf01. Inside of that project I have created these files:

Take a look at the image below (note: I manually edited the project file to make the file nest under each other, I will explain that shortly)

image

The files with .debug/.release are transform files. When I build I expect the following to happen:

  1. Transform app.config with app.{Configuration}.config and write file to output folder with the correct name i.e. Wpf01.exe.config instead of just app.config
  2. Transform Sample01.xml with Sample01.{Configuration}.config and write it to output folder with the name Sample01.config
  3. Transform Sub\Sub\Sub01.xml with Sub\Sub\Sub01.{Configuration}.config and write it to the output folder with the name Sub\Sub\Sub01.xml
  4. None of my source files should change

Usage

Before I get into the solution let me explain how to use the solution first because if you are not interested in the MSBuild details you can skip over that Smile

  1. You must have installed Web projects with Visual Studio on the machine (it contains the TransformXmll task).
  2. Create the folder %ProgramFiles (x86)%\MSBuild\Custom. If you want to share this across team members then see my note at the end of this blog.
  3. Download TransformFiles.targets (link below) and place the file into the folder %ProgramFiles (x86)%\MSBuild\Custom.
  4. Edit your project file (right click on the project Unload Project, right click again and pick edit)
  5. At the end of the project file place the element <Import Project="$(MSBuildExtensionsPath)\Custom\TransformFiles.targets" /> immediately above the closing </Project> tag
  6. For files that you want transformed a metadata value of TransformOnBuild to true. See below on what this means.
  7. Build and take a look at files in your output directory

For #5 lets examine the sample that I created. In this sample I had an app.config file. When I first created the project the entry in the project file for app.config looked like the following.

<None Include="app.config" />

So what you need to do is to add a new metadata value as described above for that. So it will turn into the following.

<None Include="app.config">
  <TransformOnBuild>true</TransformOnBuild>
</None>

The transform targets will look for items that have this value declared on them and then during build it will transform them, if the transform file exists in the same folder as the file itself. You will need to add TransfromOnBuild to all the files that you want to transform. So in my case I added it to app.config, Sample01.xml and Sub01.xml. Note you should not add this to the transform files themselves because you will just waste your own time. After you do this you should perform a build then take a look at the output directory for your transformed files. The app.config should write out the the correct file and the others as expected.

Nest transforms under the source file

You might have noticed that in the image above that the transform files are nested under the files themselves. To do this you need to add the DependentUpon metadata value to the child items. For instance for app.config the child items look like the following.

<None Include="app.debug.config">
  <DependentUpon>app.config</DependentUpon>
</None>
<None Include="app.release.config">
  <DependentUpon>app.config</DependentUpon>
</None>

Implementation

If you are wondering how this works then this is the section for you. TransformFile.targets has 2 targets; DiscoverFilesToTransform and TransformAllFiles. DiscoverFilesToTransform looks through a set of items (None, Content, and Resource). Inside of DiscoverFilesToTransform I look for values with the %(TransformOnBuild)==true. After all of those are collected I identify if there is an app.config file being transformed and if so it is placed into a specific item list and all others go into another item list.

Inside of TransformAllFiles the TransformXml task is used to transform all of the files. This target injects itself into the build process by having the attribute AfterTargets="Build;_CopyAppConfigFile". So whenever the Build or _CopyAppConfigFile targets are called the TransformAllFiles target will execute.

Here if the full code for this file.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="TransformXml"
         AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
  
  <ItemDefinitionGroup>
    <!-- Set the default value to false here -->
    <None>
      <TransformOnBuild>false</TransformOnBuild>
    </None>    
    <Content>
      <TransformOnBuild>false</TransformOnBuild>
    </Content>    
    <Resource>
      <TransformOnBuild>false</TransformOnBuild>
    </Resource>
    <EmbeddedResource>
      <TransformOnBuild>false</TransformOnBuild>
    </EmbeddedResource>
    
    <_FilesToTransform>
      <IsAppConfig>false</IsAppConfig>
    </_FilesToTransform>
  </ItemDefinitionGroup>

  <PropertyGroup>
    <TransformAllFilesDependsOn>
      DiscoverFilesToTransform;
    </TransformAllFilesDependsOn>
  </PropertyGroup>
  <Target Name="TransformAllFiles" DependsOnTargets="$(TransformAllFilesDependsOn)" AfterTargets="Build;_CopyAppConfigFile">
    <!-- Now we have the item list _FilesToTransformNotAppConfig and _AppConfigToTransform item lists -->
    <!-- Transform the app.config file -->    
    <ItemGroup>
      <_AppConfigTarget Include="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')" />
    </ItemGroup>
    
    <PropertyGroup>
      <_AppConfigDest>@(_AppConfigTarget->'%(FullPath)')</_AppConfigDest>
    </PropertyGroup>

    <MakeDir Directories="@(_FilesToTransformNotAppConfig->'$(OutDir)%(RelativeDir)')"
             Condition="Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)')"/>
    
    <TransformXml Source="@(_AppConfigToTransform->'%(FullPath)')"
                  Transform="%(RelativeDir)%(Filename).$(Configuration)%(Extension)"
                  Destination="$(_AppConfigDest)"
                  Condition=" Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)') " />

    
    <TransformXml Source="@(_FilesToTransformNotAppConfig->'%(FullPath)')"
                  Transform="%(RelativeDir)%(Filename).$(Configuration)%(Extension)"
                  Destination="@(_FilesToTransformNotAppConfig->'$(OutDir)%(RelativeDir)%(Filename)%(Extension)')"
                  Condition=" Exists('%(RelativeDir)%(Filename).$(Configuration)%(Extension)') " />
  </Target>
  
  <Target Name="DiscoverFilesToTransform">
    <!-- 
    This will look through items list: None & Content for those
    with Metadata <TransformOnBuild>True</TransformOnBuild>
    -->
    <ItemGroup>
      <_FilesToTransform Include="@(None);@(Content);@(Resource);@(EmbeddedResource)"
                         Condition=" '%(TransformOnBuild)' == 'true' "/>
    </ItemGroup>    

    <PropertyGroup>
      <_AppConfigFullPath>@(AppConfigWithTargetPath->'%(RootDir)%(Directory)%(Filename)%(Extension)')</_AppConfigFullPath>
    </PropertyGroup>

    <!-- Now look to see if any of these are the app.config file -->
    <ItemGroup>
      <_FilesToTransform Condition=" '%(FullPath)'=='$(_AppConfigFullPath)' ">
        <IsAppConfig>true</IsAppConfig>
      </_FilesToTransform>
    </ItemGroup>
          
    <ItemGroup>
      <_FilesToTransformNotAppConfig Include="@(_FilesToTransform)"
                                     Condition=" '%(IsAppConfig)'!='true'"/>
      
      <_AppConfigToTransform  Include="@(_FilesToTransform)"
                              Condition=" '%(IsAppConfig)'=='true'"/>
    </ItemGroup>
  </Target>
</Project>

Gaps

With most things found on blogs there are some gaps Those are described here.

Clean build => It’s a best practice to delete files upon clean, but in this case I am not. This would be pretty easy to add, if you are interested let us know and I will update the sample.

Incremental build => The transforms will run every time you build even if the outputs are up to date, if this is an issue for you let us know and I will update the sample.

Sharing with team members

If you want to share with team members instead of placing this into %ProgramFiles (x86)% just place it into a folder in version control then change the <Import statement to point to that file instead of using MSBuildExtensionPath.

 

If you end up using this please let us know what is your experience with it.

Resources

Sayed Ibrahim Hashimi @sayedihashimi

msbuild | MSBuild 4.0 | MSDeploy | Web Publishing Pipeline Thursday, November 18, 2010 5:41:09 AM (GMT Standard Time, UTC+00:00)  #     | 
Thursday, October 21, 2010

ASP.NET Web Projects: web.debug.config & web.release.config

Warning: What you see below feels hacky to me, but if you find it useful then use it

I have heard a lot of questions and confusion regarding web.debug.config and web.release.config. For example here is just one question on StackOverflow. The question states:

Hello, I want to use the web.config transformation that works fine for publish also for debugging.

When i publish a web app, visual studio automatically transforms the web.config based on my 
current build configuration. How can i tell visual studio 
to do the same when i start debugging. On debug start it simply 
uses the default web.config without transformation.

Any idea?

First let me explain, as I did to that question, the purpose of the files: web.config/web.debug.config/web.release.config.

web.config

This is the config file which developers should use locally. Ideally you should get this to be standardized. For instance you could use localhost for DB strings, and what not. You should strive for this to work on dev machines without changes.

web.debug.config

This is the transform that is applied when you publish your application to the development staging environment. This would make changes to the web.config which are required for the target environment.

web.release.config

This is the transform that is applied when you publish your application to the "production" environment. Obviously you'll have to be careful with passwords depending on your application/team.

The problem with transforming the web.config that you are currently running is that a transform can perform destructive actions to the web.config. For example it may delete a attributes, delete elements, etc.

Resolution

Ok, with that out the way not let’s see how we can enable what the question asker wants to do. To recap, when he builds on a particular configuration he wants a specific transform to be applied to web.config. So obviously you do not want to maintain a web.config file, because it is going to be overwritten. So what we need to do is to create a new file web.template.config, which is just a copy of web.config. Then just delete web.config by using Windows Explorer (don’t delete using Visual Studio because we do not want to delete it from the project). Note: If you are using a source control provider which is integrated into Visual Studio then you probably want to delete web.config from source control. Also with this we do not want to use web.debug.config or web.release.config because these already have a well defined role in the Web Publishing Pipeline so we do not want to disturb that. So instead we will create two new files, in the same folder as the project and web.template.config, web.dev.debug.config and web.dev.release.config. The ideas is that these will be the transforms applied when you debug, or run, your application from Visual Studio. Now we need to hook into the build/package/publish process to get this all wired up. With Web Application Projects (WAP) there is an extensibility point that you can create a project file in the same folder with the name {ProjectName}.wpp.targets where {ProjectName} is the name of the project. If this file is on disk in the same folder as the WAP then it will automatically be imported into the project file. So I have created this file. And I have placed the following content:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- Make sure web.config will be there even for package/publish -->
  <Target Name="CopyWebTemplateConfig" BeforeTargets="Build">
    <Copy SourceFiles="web.template.config"
          DestinationFiles="web.config"/>
  </Target>
  
  <PropertyGroup>
    <PrepareForRunDependsOn>
      $(PrepareForRunDependsOn);
      UpdateWebConfigBeforeRun;
    </PrepareForRunDependsOn>
  </PropertyGroup>

  <!-- This target will run right before you run your app in Visual Studio -->
  <Target Name="UpdateWebConfigBeforeRun">
    <Message Text="Configuration: $(Configuration): web.dev.$(Configuration).config"/>
    <TransformXml Source="web.template.config"
              Transform="web.dev.$(Configuration).config"
              Destination="web.config" />
  </Target>

  <!-- Exclude the config template files from the created package -->
  <Target Name="ExcludeCustomConfigTransformFiles" BeforeTargets="ExcludeFilesFromPackage">
    <ItemGroup>
      <ExcludeFromPackageFiles Include="web.template.config;web.dev.*.config"/>
    </ItemGroup>
    <Message Text="ExcludeFromPackageFiles: @(ExcludeFromPackageFiles)" Importance="high"/>
  </Target>
</Project>

Let me explain this a bit. I have created the CopyWebTemplateConfig target which will always copy web.template.config to web.config on build, even if you are not debugging your application in Visual Studio. This is needed because we still need to support the package/publish process of Visual Studio. Then I extended the property PrepareForRunDependsOn to include the UpdateWebConfigBeforeRun target. This property is used to identify the list of targets which needs to be executed before any managed project is run from Visual Studio. In this target I am using the TransformXml task to transform web.template.config, using the correct web.dev.***.config file. After that your app starts up using the correct web.config based on  your build configuration.

After that I have another target ExcludeCustomConfigTransformsFiles, which I inject into the package/publish process via the attribute BeforeTargets=”ExcludeFilesFromPackage”. This is needed because we do not want these files to be included when the application is packaged or published.

So that is really all there is to it. To explain the package/publish process a bit more for this scenario. When you package/publish web.debug.config or web.release.config, depending on build configuration, will still be used. But ultimately the file that it is transforming is web.template.config, so you may have to adjust depending on what you have in that file. Questions/Comments?

Sayed Ibrahim Hashimi - @sayedihashimi

msbuild | MSDeploy | Web Publishing Pipeline Thursday, October 21, 2010 7:13:26 AM (GMT Daylight Time, UTC+01:00)  #     | 
Sunday, August 15, 2010

Web Deployment Tool (MSDeploy): How to exclude files from package based on Configuration

A while back I posted an entry on How to build a package including extra files or exclude files a reader posted a question to StackOverflow.com asking how to exclude files from the created package based on the configuration for the project. He asked me to take a look at it so I figured it would be a good blog post.

From the previous post we can see that the way to exclude files from packaging is by declaring an item as follows.

<ItemGroup>
  <ExcludeFromPackageFiles Include="Sample.Debug.xml">
    <FromTarget>Project</FromTarget>
  </ExcludeFromPackageFiles>
</ItemGroup>

So we need to extend this to only exclude files if the config is a certain value. Since MSBuild supports conditions on almost every element this is going to be a breeze. As an example I have created a sample web project with a scripts directory that has the following files.

image

In that folder there I there are two files which have ‘debug’ in the name of the file. We only want those to be included if the configuration is set to Debug, or another way of putting it is we want to exclude those files if the configuration is not Debug. So we need to create to add files to the ExcludeFromPackageFiles and guard it with the condition that the configuration is not debug. Here is that.

<Target Name="CustomExlucdeFiles" BeforeTargets="ExcludeFilesFromPackage">
  <ItemGroup Condition=" '$(Configuration)'!='Debug' ">
    <ExcludeFromPackageFiles Include="scripts\**\*debug*" />
  </ItemGroup>
  
  <Message Text="Configuration: $(Configuration)" />
  <Message Text="ExcludeFromPackageFiles: @(ExcludeFromPackageFiles)" Importance="high" />
</Target>

You can see the item group defined above which does what we want. Please note that I put this inside of a target, CustomExcludeFiles, I will discuss why in a bit but let’s stay on topic now. So this is pretty straight forward when the item group is evaluated all files under scripts which have debug in the file name will be excluded if the configuration is not set to Debug. Let’s see if it works, I will build the deployment package once in both debug & release then examine the contents of the Package folder.

image

So we can see that the files were excluded from the Release package. Now back to why I declared the item group in a target instead of directly in the project file itself. I noticed that if I declare that item in the project file there are some visual issues with the representation in the Solution Explorer. To be specific the files show up as dups, see image below.

image

I have reported this to the right people, but for now this is a harmless issue with an easy workaround.

Sayed Ibrahim Hashimi

Deployment | msbuild | MSDeploy | Web Deployment Tool | Web Publishing Pipeline Sunday, August 15, 2010 7:56:50 PM (GMT Daylight Time, UTC+01:00)  #     | 
Saturday, May 01, 2010

Web Deployment Tool (MSDeploy) : Build Package including extra files or excluding specific files

If you are using Visual Studio 2010 then you may already be aware that Web Deployment Tool (aka MSDeploy) is integrated into Visual Studio. I’ve posted a few blog entries already about this tool. Two of the common questions that I get discussing this with people are

  1. How do I exclude files from being placed in the package?
  2. How do I add other files to the created package?

I will address these two questions here, first we look at the easier one, how to exclude files but we will go over a bit of background first.

Web Publishing Pipeline

With Visual Studio 2010 a new concept has been created which is known as the Web Publishing Pipeline. In a nutshell this is a process which will take your web application, build it and eventually create a package that you can use to deploy your application. This process is fully captured in MSBuild. With VS 2010 many targets and many tasks are shipped to support this process. Since its captured in MSBuild format, you can customize and extend to your hearts desire. So what we need to do is hook into this process to perform the customizations that we need. This process is captured in the following files.

%program files%\MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets
%program files%\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets

The Microsoft.WebApplication.targets file is imported by the web applications projects file, then that file imports the Microsoft.Web.Publishing.targets file.

Excluding files from being packaged

If you open the project file of a web application created with VS 2010 towards the bottom of it you will find a line with.

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

BTW you can open the project file inside of VS. Right click on the project pick Unload Project. Then right click on the unloaded project and select Edit Project.

This statement will include all the targets and tasks that we need. Most of our customizations should be after that import, if you are not sure put if after! So if you have files to exclude there is an item name, ExcludeFromPackageFiles, that can be used to do so. For example let’s say that you have file named Sample.Debug.js which included in your web application but you want that file to be excluded from the created packages. You can place the snippet below after that import statement.

<ItemGroup>
  <ExcludeFromPackageFiles Include="Sample.Debug.xml">
    <FromTarget>Project</FromTarget>
  </ExcludeFromPackageFiles>
</ItemGroup>

By declaring populating this item the files will automatically be excluded. Note the usage of the FromTarget metadata here. I will not get into that here, but you should know to always specify that.

Including extra files into the package

Including extra files into the package is a bit harder but still no bigee if you are comfortable with MSBuild, and if you are not then read this.  In order to do this we need to hook into the part of the process that collects the files for packaging. The target we need to extend is called CopyAllFilesToSingleFolder. This target has a dependency property, PipelinePreDeployCopyAllFilesToOneFolderDependsOn, that we can tap into and inject our own target. So we will create a target named CustomCollectFiles and inject that into the process. We achieve this with the following (remember after the import statement).

<PropertyGroup>
  <CopyAllFilesToSingleFolderForPackageDependsOn>
    CustomCollectFiles;
    $(CopyAllFilesToSingleFolderForPackageDependsOn);
  </CopyAllFilesToSingleFolderForPackageDependsOn>
</PropertyGroup>

This will add our target to the process, now we need to define the target itself. Let’s assume that you have a folder named Extra Files that sits 1 level above your web project. You want to include all of those files. Here is the CustomCollectFiles target and we discuss after that.

<Target Name="CustomCollectFiles">
  <ItemGroup>
    <_CustomFiles Include="..\Extra Files\**\*" />

    <FilesForPackagingFromProject  Include="%(_CustomFiles.Identity)">
      <DestinationRelativePath>Extra Files\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
    </FilesForPackagingFromProject>
  </ItemGroup>
</Target>

Here what I did was create the item _CustomFiles and in the Include attribute told it to pick up all the files in that folder and any folder underneath it. Then I use this item to populate the FilesForPackagingFromProject item. This is the item that MSDeploy actually uses to add extra files. Also notice that I declared the metadata DestinationRelativePath value. This will determine the relative path that it will be placed in the package. I used the statement Extra Files%(RecursiveDir)%(Filename)%(Extension) here. What that is saying is to place it in the same relative location in the package as it is under the Extra Files folder.

Admittedly this could be easier, but its not too bad, and its pretty flexible.

Sayed Ibrahim Hashimi

msbuild | MSBuild 4.0 | MSDeploy | Visual Studio | Visual Studio 2010 | Web Deployment Tool | Web Publishing Pipeline Saturday, May 01, 2010 4:09:16 AM (GMT Daylight Time, UTC+01:00)  #     |