- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Saturday, February 18, 2012

How to create a Web Deploy package when publishing a ClickOnce project

The other day I saw a question on StackOverflow (link in resources below) asking How you can create a Web Deploy (AKA MSDeploy) package when publishing a ClickOnce project. The easiest way to do this is to use the Web Deploy command line utility, msdeploy.exe. With the command line you can easily create an MSDeploy package from a folder with a command like the following:

    %msdeploy% 
      -verb:sync 
      -source:contentPath="C:\Temp\_NET\WebPackageWithClickOnce\WebPackageWithClickOnce\bin\Debug\app.publish" 
      -dest:package="C:\Temp\_NET\WebPackageWithClickOnce\WebPackageWithClickOnce\bin\Debug\co-pkg.zip"

 

Here you can see that I’m using the sync verb, along with a contentPath provider (which points to a folder) as the source and the destination is using the package provider, this point to where I want the package to be stored.

Now that we understand how to create an MSDeploy package from a folder we need to extend the ClickOnce publish process to create a package. I’m not a ClickOnce expert, but the ClickOnce publish process is captured in MSBuild so after investigating for a bit I found the following relevant details.

Now that we know what target to extend as well as what property we can use to refer to the folder which has the content we can complete sample. We need to edit the project file. Below is the full contents which I have placed at the bottom of the project file (right above </Project>).

  <PropertyGroup>
    <WebDeployPackageName Condition=" '$(WebDeployPackageName)'=='' ">$(MSBuildProjectName).zip</WebDeployPackageName>
    <!--Unless specified otherwise, the tools will go to HKLM\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\1 to get the installpath for msdeploy.exe.-->
    <MSDeployPath Condition="'$(MSDeployPath)'==''">$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\3@InstallPath)</MSDeployPath>
    <MSDeployPath Condition="'$(MSDeployPath)'==''">$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\2@InstallPath)</MSDeployPath>
    <MSDeployPath Condition="'$(MSDeployPath)'==''">$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IIS Extensions\MSDeploy\1@InstallPath)</MSDeployPath>
    <MSDeployExe Condition=" '$(MSDeployExe)'=='' ">$(MSDeployPath)msdeploy.exe</MSDeployExe>
  </PropertyGroup>
  <Target Name="CreateWebDeployPackage" AfterTargets="Publish" DependsOnTargets="Publish">
    <!--
    %msdeploy% 
      -verb:sync 
      -source:contentPath="C:\Temp\_NET\WebPackageWithClickOnce\WebPackageWithClickOnce\bin\Debug\app.publish" 
      -dest:package="C:\Temp\_NET\WebPackageWithClickOnce\WebPackageWithClickOnce\bin\Debug\co-pkg.zip"
      -->
    <PropertyGroup>
      <Cmd>"$(MSDeployExe)" -verb:sync -source:contentPath="$(MSBuildProjectDirectory)\$(PublishDir)" -dest:package="$(OutDir)$(WebDeployPackageName)"</Cmd>
    </PropertyGroup>
    <Message Text="Creating web deploy package with command: $(Cmd)" />
    <Exec Command="$(Cmd)" />
  </Target>

Here I’ve created a couple properties as well as a new target, CreateWebDeployPackage. I have declared the property WebDeployPackageName which will be the name (excluding path) of the Web Deploy package which gets created. This defaults to the name of the project, but you can override it if you want. Next I define the property, MSDeployPath, which points to msdeploy.exe. It will pick the latest version.

The CreateWebDeployPackage target just constructs the full command line call which needs to be executed and invokes it using the Exec MSBuild task. There are a couple subtle details on the target itself though which are worth pointing out. The target has declared AfterTargets=”Publish” which means that it will be invoked after the Publish target. It also declares DependsOnTargets=”Publish”. Which means that whenever the target gets invoked that Publish will need to be executed before CreateWebDeployPackage.

Now that we have defined these updates when you publish your ClickOnce project (wither through Visual Studio or the command line/build servers) a Web Deploy package will be generated in the output folder which you can use to incrementally publish your ClickOnce app to your web server. You can find the latest version of this sample on my github repository.

Sayed Ibrahim Hashimi @SayedIHashimi

Resources

ClickOnce | IIS | Microsoft | msbuild | MSDeploy | web Saturday, February 18, 2012 6:47:30 PM (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)  #     | 
Saturday, February 04, 2012

SlowCheetah XML Updates for Setup projects, ClickOnce, F# and more

I blogged in December about SlowCheetah which is a Visual Studio add in which can be used to transform app.config (or any XML file for that matter) in ways similar to web.config. A few days back we released an update to this adding with a few key items which are listed below.

  1. Support for ClickOnce
  2. Support for Setup projects
  3. Support for F# projects
  4. Support for a custom diff viewer
  5. Support for XP/Win server 2003
  6. Better error handling, logging

I’ve created a set of samples for SlowCheetah which I’ll use to describe these new features. Download links for the samples, as well as pointers to the latest versions are at the bottom of this page.

Support for ClickOnce

If you are building a windows client application (Win forms or WPF) then you can use ClickOnce to publish this application. When you publish a project using ClickOnce a setup.exe will be generated as well as an HTML page which can be served by a web server to enable users to download it. When this setup.exe is generated all of the files needed to run your application (including app.config) are placed inside of that executable. When you have a project using SlowCheetah and you publish using a specific configuration it will now publish the transformed app.config (as well as any other transformed files).

