Sunday, January 08, 2012

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

Sunday, January 08, 2012 8:44:39 PM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Tuesday, November 08, 2011

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:

  • By default, deployment packages are created as .zip files. You don't need to change this setting.
  • By default, deployment packages are created in the project's obj\Test\Package folder. You don't need to change this setting.
  • The default IIS web application name is the name of the project with "_deploy" appended to it. Remove that suffix. You want the application to be named just ContosoUniversity in IIS on your computer.
  • For this tutorial you're not deploying IIS settings, so you don't need to enter a password for that.

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

Tuesday, November 08, 2011 5:11:43 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Friday, February 25, 2011

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

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



  

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

    
    
    

    
    
    
  

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

1. Gather files to be compressed

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

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

2. Compress files

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

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

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

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

image

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

Sayed Ibrahim Hashimi

Resources
Friday, February 25, 2011 5:21:44 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Saturday, January 08, 2011

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

  • web.conig (XDT) transforms
  • How to publish to local file system using Visual Studio
  • How to publish to a 3rd party host using Visual Studio via MSDeploy
  • How to publish to local IIS server using the .cmd file generated by Visual Studio
  • How to use msdeploy.exe to delete IIS applications
  • How to use the IIS Manager to import web packages
  • How to use msdeploy.exe to deploy a web package to the local IIS server
  • How to use msdeploy.exe to deploy a web package to a remove IIS server
  • How to use msdeploy.exe to deploy a web package & set parameters using SetParameters.xml to a remote IIS server

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

Sayed Ibrahim Hashimi @sayedihashimi

Saturday, January 08, 2011 8:34:08 PM (GMT Standard Time, UTC+00:00)  #    Comments [3]  | 
Thursday, November 18, 2010

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

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

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

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

  • app.confog
    • app.debug.config
    • app.release.config
  • Sample01.xml
    • Sample01.debug.xml
    • Sample01.release.xml
  • Sub\Sub\Sub01.xml
    • Sub\Sub\Sub01.debug.xml
    • Sub\Sub\Sub01.release.xml

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

image

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

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

Usage

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

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

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

<None Include="app.config" />

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

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

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

Nest transforms under the source file

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

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

Implementation

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

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

Here if the full code for this file.

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

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

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

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

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

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

Gaps

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

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

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

Sharing with team members

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

 

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

Resources

Sayed Ibrahim Hashimi @sayedihashimi

Thursday, November 18, 2010 5:41:09 AM (GMT Standard Time, UTC+00:00)  #    Comments [2]  | 
Thursday, November 11, 2010

Today I just saw a question posted on stackoverflow.com asking Why are some Web.config transforms tokenised into SetParameters.xml and others are not? Let me give some background on this topic for those who are not aware of what the question is.

With Visual Studio 2010 when you package your application using the Build Deployment Package context menu option, see image below.

image

When build the package by default the package will be created in obj\{Configuration}\Package\{ProjectName}.zip where {Configuration} is the current build configuration, and {ProjectName} is the name of the project. So in this case I since I’m building with Debug and the project name is MvcApplication1 the package will be placed at obj\Debug\Package\MvcApplication1.zip. If you take this package and then import into IIS 7 with the “Import Application” option shown below. Note: The machine must have the Web Deployment Tool (aka MSDeploy) installed.

image

Once you click on Import Application then browse out to the package you will be shown a screen which prompts your for parameters. Its shown below.

SNAGHTML2d2664

On this screen you can see that we are prompting for a couple parameter values here. One is an IIS setting, Application Path, and the other is a connection string which will be placed inside the web.config file. If your Web Application Project (WAP)  had 5 different connection strings then they would automatically show up here on this page. Since connection strings are replaced so often we create parameters for all connection strings by default. You can define new parameters on your own, quite easily actually, but that is the topic for another blog post.

Now back to the question. He is asking why do we “tokenize” the connection strings in web.config. To clarify take a look at my web.config file below.

<configuration>
  <appSettings>
    <add key="setting01" value="value01"/>
  </appSettings>
  
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
  
</configuration>

After I perform a package this will get changed. Take a look @ the web.config file which resides in the package (you can get to the file at obj\{CofigurationName}\Package\PackageTmp\web.config). You will see what is shown below.

<configuration>
  <appSettings>
    <add key="setting01" value="value01"/>
  </appSettings>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="$(ReplacableToken_ApplicationServices-Web.config Connection String_0)"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

</configuration>

So his question is why is the connection string replaced with $(ReplacableToken_ApplicationServices-Web.config Connection String_0) and nothing else is? We do this because we do not want you to accidently copy your web to a location and have it executing SQL statements against a SQL server which you did not intend. The idea is that you will create a package that you can deploy to many different environments. So the value that was in your web.config (or web.debug.config/web.release.config if you are using a web.config transformation) will not be placed inside the web.config in the package. Instead those values will be used as defaults in the package itself. We also create a SetParameters.xml file for you so that you can tweak the values. For my app see the MvcApplication1.SetParameters.xml file below.

<?xml version="1.0" encoding="utf-8"?>
<parameters>
  <setParameter name="IIS Web Application Name" 
                value="Default Web Site/MvcApplication1_deploy" />
  <setParameter name="ApplicationServices-Web.config Connection String" 
                value="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" />
</parameters>

The idea is that you can deploy your package in 2 ways. Through the IIS Manager which will prompt you for the parameters or you can deploy using msdeploy.exe with the –setParamFile switch to specify the path to the SetParameters.xml file. In this case I could create a QA01.SetParameters.xml file along with a QA02.SetParameters.xml file to deploy my web to my two QA servers. How do we do this?

