- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Sunday, April 22, 2012

Visual Studio 11 Web Publish database section

If you have tried out the web publish feature in Visual Studio then you may have noticed the message seen in the image towards the end of the post in the database section.

We did not have time for VS 11 beta to light up these features, but if you want a sneak peak of the behavior then take a look at the channel 9 video I posted recently Dirt Simple Web and Database Deployment in Visual Studio 11. If you have any questions/feedback feel free to send me an email: sayedha [at] {Microsoft}dotCOM.

image

Thanks,

Sayed Ibrahim Hashimi @SayedIHashimi

Sunday, April 22, 2012 9:15:30 PM (GMT Daylight Time, UTC+01:00)  #     | 

Video about upcoming Visual Studio 11 web publish updates

Recently I recorded a video on Channel 9 with Brady Gaster Dirt Simple Web and Database Deployment in Visual Studio 11. In this video I show the work that we have done to enable Entity Framework Code First migrations during publish and I also cover the investments that we are making regarding incremental database schema publish when you are not using EF Code First. If you get a chance I’d love for you to watch the video and give feedback regarding the direction that we are taking with VS Web Publishing.

Thanks,
Sayed Ibrahim Hashimi @SayedIHashimi

Sunday, April 22, 2012 8:15:40 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)  #     | 
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)  #     |