In order to demonstrate this I’ve created a sample WPF application which I will publish with ClickOnce (download link is at the bottom). This project has an app.config file as well as app.Debug.config and app.Release.config.

Below is what the original app.config file looks like.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="appName" value="WPF Demo-Debug-default"/>
    <add key="url" value="http://localhost:8080/Default/"/>
    <add key="email" value="demo-default@contoso.com"/>
  </appSettings>
  
  <connectionStrings>
    <clear />
    <add name="RecordsDb" connectionString=".\SQLExpress;Initial Catalog=RecordsDb-Default;Integrated Security=true"/>  
  </connectionStrings>
  
</configuration>

And here is app.Debug.Config

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  
  <appSettings>
    <add key="appName" value="WPF Demo-Debug" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
    <add key="url" value="http://localhost:8080/" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
    <add key="email" value="debug@contoso.com" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
  </appSettings>

  <connectionStrings>
    <add name="RecordsDb" connectionString=".\SQLExpress;Initial Catalog=RecordsDb;Integrated Security=true"
         xdt:Transform="Replace" xdt:Locator="Match(name)"/>
  </connectionStrings>
  
</configuration>

ddd

As you can see I am transforming both the appSettings as well as the connection string. The application itself is pretty simple, it has a single page which shows the following values.

When I run this application in Debug mode I will get the following result.

image

In previous versions of SlowCheetah when you published your application with ClickOnce the original version of app.config was getting published instead of the transformed one. This was actually a bug in the .targets files for ClickOnce, but we have now worked around it in the SlowCheetah .targets files. So let’s say that you have a ClickOnce application and you need to publish different versions which have config changes then you can now easily do that with SlowCheetah.

Support for Setup projects

Visual Studio 2010 also has Setup projects, which can be used to generate an MSI. When you create the MSI you can specify that it includes the outputs of a project in the solution. We had a similar story here in which the original app.config was being placed into the MSI instead of the transformed on in the output folder. We have now fixed that and if you have a Setup project which reference a project with SlowCheetah transforms then the transformed files will endup in the resulting MSI instead of the original one.

Support for F# projects

If you have F# project and tried SlowCheetah you probably noticed that the “Add Transforms” menu item didn’t show up for those project types. They now do.

image

After you invoke this command you will see a transform created for each configuration defined for the project. In other project types these transform appear as child items, but F# projects don’t support this in the same way as C#/VB projects so they will not be nested. So they will show up as the following.
image

I created a very simple F# application to demonstrate the behavior, below is the code.

// A simple application to create the config and show results in the console
let settingOne = System.Configuration.ConfigurationManager.AppSettings.["settingOne"];
let settingTwo = System.Configuration.ConfigurationManager.AppSettings.["settingTwo"];
let settingthree = System.Configuration.ConfigurationManager.AppSettings.["settingThree"];


printfn "settingOne: %s" settingOne
printfn "settingTwo: %s" settingTwo
printfn "settingThree: %s" settingthree

printfn " "
printfn "Press any key to close"
System.Console.ReadKey(true)

As you can see this app just reads a few config values from app.config and displays them on the console. Here is the original app.config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="settingOne" value="one default value"/>
    <add key="settingTwo" value="two default value"/>
    <add key="settingThree" value="three default value"/>
  </appSettings>
</configuration>

Here is app.Debug.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="settingOne" value="one Debug"
         xdt:Locator="Match(key)" xdt:Transform="Replace" />
    
    <add key="settingTwo" value="two Debug"
         xdt:Locator="Match(key)" xdt:Transform="Replace" />
    
    <add key="settingThree" value="three Debug"
         xdt:Locator="Match(key)" xdt:Transform="Replace" />
  </appSettings>
</configuration>

There is a similar app.Release.config as well for this project.

When I run this in Debug mode below is the result

image

When I run this in Release mode below is the result.

image

As you can see F# project will now pickup up the transformed web.config file instead of the original one when running.

Support for a custom diff viewer

As you may know SlowCheetah has a Preview Transform functionality that allows you to quickly see the difference between the source file and the transformed one. For example in my Wpf.Transform project I have a app.config file and app.Debug.config when I select one of the transforms I can see this menu item.

image
When that menu item is invoked we open the Visual Studio diff viewer to show you the difference between these two files. We have received a number of requests to support different diff viewers and we have enabled this in the latest release.

After installing SlowCheetah you can go to Tools->Options and then pick SlowCheetah on the left hand side. You will see the following.

image

Here we have three settings which you can customize, they are outlined below.

Setting

Description

Preview Tool Command Line This is a string.Format string that should be used to construct the command to be executed. Here {0} will be replaced with the full path to the original file and {1} with the full path to the transformed file.
Preview Tool Executable Path This is the full path to the .exe which should be invoked during preview.
The default value for this is “C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\diffmerge.exe” which is the diff viewer that VS uses by default.
Run Preview Tool If this is set to False then a diff viewer will not be shown, instead the transformed file will just be opened.

If you want to use KDiff with SlowCheetah then you need it to invoke the following command:

“C:\Program Files (x86)\KDiff3\kdiff3.exe” {Path-to-original-file} {Path-to-transformed-file}

So in this case I just have to update the value for Preview Tool Executable Path to be C:\Program Files (x86)\KDiff3\kdiff3.exe. After that when I perform a transform I will see something like.

image

Support for XP/Win server 2003

After publishing the original version of SlowCheetah users started complaining that it didn’t work for either Windows XP or Windows Server 2003. This is because SlowCheetah writes file under the %LocalAppData% folder. What I didn’t realize was that XP and Win server 2003 don’t have this environment variable defined! Because of this I had to update the logic of where to write the files if that environment variable didn’t exist. Now everything works seamlessly.

Better error handling, logging

We had some issues in previous builds and they were difficult to diagnose because we didn’t have any logging support. We have now updated this and we should be able to diagnose user issues much easier.

 

Below are a list of resources and pointers, I hope that you guys like these updates and please do continue giving us feedback on this!

Resources

Sayed Ibrahim Hashimi @SayedIHashimi

Note: I work for Microsoft but this add in was not created nor is supported by Microsoft.

Saturday, February 04, 2012 8:14: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)  #     | 
Thursday, December 29, 2011

Updating XML files with MSBuild

Today I just saw a question posted on StackOverflow asking how to update an XML file using MSBuild during a CI build executed from Team City.

There is not correct single answer, there are several different ways that you can update an XML file during a build. Most notably:

  1. Use SlowCheetah to transform the files for you
  2. Use the TransformXml task directly
  3. Use the built in (MSBuild 4.0) XmlPoke task
  4. Use a third party task library

#1 Use SlowCheetah to transform the files for you

Before you start reading too far into this post let me go over option #3 first because I think it’s the easiest approach and the most easily maintained. You can download my SlowCheetah XML Transforms Visual Studio add in. Once you do this for your projects you will see a new menu command to transform a file on build (for web projects on package/publish). If you want to get this working from a CI server, its pretty easy see my post at http://sedodream.com/2011/12/12/SlowCheetahXMLTransformsFromACIServer.aspx.

#2 Use the TransformXml task directly

If you want a technique where you have a “main” XML file and you want to be able to contain transformations to that file inside of a separate XML file then you can use the TransformXml task directly. For more info see my previous blog post at http://sedodream.com/2010/11/18/XDTWebconfigTransformsInNonwebProjects.aspx

#3 Use the built in XmlPoke task

Sometimes it doesn’t make sense to create an XML file with transformations for each XML file. For example if you have an XML file and you want to modify a single value but to create 10 different files the XML transformation approach doesn’t scale well. In this case it might be easier to use the XmlPoke task. Note this does require MSBuild 4.0.

Below are the contents of sample.xml (came from the SO question).

<Provisioning.Lib.Processing.XmlConfig instancetype="XmlConfig, Processing, Version=1.0.0.0, Culture=neutral">
  <item>
    <key>IsTestEnvironment</key>
    <value>True</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutDir</key>
    <value>C:\DevPath1</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutCopyDir</key>
    <value>C:\DevPath2</value>
    <encrypted>False</encrypted>
  </item>
</Provisioning.Lib.Processing.XmlConfig>

So in this case we want to update the values of the value element. So the first thing that we need to do is to come up with the correct XPath for all the elements which we want to update. In this case we can use the following XPath expressions for each value element.

I’m not going to go over what you need to do to figure out the correct XPath because that’s not the purpose of this post. There are a bunch of XPath related resources on the interwebs. In the resources section I have linked to the online XPath tester which I always use.

Now that we’ve got the required XPath expressions we need to construct our MSBuild elements to get everything updated. Here is the overall technique:

  1. Place all info for all XML updates into an item
  2. Use XmlPoke along with MSBuild batching to perform all the updates

For #2 if you are not that familiar with MSBuild batching then I would recommend buying my book or you can take a look at the resources I have online relating to batching (the link is below in resources section). Below you will find a simple MSBuild file that I created, UpdateXm01.proj.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="UpdateXml" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <SourceXmlFile>$(MSBuildProjectDirectory)\sample.xml</SourceXmlFile>
    <DestXmlFiles>$(MSBuildProjectDirectory)\result.xml</DestXmlFiles>
  </PropertyGroup>

  <ItemGroup>
    <!-- Create an item which we can use to bundle all the transformations which are needed -->
    <XmlConfigUpdates Include="ConfigUpdates-SampleXml">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value</XPath>
      <NewValue>H:\ReleasePath1</NewValue>
    </XmlConfigUpdates>
    
    <XmlConfigUpdates Include="ConfigUpdates-SampleXml">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value</XPath>
      <NewValue>H:\ReleasePath2</NewValue>
    </XmlConfigUpdates>
  </ItemGroup>
  
  <Target Name="UpdateXml">
    <Message Text="Updating XML file at $(DestXmlFiles)" />
    <Copy SourceFiles="$(SourceXmlFile)"
          DestinationFiles="$(DestXmlFiles)" />
    <!-- Now let's execute all the XML transformations -->
    <XmlPoke XmlInputPath="$(DestXmlFiles)"
             Query="%(XmlConfigUpdates.XPath)"
             Value="%(XmlConfigUpdates.NewValue)"/>
  </Target>