How connection strings are tokenized

You might be wondering how the connection strings are tokenized to begin with. With Visual Studio 2010 we released web.config transformations, which all you to write terse web.config transformations inside of files like web.debug.config/web.release.config. When you package/publish your web these transform files are used to transform your web.config based on what you expressed in the appropriate transform file. We have an MSBuild task TransformXml which performs the transformation. We use that same task to tokenize the connection strings. If you are interested in the details take a look at %ProgramFiles32%\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets in the AutoParameterizationWebConfigConnectionStringsCore target.

Now what if you do not want the connection string tokenized?

Prevent tokenizing connection strings

If you want to prevent your web.config connection strings from being tokenized it’s pretty easy. All we need to do is the add a property to the build/package/publish process. We can do that in 2 ways. Edit the project file itself or create a file with the name {ProjectName}.wpp.targets where {ProjectName} is the name of your project. The second approach is easier so I use that. In my case it would be MvcApplication1.wpp.targets. The contents of the file are shown below.

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

  <PropertyGroup>
    <AutoParameterizationWebConfigConnectionStrings>false</AutoParameterizationWebConfigConnectionStrings>
  </PropertyGroup>
  
</Project>

Note: You may need to reload the project in Visual Studio for this to take effect.

Inside of this file I have declared the property, AutoParameterizationWebConfigConnectionStrings, to be false. This is telling the Web Publishing Pipeline (WPP) that it should not replace replace the connection strings with tokens, instead leave them as they are.

Questions/Comments???

Other Resources

Thursday, November 11, 2010 5:41:09 AM (GMT Standard Time, UTC+00:00)  #    Comments [2]  | 
Thursday, November 04, 2010

I just saw a post on twitter asking the question

Is there any easy way to see the underlying MSBuild command when building in VS2010? Want to see the MSDeploy params. @wdeploy?

This is actually pretty easy, but wouldn’t fit into 140 characters, so I decided to blog it.

One thing to know is that when you publish from Visual Studio, by default we use the MSDeploy (AKA Web Deployment Tool) Object Model in order to perform the deployment. We do this for performance and other reasons. Because of this there is no real msdeploy.exe command that is being issued. You can however change that behavior. This is controlled by an MSBuild property UseMSDeployExe which is false by default. In this case since Troy wants to see the command we will need to set that property to false. There are 2 ways in which you can do this. You can set it in the project file itself, or you can define it in a .wpp.targets file. I would recommend the second approach. What you need to do is to create a file with the name {ProjectName}.wpp.targets in the same directory as the project where {ProjectName} is the name of the Web Application Project (WAP). When you do this, during a build or publish the file is automatically imported into the build process. In my example I have a WAP named WebApplication1.csproj, so I created the file WebApplication1.wpp.targets and its contents are shown below.

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

In the snippet above you can see that I defined the property to true. Now there is one more thing to do, publish the project. Once you publish the project, in the output window you will see the MSDeploy command which is being used. In my case I published the project to localhost to the Default Web Site/Test01 application path. You may have to copy the text from the output window into Notepad and search for msdeploy.exe. The command that was issued in my case is shown below (with formatting changes for readability).

"C:\Program Files (x86)\IIS\Microsoft Web Deploy\msdeploy.exe" 
-source:manifest='C:\temp\_NET\ThrowAway\WebApplication3\WebApplication1\obj\Debug\Package\WebApplication1.SourceManifest.xml' 
-dest:auto,IncludeAcls='False',AuthType='NTLM' 
-verb:sync 
-enableRule:DoNotDeleteRule 
-disableLink:AppPoolExtension 
-disableLink:ContentExtension 
-disableLink:CertificateExtension 
-setParam:kind='ProviderPath',
    scope='IisApp',match='^C:\\temp\\_NET\\ThrowAway\\WebApplication3\\WebApplication1\\obj\\Debug\\Package\\PackageTmp$',
    value='Default Web Site/Test01' 
-setParam:kind='ProviderPath',
    scope='setAcl',
    match='^C:\\temp\\_NET\\ThrowAway\\WebApplication3\\WebApplication1\\obj\\Debug\\Package\\PackageTmp$',
    value='Default Web Site/Test01' 
-retryAttempts=2 

So that’s it, pretty simple.

FYI, if you want more detail you can increase the MSBuild Output Window verbosity by going to Tools->Options->Projects and Solutions->Build and Run then specifying a different value for MSBuild project build output verbosity.

Sayed Ibrahim Hashimi - @sayedihashimi

Thursday, November 04, 2010 4:03:26 AM (GMT Standard Time, UTC+00:00)  #    Comments [3]  | 
Thursday, October 21, 2010

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

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

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

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

Any idea?

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

web.config

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

web.debug.config

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

web.release.config

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

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

Resolution

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

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

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

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

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

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

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

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

Sayed Ibrahim Hashimi - @sayedihashimi

Thursday, October 21, 2010 7:13:26 AM (GMT Daylight Time, UTC+01:00)  #    Comments [4]  | 
Thursday, September 09, 2010

Last week on StackOverflow I answered a question, Make web.config transformations working locally and in a response to my answer the question asker asked me if I would be able to a question he posed earlier Advanced tasks using web.config transformation. Evidently he is really interested in config transformations! I don’t blame him, I’m really into them as well.

In his question he asks (summarizing) can we replace portions of attribute values instead of this entire attribute? So for instance you have the following in your web.config. Below is two sets of appSettings one from Dev and the other from Prod (taken from the original question).

<!-- DEV ENTRY -->
<appSettings>
 <add key="serviceName1_WebsService_Url" value="http://wsServiceName1.dev.domain.com/v1.2.3.4/entryPoint.asmx" />
 <add key="serviceName2_WebsService_Url" value="http://ma1-lab.lab1.domain.com/v1.2.3.4/entryPoint.asmx" />
</appSettings>

<!-- PROD ENTRY -->
<appSettings>
 <add key="serviceName1_WebsService_Url" value="http://wsServiceName1.prod.domain.com/v1.2.3.4/entryPoint.asmx" />
 <add key="serviceName2_WebsService_Url" value="http://ws.ServiceName2.domain.com/v1.2.3.4/entryPoint.asmx" />
</appSettings>

In the above we just want to replace dev with prod and ma1-lab.lab1.domain with ws.ServiceName2.domain. For those wondering currently we have the following transformations out of the box.

  • Replace – Replaces the entire element
  • Remove – Removes the entire element
  • RemoveAll – Removes all matching elements
  • Insert – Inserts an element
  • SetAttributes – Sets the value of the specified attributes
  • RemoveAttributes – Removes attributes
  • InsertAfter – Inserts an element after another
  • InsertBefore – Inserts an element before another

At the end of this article I’ve linked to another blog which has more info about these transformations. So it sounds like SetAttributes is almost what we want, but not quite what there. A little known fact is that you can create your own config transformations and use those. In fact all of the out of the box transformations follow the same patterns that custom transformations would. To solve this issue we need to create our own config transformation, AttributeRegexReplace. This transformation will take an attribute value and do a regular expression replace on its value. In order to create a new transformation you first reference the Microsoft.Web.Publishing.Tasks.dll which can be found in the %Program Files (x86)%MSBuild\Microsoft\VisualStudio\v10.0\Web folder. If you are working with a team it is best if you copy that assembly, place it in a shared folder in source control, and make the reference from that location. After you create the reference to that assembly you will need to create a class which extends the Transform class. The class diagram for this abstract class is shown below.

image

The only thing that you will need to implement is the Apply method. You don’t even need to fully understand all of the properties and methods just the portions that you are interested in. Here we will not cover all the details of this class, or other related classes which exist, but there will be future posts which will shed more light on this area.

In the sample class library that I created, I called the project CustomTransformType. Inside of that project I created the class AttributeRegexReplace. The entire contents of that class are shown below, we will go over the details after that.

namespace CustomTransformType
{
    using System;
    using System.Text.RegularExpressions;
    using System.Xml;
    using Microsoft.Web.Publishing.Tasks;

    public class AttributeRegexReplace : Transform
    {
        private string pattern;
        private string replacement;
        private string attributeName;

        protected string AttributeName
        {
            get
            {
                if (this.attributeName == null)
                {
                    this.attributeName = this.GetArgumentValue("Attribute");
                }
                return this.attributeName;
            }
        }
        protected string Pattern
        {
            get
            {
                if (this.pattern == null)
                {
                    this.pattern = this.GetArgumentValue("Pattern");
                }

                return pattern;
            }
        }

        protected string Replacement
        {
            get
            {
                if (this.replacement == null)
                {
                    this.replacement = this.GetArgumentValue("Replacement");
                }

                return replacement;
            }
        }

        protected string GetArgumentValue(string name)
        {
            // this extracts a value from the arguments provided
            if (string.IsNullOrWhiteSpace(name)) 
            { throw new ArgumentNullException("name"); }

            string result = null;
            if (this.Arguments != null && this.Arguments.Count > 0)
            {
                foreach (string arg in this.Arguments)
                {
                    if (!string.IsNullOrWhiteSpace(arg))
                    {
                        string trimmedArg = arg.Trim();
                        if (trimmedArg.ToUpperInvariant().StartsWith(name.ToUpperInvariant()))
                        {
                            int start = arg.IndexOf('\'');
                            int last = arg.LastIndexOf('\'');
                            if (start <= 0 || last <= 0 || last <= 0)
                            {
                                throw new ArgumentException("Expected two ['] characters");
                            }

                            string value = trimmedArg.Substring(start, last - start);
                            if (value != null)
                            {
                                // remove any leading or trailing '
                                value = value.Trim().TrimStart('\'').TrimStart('\'');
                            }
                            result = value;
                        }
                    }
                }
            }
            return result;
        }

        protected override void Apply()
        {
            foreach (XmlAttribute att in this.TargetNode.Attributes)
            {
                if (string.Compare(att.Name, this.AttributeName, StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    // get current value, perform the Regex
                    att.Value = Regex.Replace(att.Value, this.Pattern, this.Replacement);
                }
            }
        }
    }
}

In this class we have 3 properties; Pattern, Replacement, and AttributeName. All of these values will be provided via an argument in the config transformation. For example take a look at the element below which contains a transform attribute may look like the following.

<add key="two" value="two-replaced" 
         xdt:Transform="AttributeRegexReplace(Attribute='value', Pattern='here',Replacement='REPLACED')" 
         xdt:Locator="Match(key)"/>

In this example I declare that I am using AttributeRegexReplace and then specify the values for the attributes within the (). In the class above I have a method, GetArgumentValue, which is used to parse values from that argument string. When your transform is invoked the string inside of () is passed in as the ArgumentString value. If you are using a , as the argument separator, as I am, then you can use the Arguments list. Which will split up the arguments by the , character. Surprisingly in the 101 lines of code in the sample there are only a few interesting lines. Those are what’s contained inside the Apply method. Inside that method I search the TargetNode’s attributes (TargetNode is the node which was matched in the xml file being transformed) for an attribute with the same name as the one specified in the AttributeName property. Once I find it I just make a call to Regex.Replace to get the new value, and assign it. Pretty simple! Now lets see how can we use this.

Let’s say you have the following very simple web.config

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="one" value="one"/>
    <add key="two" value="partial-replace-here-end"/>
    <add key="three" value="three here"/>
  </appSettings>
</configuration>

If we want to be able to use our own transform then we will have to use the xdt:Import element. You can place that element inside the xml document anywhere immediately under the root element. This element will allow us to utilize our own transform class. It only has 3 possible attributes.