</Project>

The parts to pay close attention to is the XmlConfigUpdates item and the contents of the UpdateXml task itself. Regarding the XmlConfigUpdates, that name is arbitrary you can use whatever name you want, you can see that the Include value (which typically points to a file) is simply left at ConfigUpdates-SampleXml. The value for the Include attribute is not used here. I would place a unique value for the Include attribute for each file that you are updating. This just makes it easier for people to understand what that group of values is for, and you can use it later to batch updates. The XmlConfigUpdates item has these two metadata values:

Inside of the UpdateXml target you can see that we are using the XmlPoke task and passing the XPath as %(XmlConfigUpdate.XPath) and the value as %(XmlConfigUpdates.NewValue). Since we are using the %(…) syntax on an item this start MSBuild batching. Batching is where more than one operation is performed over a “batch” of values. In this case there are two unique batches (1 for each value in XmlConfigUpdates) so the XmlPoke task will be invoked two times. Batching can be confusing so make sure to read up on it if you are not familiar.

Now we can use msbuild.exe to start the process. The resulting XML file is:

<Provisioning.Lib.Processing.XmlConfig instancetype="XmlConfig, Processing, Version=1.0.0.0, Culture=neutral">
  <item>
    <key>IsTestEnvironment</key>
    <value>True</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutDir</key>
    <value>H:\ReleasePath1</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutCopyDir</key>
    <value>H:\ReleasePath2</value>
    <encrypted>False</encrypted>
  </item>
</Provisioning.Lib.Processing.XmlConfig>

So now we can see how easy it was to use the XmlPoke task. Let’s now take a look at how we can extend this example to manage updates to the same file for an additional environment.

How to manage updates to the same file for multiple different results

Since we’ve created an item which will keep all the needed XPath as well as the new values we have a bit more flexibility in managing multiple environments. In this scenario we have the same file that we want to write out, but we need to write out different values based on the target environment. Doing this is pretty easy. Take a look at the contents of UpdateXml02.proj below.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="UpdateXml" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  
  <PropertyGroup>
    <SourceXmlFile>$(MSBuildProjectDirectory)\sample.xml</SourceXmlFile>
    <DestXmlFiles>$(MSBuildProjectDirectory)\result.xml</DestXmlFiles>
  </PropertyGroup>

  <PropertyGroup>
    <!-- We can set a default value for TargetEnvName -->
    <TargetEnvName>Env01</TargetEnvName>
  </PropertyGroup>
  
  <ItemGroup Condition=" '$(TargetEnvName)' == 'Env01' ">
    <!-- Create an item which we can use to bundle all the transformations which are needed -->
    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value</XPath>
      <NewValue>H:\ReleasePath1</NewValue>
    </XmlConfigUpdates>
    
    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value</XPath>
      <NewValue>H:\ReleasePath2</NewValue>
    </XmlConfigUpdates>
  </ItemGroup>

  <ItemGroup Condition=" '$(TargetEnvName)' == 'Env02' ">
    <!-- Create an item which we can use to bundle all the transformations which are needed -->
    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value</XPath>
      <NewValue>G:\SomeOtherPlace\ReleasePath1</NewValue>
    </XmlConfigUpdates>

    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value</XPath>
      <NewValue>G:\SomeOtherPlace\ReleasePath2</NewValue>
    </XmlConfigUpdates>
  </ItemGroup>
  
  <Target Name="UpdateXml">
    <Message Text="Updating XML file at $(DestXmlFiles)" />
    <Copy SourceFiles="$(SourceXmlFile)"
          DestinationFiles="$(DestXmlFiles)" />
    <!-- Now let's execute all the XML transformations -->
    <XmlPoke XmlInputPath="$(DestXmlFiles)"
             Query="%(XmlConfigUpdates.XPath)"
             Value="%(XmlConfigUpdates.NewValue)"/>
  </Target>
</Project>

The differences are pretty simple, I introduced a new property, TargetEnvName which lets us know what the target environment is. (note: I just made up that property name, use whatever name you like). Also you can see that there are two ItemGroup elements containing different XmlConfigUpdate items. Each ItemGroup has a condition based on the value of TargetEnvName so only one of the two ItemGroup values will be used. Now we have a single MSBuild file that has the values for both environments. When building just pass in the property TargetEnvName, for example msbuild .\UpdateXml02.proj /p:TargetEnvName=Env02. When I executed this the resulting file contains:

<Provisioning.Lib.Processing.XmlConfig instancetype="XmlConfig, Processing, Version=1.0.0.0, Culture=neutral">
  <item>
    <key>IsTestEnvironment</key>
    <value>True</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutDir</key>
    <value>G:\SomeOtherPlace\ReleasePath1</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutCopyDir</key>
    <value>G:\SomeOtherPlace\ReleasePath2</value>
    <encrypted>False</encrypted>
  </item>
</Provisioning.Lib.Processing.XmlConfig>

You can see that the file has been updated with different paths in the value element.

#4 Use a third party task library

If you are not using MSBuild 4 then you will need to use a third party task library like the MSBuild Extension Pack (link in resources).

Hope that helps.

Resources

Sayed Ibrahim Hashimi @SayedIHashimi

Thursday, December 29, 2011 3:38:56 AM (GMT Standard Time, UTC+00:00)  #     | 
Saturday, December 24, 2011

Package once publish anywhere

When I discuss the web package features in Visual Studio one item that always comes up is that people want to be able to create a single Web Deploy package and then publish that to may different environments. One reason why they have a difficult time in doing this is because when a web package is created the web.config transforms are executed before the package is created and then only the transformed web.config file makes it into the package. Also Web Deploy itself doesn’t have support for web.config transforms. Since I’ve heard this soo many times I decided to take matters into my own hands and address this for existing Visual Studio 2010 scenarios. Here is what I (note that this is not a Microsoft sponsored project, it’s a personal project) have done.

I have create a new Nuget package, PackageWeb, which you can use to address this. In this blog post I will focus on the user experience and not discuss too many details about the implementation, but I will have later blog posts to discuss how this was implemented. Since this is a Nuget package you have to have Nuget installed. If you don’t go to nuget.org and click the huge Install Nuget button. Here are the steps to get you started:

  1. Install the PackageWeb Nuget package
  2. Create a web deployment package
  3. Notice there is a Publish-Interactive.ps1 file in the package folder
  4. Execute the Publish-Interactive.ps1 file from the package folder

#1 Install the PacakgeWeb Nuget package

To install the package just right-click on the web project in the Solution Explorer and select Manage Nuget Packages. In the dialog that pops up just search for PackageWeb and click on the install button.

SNAGHTML6c282a

After you do this a few files will be added to your project and the package creation process will be extended to include those files. Stay tuned to this blog for more details on this part.

#2 Create a web deployment package

For most who are interested they will already know how to do this, but if not don’t worry its simple. Right-click on the project in solution explorer and pick Build Deployment Package.

#3 Notice there is a Publish-Interactive.ps1 file in the package folder