  • namespace – This is the namespace which the transform is contained in
  • path – This is the full path to the assembly
  • assembly – This is the assembly name which contains the transform

You can only use one of the two; path and assembly. Basically it boils down to how the assembly is loaded. If you use path the assembly will be loaded with Assembly.LoadFrom and if you chose to use assembly passing in the AssemblyName, for instance if the assembly in in the GAC, then it will be loaded using Assembly.Load.

I chose to use path, because I just placed the file inside of the MSBuild Extensions directory (%Program Files (x86)%MSBuild) in a folder named Custom. Then I created my config transform file to be the following.

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
                        
  <xdt:Import path="C:\Program Files (x86)\MSBuild\Custom\CustomTransformType.dll"
              namespace="CustomTransformType" />

  <appSettings>
    <add key="one" value="one-replaced" xdt:Transform="Replace" xdt:Locator="Match(key)" />
    <add key="two" value="two-replaced" xdt:Transform="AttributeRegexReplace(Attribute='value', Pattern='here',Replacement='REPLACED')" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

Then to run this I created an MSBuild file, PerformTransform.proj, which is shown below.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="TransformXml"
           AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

  <PropertyGroup>
    <TransformDest>web.tranzed.config</TransformDest>
  </PropertyGroup>
  
  <Target Name="Demo">
    <Delete Files="$(TransformDest)" />
    <TransformXml Source="web.config"
                  Transform="web.dev.config"
                  Destination="$(TransformDest)" />                  
  </Target>
  
</Project>

This file uses the TransformXml task as I outlined in a previous post Config transformations outside of web app builds. Once you execute the Demo target with the command msbuild PerformTransform.proj /t:Demo you will see the file web.tranzed.config with the following contents.

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="one" value="one-replaced"/>
    <add key="two" value="partial-replace-REPLACED-end"/>
    <add key="three" value="three here"/>
  </appSettings>
</configuration>

So you can see that the replacement did occur as we intended. Below you will find the download link for the samples as well as another blog entry for more info on the out of the box transformations.

Resources

Sayed Ibrahim Hashimi

Thursday, September 09, 2010 6:51:43 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Sunday, August 15, 2010

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

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

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

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

image

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

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

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

image

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

image

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

Sayed Ibrahim Hashimi

Sunday, August 15, 2010 7:56:50 PM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Monday, June 07, 2010

If you are doing any kind of web development and you are not familiar with the Web Platform Installer(WPI) then you need to take a look at it. I just installed WordPress on IIS 7 with just a few clicks and  filled in a few text boxes. When you install WordPress there are some prerequisites like mySql and php. The WPI was smart enough to realize that I had neither installed, downloaded those, installed them and configured them. I was prompted for some info for those tools of course. I’ve also installed a few other apps using the WPI like, MSDeploy and dasBlog and I didn’t have any issues what so ever.

When using the WPI there are two main categories that can be installed, Web Platform and Web Applications. The Web Platform category includes items like frameworks (i.e. ASP.NET, PHP), Database (i.e. mySql) and other high level shared components. The Web Applications includes various web applications. Some others that I didn’t list previously include; DotNetNuke, nopCommerce, and umbarco just to name a few. I’m not sure how many apps are available but it looks like at least 50.

If you are an app creator and would like to share your app then you can visit the WPI Developer page for a starting point.

Monday, June 07, 2010 4:17:01 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Saturday, May 01, 2010

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

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

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

Web Publishing Pipeline

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

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

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

Excluding files from being packaged

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

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

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

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

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

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

Including extra files into the package

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

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

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

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

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

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

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

Sayed Ibrahim Hashimi

Saturday, May 01, 2010 4:09:16 AM (GMT Daylight Time, UTC+01:00)  #    Comments [4]  | 
Monday, April 26, 2010

If you are using Visual Studio 2010 then you may already be familiar with the Web.config transformations that are now available. What you might not know is that you can use that same technology to transform config files outside of the build process. You will need Visual Studio 2010 installed on the machine where you perform these transformations. It is very easy to perform these transformation as well. Let’s say that we start with the app.config file shown below.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=localhost;Initial Catalog=Sample01;Integrated Security=True;" />
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@demo.example.com"/>
        <add key="siteUrl" value="http://demo.example.com"/>
    </appSettings>
    
</configuration>

Then we create another file, transform.xml, which contains our transformations. That file is shown below.

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;" 
             xdt:Locator="Match(name)" xdt:Transform="Replace"/>
    </connectionStrings>

    <appSettings>
        <add key="contactEmail" value="contact@example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
        <add key="siteUrl" value="http://example.com" xdt:Locator="Match(key)" xdt:Transform="Replace"/>
    </appSettings>

</configuration>

Then we can easily execute the transformations by using MSBuild. So I created a file named trans.proj and it is shown below.

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="TransformXml"
             AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