When the package is built (by default it is written to the obj\{Configuration}\Package\ folder. In that folder you will now see a Publish-Interactive.ps1 file.

image

#3 Execute the Publish-Interactive.ps1 file from the package folder

Now that we have everything setup let’s start publishing! First let’s take a look at the project which I am publishing, here is a view of the relevant portions of the project.

SNAGHTML7bc0ff

In this image you can see that I have 4 web.config transforms (web.debug.config,web.release.config,web.Prod.config and web.Test.config). I didn’t add any new build configurations to create the Test and Prod files. I just create them and added them to the project. Now open a PowerShell prompt and cd to the package folder. From there execute the command .\Publish-Interactive.ps1. Once you start it you will see that a file copy progress indicator starts. I’m extracting the package to a temp folder. The first thing that the script will do is to prompt you for the web.config transform to be executed.

image

In the image above you can see that it detected all 4 web.config transforms. At this point you just type in the name of the transform which you want to apply. I will pick Release here for this demo. After that some magic will happen and you will start to be prompted for some values. It will prompt you for Web Deploy endpoint info and for any parameters which exist in the package.

image

The first 5 prompts will always be the same, those are the Web Deploy endpoint values. Notice the whatif here, if you want to simulate a publish then for whatif pass in the value true. This will cause Web Deploy to go into a simulation mode, and it will tell you everything that it would do, but no changes will be made.

After the first 5 values you will be prompted for all the parameters which exist in the package. These usually have default values (you can see default values printed in cyan) so if you want to go with the defaults just hit the enter key.

After you enter all the parameter values you will see the message shown below.

image

You may not want to enter all these values everytime so to help with that when you invoke Publish-Interactive.ps1 it will write a file, PublishConfiguration.ps1.readme, which has all the parameter values (except password, you have to fill that one in).

image

If you want that file to be picked up, just remove the .readme from the extension and the next time you invoke Publish-Interactive.ps1 it will pick up the PublishConfiguration.ps1 file and not annoy you!

Currently I’m listing this project as Alpha quality, I will be able to promote it to beta/RTM only with your help. Please download it and try it out. Send the feedback to me either here or you can create an issue on the project’s issue page.

Note: As I stated previously this is not a Microsoft sponsored project, it is a personal project.

Sayed Ibrahim Hashimi @SayedIHashimi

Note: I work for Microsoft, but these are my own opinions

Saturday, December 24, 2011 5:04:44 AM (GMT Standard Time, UTC+00:00)  #     | 
Monday, December 12, 2011

SlowCheetah XML transforms from a CI server

Please read my updated post, the info below is no longer required for some scenarios SlowCheetah build server support updated

A few months ago I published a Visual Studio add-in, SlowCheetah – XML Transforms, which enables you to transform any XML file during a build in the same way that you can transform the web.config during publish/package for web projects. Since then I’ve received numerous requests to demonstrate how the transforms can be kicked in from a CI server. It’s actually pretty easy because all the logic from a transform perspective is contained inside of MSBuild tasks/targets. The Visual Studio add-in itself is only delivering the support for gestures/preview.

Before I dive into how to enable this for a CI server let me explain how the plugin works and then the CI integration will be come much clearer. When you load a project/solution in Visual Studio for the first time after you installed SlowCheetah a set of files will be written to %localappdata%\Microsoft\MSBuild\SlowCheetah\v1. Those file are:

 

Name

Description

Install-Manifest.xml An XML file which describes the files that are installed. This is used by the plugin itself, you should never have to do anything with this file.
SlowCheetah.Tasks.dll The assembly which contains the MSBuild task which transforms XML files.
SlowCheetah.Transforms.targets The MSBuild file which enables XML file transformation

 

The add-in adds the following menu command to any XML file for supported project types.

image

When you click on this for the first time the add-in needs to make some changes to your project file so you are prompted to accept that. After you do the following changes are applied to your project file.

  1. A new MSBuild property SlowCheetahTargets is added to the project and it points to the SlowCheetah.transforms.targets file in %localappdata%
  2. An import for that file is added

More specifically the elements added to your project file are.

<PropertyGroup>
  <SlowCheetahTargets Condition=" '$(SlowCheetahTargets)'=='' ">$(LOCALAPPDATA)\Microsoft\MSBuild\SlowCheetah\v1\SlowCheetah.Transforms.targets</SlowCheetahTargets>
</PropertyGroup>

<Import Project="$(SlowCheetahTargets)" Condition="Exists('$(SlowCheetahTargets)')" />

Note: If you look at your project file they will not be next to each other. The property will be towards the top of the project file and the import towards the bottom.

So now you might have an idea of why this won’t work on your CI server, the file SlowCheetah.Transforms.targets will not exist in the %localappdata% folder. There is an easy to way to fix this which doesn’t require any changes to your CI server. Check in the files and update your import. Here is what I did:

  1. Create a folder named SlowCheetah in the same folder as my solution
  2. Added SlowCheetah.Tasks.dll and SlowCheetah.Transforms.targets to that folder
  3. Create a solution folder named SlowCheetah in the solution
  4. Drag and drop the files from your SlowCheetah folder into the SolutionFolder
  5. Updated my project file to point to these files

Now your solution should look roughly like the following image.

image

Note: Steps #3/#4 are not strictly required

For #5 you’ll have to edit your project file, don’t worry its an easy modification. Just follow these steps:

  1. Right click on the project and select unload
  2. Right click on the unloaded project and select Edit
  3. Update the value for the SlowCheetahTargets import

In my case the folder that I placed the files into is 1 directory above the project itself. So the new value for the property is as follows.

<SlowCheetahTargets>$(MSBuildProjectDirectory)\..\SlowCheetah\SlowCheetah.Transforms.targets</SlowCheetahTargets>

After that I checked in all the files, kicked off a build and viola the files are transformed. Let me know if you need any more info.

Sayed Ibrahim Hashimi – @SayedIHashimi

Note: I work for Microsoft, but these are my own opinions

msbuild | SlowCheetah | Visual Studio | Visual Studio 2010 Monday, December 12, 2011 5:02:42 AM (GMT Standard Time, UTC+00:00)  #     | 
Wednesday, November 16, 2011

Publish web + database incrementally

I get a lot of questions regarding how to publish your web plus your database incrementally. The users of Database projects in Visual Studio 2010 are especially curious about this and how we can integrate Web Deploy with VSDDcmd.exe. I’ve put together a sample which shows how you can use Web Deploy to deploy your web content and VSDBcmd.exe to incrementally publish your database, and the sample also shows how to do this from Team Build.

I am working on creating some more formal documentation for this, but I thought you guys might find this useful before we are able to formalize it. At the end of the post you will the link to download the sample. What you are going to find below is an email which I sent to a tech writer who will be creating the formalized documentation.

 

What you are going to find here is a bit different from other references which you may have seen but I think that this solution is going to work nicely for enterprise customers.

Here were my guiding principles when creating this:

Let me break down the assets quickly:

Asset name

Description

ContactManager-WCF.sln

The solution file which will be built both locally and from Team Build

Publish\Publish.proj

The MSBuild project file which contains the logic to publish

Publish\EnvConfig\Env-Dev.proj

The MSBuild project file which contains the properties for the Dev environment.

Publish\Publish-Dev.cmd

A script file which developers can double click to invoke a deploy. It just calls out to MSBuild with the correct properties set.

Publish\Reset-Local.cmd

You can ignore this, it’s just a script I was using to reset my target env so that I could test publishing.

Publish\Reset-Database.sql

Same as previous, you can ignore it just used for my own testing.

Pubish\Out

The folder where the build output will be placed when building locally.

Here is what happens:

Publish.proj has a set of targets which perform the deployment. If you want to publish locally you will not use Visual Studio (because if you did you wouldn’t be able to publish all projects at the same time), instead the developer will simply double click the Publish\Publish-Dev.cmd file. When it is executed locally the Publish.proj file will build the solution and then publish all the projects.

At the very top of that file you will find the following Import statement

<Import Project="$(TargetEnvPropsFile)"/>

This import will use the property TargetEnvPropsFile to import the environment specific properties. This gives the hook to easily define environment specific properties. In my example case I have placed those properties in the file Publish\EnvConfig\Env-Dev.proj. When publishing when I call msbuild.exe I specify the property /p:TargetEnvPropsFile=EnvConfig\Env-Dev.proj which will import all the properties from that file. You can imagine that we can create different files for different environments and we just specify a different property value and the rest of the process stays the same.

For environments which devs don’t have access to admins can create these property files and just pass a different value for TargetEnvPropsFile.

After the solution is built the Publish.proj file will perform the following:

  1. Publish the database using vsdbcmd.exe and pass in the .deploymanifest file which was generated from the database project
  2. Update the SetParameters.xml file to contain the env specific values for both web projects
  3. Execute the deploy.cmd file for both web projects to perform the publish

Note: I had to create a parameters.xml file for the ContactManager.Mvc project in order to enable me to easily update the WCF endpoint value for the service.

Team Build

In Team Build here is what we are going to do:

In the Team Build situation there are these customizations that need to be made to the Publish.proj file:

 

01

Note: You can also specify the property /p:Whatif=true if you want to simulate a publish (MSDeploy updates are spewed to the command line, and DB operations written to a .sql file).

You can download the samples below, please send me any questions/concerns with this approach.

https://github.com/sayedihashimi/sayed-samples/raw/master/ContactManager-publish-example/ContactManager-publish-example-v01.zip

You can find the latest sources for this at my github repository: https://github.com/sayedihashimi/sayed-samples/tree/master/ContactManager-publish-example

Sayed Ibrahim Hashimi – @SayedIHashimi

Wednesday, November 16, 2011 10:32:56 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)  #     | 

Setting Folder Permissions on Web Publish

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

As part of the deployment process you might need to set permissions on one or more folders in the destination web application. For example, if your application allows users to upload files, it needs write access to a folder in order to store the uploaded files. By default, theweb publishing pipeline (the automated deployment process) automatically sets write permission on the App_Data folder in order to enable database updates in case you store databases in that folder, and you can use the same method to make the deployment process automatically set permissions on any folder you choose.

The <projectname>.wpp.targets File

The web publishing pipeline is controlled by MSBuild .targets files that are installed with Visual Studio. Rather than editing the Visual Studio .targets files, you can create a project-specific .targets file that Visual Studio will use to modify the deployment process for a specific project. To do that, you create an XML file in the project folder and name it < projectname>.wpp.targets. The following example shows XML markup that causes the web publishing pipeline to set write permissions for a folder named Elmah located in the root folder of the application. The sample is designed to work for both deployment methods: publish or web deployment package.

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

  <Target Name="SetupCustomAcls" AfterTargets="AddIisSettingAndFileContentsToSourceManifest">   
    <ItemGroup>
      <MsDeploySourceManifest Include="setAcl">
        <Path>$(_MSDeployDirPath_FullPath)\Elmah</Path>
        <setAclAccess>Read,Write</setAclAccess>
        <setAclResourceType>Directory</setAclResourceType>
        <AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
      </MsDeploySourceManifest>
    </ItemGroup>
  </Target>

  <Target Name="DeclareCustomParameters" AfterTargets="AddIisAndContentDeclareParametersItems">
    <ItemGroup>
      <MsDeployDeclareParameters Include="ElmahSetAclParam">
        <Kind>ProviderPath</Kind>
        <Scope>setAcl</Scope>
        <Match>^$(_EscapeRegEx_MSDeployDirPath)\\Elmah$</Match>
        <Description>Add write permission to the Elmah folder.</Description>
        <DefaultValue>{$(_MsDeployParameterNameForContentPath)}/Elmah</DefaultValue>
        <Value>$(_DestinationContentPath)/Elmah</Value>
        <Tags>Hidden</Tags>
        <Priority>$(VsSetAclPriority)</Priority>
        <ExcludeFromSetParameter>True</ExcludeFromSetParameter>
      </MsDeployDeclareParameters>
    </ItemGroup>
  </Target>
  
</Project>

The first Target element ("SetupCustomAcls") causes a setAcl action to be added to the deployment package's source manifest when the package is created. The source manifest is a file that specifies what to include in the deployment package. The path to the folder is specified using an MSBuild variable (_MSDeployDirPath_FullPath) that specifies the path to the project root folder:

<?xml version="1.0" encoding="utf-8"?>
<sitemanifest>  <IisApp path="C:\ContosoUniversity\ContosoUniversity\obj\Release\Package\PackageTmp" managedRuntimeVersion="v4.0" />
  <setAcl path="C:\ContosoUniversity\ContosoUniversity\obj\Release\Package\PackageTmp" setAclResourceType="Directory" />
  <setAcl path="C:\ContosoUniversity\ContosoUniversity\obj\Release\Package\PackageTmp" setAclUser="anonymousAuthenticationUser" setAclResourceType="Directory" />
  <setAcl path="C:\ContosoUniversity\ContosoUniversity\obj\Release\Package\PackageTmp\App_Data" setAclResourceType="Directory" setAclAccess="Write" />
  <setAcl path="C:\ContosoUniversity\ContosoUniversity\obj\Release\Package\PackageTmp\Elmah" setAclResourceType="Directory" setAclAccess="Read,Write" />
</sitemanifest>

This isn't all you need to do, however, because the permissions actually need to be set on the folder in the destination application, not the folder in the deployment package. That's why the second Target element is needed. The second Target element ("DeclareCustomParameters") creates a user-defined Web Deploy parameter named ElmahSetAclParam. The value of this parameter will change the value of the setAcl path at deploy time. TheDefaultValue element of the ElmahSetAclParam parameter sets the value that will be used when you are deploying by creating and installing a deployment package; the Value element sets the value that will be used when you are publishing without using a deployment package.