    <Target Name="Demo">
        <TransformXml Source="app.config"
                      Transform="Transform.xml"
                      Destination="app.prod.config"/>
    </Target>
</Project>

This MSBuild file uses the TransformXml task which is shipped with Visual Studio 2010. We specify the source file, transform file and the destination. Pretty straight forward.

In order to execute this I open a Visual Studio 2010 command prompt, browse to the directory containing both files, and enter the following command

msbuild trans.proj /t:Demo

Once you do this then you will find the file app.prod.config with the following contents.

<configuration>
    <connectionStrings>
        <clear/>
        <add name="Default" connectionString="Data Source=NOT-localhost;Initial Catalog=Sample01;Integrated Security=True;"/>
    </connectionStrings>
    
    <appSettings>
        <add key="contactEmail" value="contact@example.com"/>
        <add key="siteUrl" value="http://example.com"/>
    </appSettings>
    
</configuration>

Sayed Ibrahim Hashimi

Monday, April 26, 2010 5:22:06 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Thursday, March 11, 2010

Disclaimer: Take what you read here with a grain of salt, I’m not an expert at providers … yet :)

I’ve known for quite a while that the Web Deployment Tool supports custom providers but I’ve never really looked at what it took to get actually write one. Tonight I wanted to write a simple provider to just sync a file from one place to another, just to see what is involved in creating that provider. In this post I describe how I created the provider. First you have to have the Web Deployment Tool installed, I’ve got the RTM version installed, but recently they delivered version 1.1 either should work. First things first, you need to create a class library project in Visual Studio. For this example I used Visual Studio 2010 RC for the reason that it’s the only version of Visual Studio that I have installed on this machine. If you are using Visual Studio 2010 make sure that you specify to build for .NET 3.5 because MSDeploy won’t pickup any providers written in .NET 4.0. To specify that your project should build for .NET 3.5 go to Project->Properties then on the Application tab pick the Target Framework to be .NET 3.5. See the image below for clarification.

targetframework-.net35

You will need to reference the two assemblies Microsoft.Web.Deployment.dll and Microsoft.Web.Delegation.dll. You can find both in the %Program Files%\IIS\Microsoft Web Deploy folder.

After this you need to create the class which is the provider. I called my CustomFileProvider because it will only sync a single file. The class should extend the DeploymentObjectProvider class. There are a couple abstract items that you must implement those are.

CreateKeyAttributeData

From what I can see this method is used to indicate how the “key attribute” is used. For instance when you use a contentPath provider you would use a statement like msdeploy –verb:sync –source:contentPath=C:\one\pathToSync –dest:… So we can see that the value C:\one\pathToSync is passed to the provider without a name. This is the key attribute value. This method for my provider looks like the following.

public override DeploymentObjectAttributeData CreateKeyAttributeData()
{
    DeploymentObjectAttributeData attributeData = new DeploymentObjectAttributeData(
        CustomFileProvider.KeyAttributeName,
        this.FilePath,
        DeploymentObjectAttributeKind.CaseInsensitiveCompare);

    return attributeData;
}

In this case CustomFileProvider.KeyAttributeName is a const whose value is path and its value is provided from the FilePath property. The other item that you have to override is the Name property.

Name

This property returns the name of the provider. In all the samples that I have seen (which is not very much) this name always agrees with the name of the custom provider factory, more on that in a bit. So in their example I had mine return the value customFile which my factory also returns.

Outside of these two items there are some other methods that you need to know about those are covered below.

GetAttributes

The GetAttributes method is kinda interesting. This method will be called on both the source and destination and you need to understand which context its being called in and act accordingly. You can determine if you are executing on the source or dest by using the BaseContext.IsDestinationObject property. So for this provider if you are in the source you want to ensure that the file specified exists, if not then raise a DeploymentFatalExcepton, this will stop the sync. If you are on the destination you could perform some checks to see if the file is up-to-date or not. For a simple provider you can force a sync to occur. You would do this by raising a DeploymentException. When you raise this exception at this time it causes the Add method to be called, which is exactly what we want. Here is my version of the GetAttributes method.

public override void GetAttributes(DeploymentAddAttributeContext addContext)
{
    if (this.BaseContext.IsDestinationObject)
    {
        // if we are on the destination and the file doesn't exist then we need to throw an exception
        // to ensure that the file gets synced. This happens because the Add command will be called for us.

        // Since I'm throwing an exception here Add will always be called, we could check to see if this file
        // was up-to-date and if so then skip this exception.
        throw new DeploymentException();
    }
    else
    {
        // We are acting on the source object here, make sure that the file exists on disk
        if (!File.Exists(this.FilePath))
        {
            string message = string.Format("File <{0}> does not exist",this.FilePath);
            throw new DeploymentFatalException(message);
        }
    }

    base.GetAttributes(addContext);
}

For the most part the only thing left for this simple provider to implement is to override the Add method. First I will show the method then discuss its content. Here is the method.

public override void Add(DeploymentObject source, bool whatIf)
{
    // This is called on the Destination so this.FilePath is the dest path not source path
    if (!whatIf && File.Exists(source.ProviderContext.Path))
    {
        // We can let MSDeploy do the actual sync for us using existig provider
        DeploymentProviderOptions sourceProviderOptions = new DeploymentProviderOptions(DeploymentWellKnownProvider.FilePath);
        sourceProviderOptions.Path = source.ProviderContext.Path;

        using (DeploymentObject sourceObject = DeploymentManager.CreateObject(sourceProviderOptions, new DeploymentBaseOptions()))
        {
            DeploymentProviderOptions destProviderOptions = new DeploymentProviderOptions(DeploymentWellKnownProvider.FilePath);
            destProviderOptions.Path = this.FilePath;

            // Make the call to perform an actual sync
            sourceObject.SyncTo(destProviderOptions, new DeploymentBaseOptions(), new DeploymentSyncOptions());
        }
    }
}