If you are using a deployment package, the actual path to the destination folder isn't known until the package is installed. Therefore the default value of this parameter is set using an MSDeploy parameter that resolves to the name of a system-defined Web Deploy parameter:

<DefaultValue>{$(_MsDeployParameterNameForContentPath)}/Elmah</DefaultValue>

When the package is created, _MsDeployParameterNameForContentPath is translated into "{IIS Web Application Name}", so the actual default value becomes "{IIS Web Application Name}/Elmah". When the package is installed, the value of that Web Deploy parameter is the path of the destination root folder, and so as a result the ElmahSetAclParam parameter resolves to the physical path of the destination Elmah folder (for example: C:\inetpub\wwwroot\ContosoUniversity\Elmah). That path then replaces the package's physical path for the Elmah setAcl element in the source manifest.

When you publish the project rather than using a deployment package, the Value element instead of the DefaultValue element provides the value for the ElmahSetAclParam parameter. In that case, you are deploying from Visual Studio, and Visual Studio has the actual destination path value in an MSBuild property, so the Value element can specify that MSBuild property directly and does not need to use a Web Deploy variable:

<Value>$(_DestinationContentPath)/Elmah</Value>

The "Hidden" value in the Tags element prevents the ElmahSetAclParam parameter from being displayed in the IIS Manager UI if you use IIS Manager to install the package. The UI doesn't have to show the value, because a user never needs to change it manually.

<Tags>Hidden</Tags>

Adding More Folders

You can add more folders by adding ItemGroup elements. The following example builds on the earlier one by also granting Read permission for the bin folder to the NETWORK SERVICE account. (This is required in some cases for SQL Server Compact.) Notice that in order to specify the account to which access is granted, "setAclUser" was added to the < AdditionalProviderSettings> element, and a <SetAclUser> element specifying NETWORK SERVICE was added.

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

    <Target Name="SetupCustomAcls" AfterTargets="AddIisSettingAndFileContentsToSourceManifest">
        <ItemGroup>
            <MsDeploySourceManifest Include="setAcl">
                <Path>$(_MSDeployDirPath_FullPath)\Elmah</Path>
                <setAclAccess>Read,Write</setAclAccess>
                <setAclResourceType>Directory</setAclResourceType>
                <AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
            </MsDeploySourceManifest>
            <MsDeploySourceManifest Include="setAcl">
                <Path>$(_MSDeployDirPath_FullPath)\bin</Path>
                <setAclUser>NETWORK SERVICE</setAclUser>
                <setAclAccess>Read</setAclAccess>
                <setAclResourceType>Directory</setAclResourceType>
                <AdditionalProviderSettings>setAclUser;setAclResourceType;setAclAccess</AdditionalProviderSettings>
            </MsDeploySourceManifest>
        </ItemGroup>
    </Target>

    <Target Name="DeclareCustomParameters" AfterTargets="AddIisAndContentDeclareParametersItems">
        <ItemGroup>
            <MsDeployDeclareParameters Include="ElmahSetAclParam">
                <Kind>ProviderPath</Kind>
                <Scope>setAcl</Scope>
                <Match>^$(_EscapeRegEx_MSDeployDirPath)\\Elmah$</Match>
                <Description>Add write permission to the Elmah folder.</Description>
                <DefaultValue>{$(_MsDeployParameterNameForContentPath)}/Elmah</DefaultValue>
                <Value>$(_DestinationContentPath)/Elmah</Value>
                <Tags>Hidden</Tags>
                <Priority>$(VsSetAclPriority)</Priority>
                <ExcludeFromSetParameter>True</ExcludeFromSetParameter>
            </MsDeployDeclareParameters>
            <MsDeployDeclareParameters Include="BinSetAclParam">
                <Kind>ProviderPath</Kind>
                <Scope>setAcl</Scope>
                <Match>^$(_EscapeRegEx_MSDeployDirPath)\\Bin$</Match>
                <Description>Add read permission to the bin folder.</Description>
                <DefaultValue>{$(_MsDeployParameterNameForContentPath)}/bin</DefaultValue>
                <Value>$(_DestinationContentPath)/bin</Value>
                <Tags>Hidden</Tags>
                <Priority>$(VsSetAclPriority)</Priority>
                <ExcludeFromSetParameter>True</ExcludeFromSetParameter>
            </MsDeployDeclareParameters>
        </ItemGroup>
    </Target>
    
</Project>

.targets File Caching in Visual Studio

Setting up .targets files is like writing code -- rarely do you get everything right the first time. If you do make a mistake that you have to fix, you should be aware that Visual Studio caches target files. This means that every time you change a .targets file, you must exit Visual Studio and restart it before you rebuild the package in order to see the effect of your change. This is not an issue if you create packages using the command line.

Making Changes that Affect Parameters in Packages

Visual Studio will not rebuild a package if it cannot determine that a change has been made, and changes that affect Web Deploy parameters are sometimes not recognized as changes. Therefore, if you make any changes that affect parameters in the source manifest, it's best to select Clean Solution from the Build menu in order to delete the contents of the Package folder before you rebuild the package.

Sayed Ibrahim Hashimi – @SayedIHashimi

Tuesday, November 08, 2011 4:49:54 AM (GMT Standard Time, UTC+00:00)  #     |