First I check to make sure that we are not doing a whatif run (i.e. a run where we don’t want to physically perform the action) and that the source file exists. Take note of the fact that I’m explicitly using source.ProviderContext.Path to get the source path. This provider has a property, FilePath, which contains the path but it could be either source path or dest path depending on which end you are executing in. the source.ProviderContent.Path will always point to the source value. After that you can see that I’m actually leveraging an existing provider the FilePath provider to do the actual sync for me. So all the dirty work is his job! If you are writing a provider make sure to re-use any existing providers that you can, because the code for this part looks like it can get nasty. I’ll leave that for another post.

After I prepare the source options I create an instance of the DeploymentObject class, prepare the FilePath provider and call SyncTo on the object., this is where the physical sync occurs. That is basically it for the provider itself now we need to create a provider factory class which is the guy who knows how to create our providers for us.

Fortunately creating custom provider factories is even easier then creating custom providers themselves. I called mine CustomFileProviderFactory and the entire class is shown below.

[DeploymentProviderFactory]
public class CustomFileProviderFactory : DeploymentProviderFactory
{
    protected override DeploymentObjectProvider Create(DeploymentProviderContext providerContext, DeploymentBaseContext baseContext)
    {
        return new CustomFileProvider(providerContext, baseContext);
    }

    public override string Description
    {
        get { return @"Custom provider to copy a file"; }
    }

    public override string ExamplePath
    {
        get { return @"c:\somefile.txt"; }
    }

    public override string FriendlyName
    {
        get { return "customFile"; }
    }
    public override string Name
    {
        get { return "customFile"; }
    }
}

A few things to make note of; your class should extend the DeploymentProviderFactory class and it should have the DeploymentProviderFactory attribute attached to it. Besides that there are two properties FriendlyName and Name, once again in all the samples I have seen they are always the same and always equal to the Name property on the provider itself. I followed suit and copied them. I’m still trying to figure out more about what each of these actually do, but for now I’m OK with leaving them to be the same. So that is basically it.

In order to have MSDeploy use the provider you have to create a folder named Extensibility under the %Program Files%\IIS\Microsoft Web Deploy folder if it doesn’t exist, and then copy the assembly into that folder. And then you are good to go. Here is the snippet showing my custom provider in action!

C:\temp\MSDeploy>msdeploy -verb:sync -source:customFile=C:\temp\MSDeploy\Source\source.txt -dest:customFile=C:\temp
\MSDeploy\Dest\one.txt -verbose
Verbose: Performing synchronization pass #1.
Info: Adding MSDeploy.customFile (MSDeploy.customFile).
Info: Adding customFile (C:\temp\MSDeploy\Dest\one.txt).
Verbose: The dependency check 'DependencyCheckInUse' found no issues.
Verbose: The synchronization completed in 1 pass(es).
Total changes: 2 (2 added, 0 deleted, 0 updated, 0 parameters changed, 0 bytes copied)

This was a pretty basic provider, but you have to start somewhere. I will post more about custom providers as I find out more.

You can download the entire source at http://sedotech.com/Resources#CustomProviders under the Custom Providers heading of the MSDeploy section.

Sayed Ibrahim Hashimi

Thursday, March 11, 2010 6:04:47 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Wednesday, March 10, 2010

I just received a message from a reader asking about how he can extend the package process in Visual Studio 2010 RC to include files that his web project doesn't contain or reference. If you are not familiar with this Visual Studio 2010 has support for creating Web Packages now. These packages can be used with the Web Deployment Tool to simply deployments. The Web Deployment Tool is also known as MSDeploy.

He was actually asking about including external dependencies, but in this post I will show how to include some text files which are already written to disk. To extend this to use those dependencies should be pretty easy. Here is what I did:

  1. Created a new ASP.NET MVC 2 Project (because he stated this is what he has)
  2. Added a folder named Extra Files one folder above where the .csproj file is located and put a few files there
  3. In Visual Studio right clicked on the project selected “Unload Project”
  4. In Visual Studio right clicked on the project selected “Edit project”

Then at the bottom of the project file (right above the </Project> statement). I inserted the following XML fragments.

<PropertyGroup>
  <CopyAllFilesToSingleFolderForPackageDependsOn>
    CustomCollectFiles;
    $(CopyAllFilesToSingleFolderForPackageDependsOn);
  </CopyAllFilesToSingleFolderForPackageDependsOn>
</PropertyGroup>
<Target Name="CustomCollectFiles">
  <ItemGroup>
    <_CustomFiles Include="..\Extra Files\**\*">
      <DestinationRelativePath>%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
    </_CustomFiles>

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

Here I do a few things. First I extend the CopyAllFilesToSingleFolderForPackage target by extending its DependsOn property to include my target CustomCollectFiles. This will inject my target at the right time into the Web Publishing Pipeline. Inside that target I need to add my files into the FilesForPackagingFromProject item group, but I must do so in a particular manner. Specifically I have to define the relative path to where it should be written. This captured inside the DestinationRelativePath metadata item. This is required because sometimes you may have a file which is named, or in a different folder, than it was originally. After you do that you will see that the web package that is created when you create a web package from Visual Studio (or from the command line using msbuild.exe for that matter) contains your custom files.

I just posted a blog about my upcoming talk discussing Web Deployments and ASP.NET MVC, once again check it out :)

Sayed Ibrahim Hashimi

Wednesday, March 10, 2010 5:26:14 AM (GMT Standard Time, UTC+00:00)  #    Comments [2]  | 

I will be speaking at the Orlando Code Camp on Saturday March 27. I will be giving two session; one on Simplifying deployments with MSDeploy and Visual Studio 2010 and the other on ASP.NET MVC View Helpers. By the way, the other name for MSDeploy is the Web Deployment Tool.

If you have ever had issues with deploying web applications (which includes everyone who has ever deployed a web app :) ) then you need to attend my session. I will discuss the three major scenarios of deploying web applications:

  • Deploying to a local IIS server
  • Deploying to an IIS server on the intranet
  • Deploying to a 3rd party host

I will be demonstrating how to perform 2 of the 3; deploying to local IIS server and to a 3rd party host. Since I won’t have any other machines besides my notebook I will not be demoing how to deploy to an IIS server on the intranet, but it is very similar to the other 2 scenarios. There has been a lot of work in the area of web deployment (deployment in general actually) recently which could really help spare you of a lot of headache. I presented this at the South Florida Code Camp a couple weeks ago and a person actually stated in the session “There are a lot of people who wish they were in here right now”! If you are in the area then you should attend my session, you won’t regret it.

Here is the abstract:

Visual Studio 2010 will be shipped including integration with Microsoft’s Web Deployment Tool, MSDeploy. For quite a while web deployments have been very difficult to manage and automate. With MSDeploy you can manage the complexities of web deployments. One of the great aspects of the Web Deployment Tool is that it is integrated into Visual Studio with MSBuild tasks and targets. Since Team Foundation Build can leverage MSBuild we can take advantage of those tasks and targets to automate web deployments using Team Build.

My other talk will be on creating leaner views with ASP.NET MVC View Helpers. If you are using ASP.NET MVC then this is one of the sessions you’ll be interested in. I will be getting in depth about ASP.NET View Helpers, and just talking ASP.NET MVC in general. I gave this talk at the Jacksonville Developers User Group last week and it was great. I’m very excited about these two talks, I’m sure they will be great. Here is the abstract.

If you have been using ASP.NETMVC then you certainly have been using some of the built in view helper methods that are available, you know those expressions like Html.TextBox("textBoxName") and Html.ValidationMessage("Required"). View helpers are nothing more than extension methods which create HTML that is injected into your views based on the method and its parameters. Creating your own view helpers is very simple and can be extremely beneficial. By writing your own custom view helpers you will benefit in at least the following ways

  • Simplifies Your Views
  • Eases Re-hydrating HTML Elements with ModelState Values
  • Standardizes the Creation of Common HTML Components
  • Helps you Implement the DRY (Don’t Repeat Yourself) Principal

I have published a 22 page paper discussing custom ASP.NET MVC view helpers along with a sample app at http://mvcviewhelpers.codeplex.com/ if you are interested.

 

If you are in the area this weekend its going to be a great event. I think there were >400 people there last year, so it should be a good turn out this year as well. I hope to see you there.

Sayed Ibrahim Hashimi

Wednesday, March 10, 2010 3:29:22 AM (GMT Standard Time, UTC+00:00)  #    Comments [2]  | 
Monday, December 07, 2009

A while back someone asked me if you could sync 2 or more folders with one statement using MSDeploy. I said of course, if you perform the sync using manifest files. Manifest files allow you to "group" sync operations into a file. When you invoke msdeploy.exe and point it to a manifest file, each provider will be executed in the order in which it appears inside the manifest file. A common scenario for using manifest files is to sync websites. This way you can specify the files that should be synced, the website (application) name, ACL values, etc. But you are not limited to using manifest files for web related sync operations. When using manifest files, you would specify the provider to be manifest. We will see this in the command used to snyc two folders. Often times when using a manifest file for the source you will also use one for the destination. Here are the two files.

SourceManifest.xml

<sitemanifest>

  <contentPath path="C:\temp\MSDeploy\Source01"/>

  <contentPath path="C:\temp\MSDeploy\Source02" />

</sitemanifest>

DestManifest.xml

<sitemanifest>

  <contentPath path="E:\temp\MSDeploy\Source01" />

  <contentPath path="E:\temp\MSDeploy\Source02" />

</sitemanifest>

In this example I am syncing two folders C:\temp\MSDeploy\Source01 and C:\temp\MSDeploy\Source02 to another drive location on E. The command to perform the sync would be

msdeploy -verb:sync -source:manifest=sourceManifest.xml -dest:manifest=destManifest.xml

And here are the results of that sync operation, when the destination directories don't exist.

C:\temp\MSDeploy>msdeploy -verb:sync -source:manifest=sourceManifest.xml -dest:manifest=destManifest.xml

Info: Adding sitemanifest (sitemanifest).

Info: Adding contentPath (E:\temp\MSDeploy\Source01).

Info: Adding dirPath (E:\temp\MSDeploy\Source01).

Info: Adding child filePath (E:\temp\MSDeploy\Source01\01.txt).

Info: Adding child filePath (E:\temp\MSDeploy\Source01\02.txt).

Info: Adding child filePath (E:\temp\MSDeploy\Source01\03.txt).

Info: Adding child filePath (E:\temp\MSDeploy\Source01\04.txt).

Info: Adding contentPath (E:\temp\MSDeploy\Source02).

Info: Adding dirPath (E:\temp\MSDeploy\Source02).

Info: Adding child filePath (E:\temp\MSDeploy\Source02\01.txt).

Info: Adding child filePath (E:\temp\MSDeploy\Source02\02.txt).

Info: Adding child filePath (E:\temp\MSDeploy\Source02\03.txt).

Info: Adding child filePath (E:\temp\MSDeploy\Source02\04.txt).

Total changes: 13 (13 added, 0 deleted, 0 updated, 0 parameters changed, 0 bytes copied)

As you can see the destination directories were created and the files synced into the destination folders. First all the content of the Source01 folder is synced and then the Source02 folder as expected. If you perform the sync operation and all files are up-to-date then no changes will be made.

This is just a very basic example of how you can use MSDeploy manifest files to perform a sync operation, but you can create manifest files that perform many different actions. Visual Studio 2010 uses manifest files when it creates the web packages for deployment.

 

Sayed Ibrahim Hashimi

Monday, December 07, 2009 4:50:37 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Tuesday, October 27, 2009

Last week I presented MSDeploy in a LiveMeeting session. I'm glad to say that the presentation is now available at https://www.livemeeting.com/cc/mvp/view?id=PR7D6Z. If you are interested in getting a quick look at MSDeploy then this is a good place to start. I demonstrate how we can use MSDeploy / VS2010 in the following scenarios.

  • Publish from VS 2010 to third party host
  • Publish from msdeploy.exe to local IIS
  • Publish from msdeploy.exe to remote IIS (within your intranet)

To my knowledge this is the first online presentation of the RTW version of MSDeploy.

You can download the slide deck at http://sedodream.com/content/binary/MSDeploy_Sayed-Ibrahim-Hashimi-2009-10.pdf

 

For reference here are the links from the resources slide

 

I would like to thank Charles Sterling, Vishal Joshi, and Mei Liang for making this happen.

Sayed Ibrahim Hashimi

Tuesday, October 27, 2009 3:21:20 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Friday, October 09, 2009

I will be giving an online LiveMeeting Session hosted by Microsoft next week on Wednesday October 14, 2009 at 4 PDT (Redmond Time). The title is Simplifying Deployment with the Web Deployment Tool (MSDeploy). If you are not aware of MSDeploy it is a newly released tool to ease the pain of deploying ASP.NET sites. If you are doing any type of deployment of ASP.NET sites (Manual or Automated) then you must check out MSDeploy, it will change how you look at deployment of ASP.NET sites all together. Right now there is not an abundant amount of knowledge or material available on this tool, but I think that will change soon. Hopefully I can contribute to some of that. In any case, if you are available I would love to have you check out my session. There will be some guys from Microsoft on the line including the Program Manager of the Web Deployment Tool Vishal Joshi. I'm sure he will chime in when I try to mislead you guys by feeding your mis-information.

Here is the info about the presentation.

Simplifying Deployment with the Web Deployment Tool (MSDeploy)

You are invited to join the talk which is scheduled for

Wednesday, October 14th, 2009 | 4:00pm – 5:00pm (PDT, Redmond time)

Abstract

Deploying ASP.NET Websites has always been a challenge and different teams have used different approaches to overcoming those challenges. Microsoft has offered some support for making deployment easier in the past. For instance they first introduced Web Deployment Projects for Visual Studio 2005, and also have a version for 2008. Web Deployment Projects do greatly simplify the process of calling the aspnet_compiler and aspnet_merge tool but even though their title states "Deployment" they had no support for physically deploying the site. Now Microsoft has introduced the Web Deployment Tool, also known as MSDeploy. MSDeploy will bridge the gap between taking a web site and physically deploying it to its destination. With MSDeploy you can easily and very effectively perform tasks such as pushing an ASP.NET site (Web site, Web Application Project, ASP.NET, etc) from one machine to several other machines. This is achieved by the target machines having the MSDeploy Remote Agent Service installed and running. You can sync two different Web Sites that are hosted in IIS, you can create a web package (simply a .zip file) and use that as your source, you can sync two different folders, and many other options. Another compelling feature of MSDeploy is that it will be integrated into Visual Studio 2010. From Visual Studio 2010 you can compile your ASP.NET Web Application Project and then create the Web Package which contains all your content files plus IIS settings. This one file will full describe your web.

Live Meeting Information

Join the meeting.
Audio Information
Computer Audio
To use computer audio, you need speakers and microphone, or a headset.
Telephone conferencing
Use the information below to connect:
Toll-free: +1 (866) 500-6738
Toll: +1 (203) 480-8000
Participant code: 5460396

Please join 10 minutes prior to the start time.

First Time Users:
To save time before the meeting,
check your system to make sure it is ready to use Microsoft Office Live Meeting.
Notes

Troubleshooting
Unable to join the meeting? Follow these steps:

1. Copy this address and paste it into your web browser:
https://www.livemeeting.com/cc/mvp/join

2. Copy and paste the required information:
Meeting ID: PR7D6Z
Entry Code: A5128ML0Y0D
Location:
https://www.livemeeting.com/cc/mvp

If you still cannot enter the meeting, contact support

Note

Microsoft Office Live Meeting can be used to record meetings. By participating in this meeting, you agree that your communications may be monitored or recorded at any time during the meeting.

 

Sayed Ibrahim Hashimi

 

 

Friday, October 09, 2009 3:43:27 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 

Theme design by Jelle Druyts