14

After seeing the Hanselman "You are doing it wrong" video I start to use the Web Publish feature of VS2010.

What I'm really missing is that the websites sometimes gives errors while the site is publishing because the feature does not copy the app_offline.htm file to the server.

I do not want to start using MSDeploy scripts, because I have several sites and want to keep it simple.

Maybe there is a simple tweak to tell the wizard to copy and then delete the file.

Eduardo Molteni
  • 37,007
  • 23
  • 135
  • 201
  • Heh - this sounds familiar. Did you ever find a solution to this? – James Skemp Sep 22 '10 at 22:58
  • Perhaps Scott is doing it all wrong and doesn't know it. ;-) – Dan Esparza Oct 18 '10 at 15:06
  • When I do a publish from VS2010 it copies the app_offline.htm file just fine and then removes it when it's done. – Chev Nov 25 '10 at 17:23
  • @Chevex: care to comment how you do it? where the file is placed? What options are you using? – Eduardo Molteni Nov 25 '10 at 17:44
  • I don't, it does it for me. Right-click project in solution explorer, then go to publish. I publish via FTP and while it is doing the publish it automatically does the app_offline.htm file. Not sure why yours wouldn't be. – Chev Nov 26 '10 at 04:12
  • If you can post more info about how your project and web publish are configured it may help me to make changes to make it work. – Eduardo Molteni Nov 26 '10 at 05:11
  • 3
    app_offline is only pushed when you select *Delete all files prior to publish*. It will not push the app_offline when running a replace publish. – Josh Nov 26 '10 at 15:30
  • Bingo! Josh hit it. I didn't pay close enough attention but he is correct. Tick the delete existing files box and it will do this. However, just keep in mind that deleting existing files when you don't need to severely slows down the publish. Also keep in mind that this option does exactly what it says; it deletes EVERYTHING at the target location. – Chev Nov 28 '10 at 15:33
  • That's why I publish locally (with delete selected) and use Beyond Compare to push only changed files out. I just ignore any errors that happen during the push. If you do the push right your users should only have a few second window where the site is "down" during the update. In prod we FTP to a staging area and then copy over locally on the machine. For one project we used a batch file to push in the app_offline, do the copy, and then delete the file. OP said he doesn't want to use scripts, but they are worth the effort to have a customized and effective deployment solution. – Josh Nov 29 '10 at 15:02
  • Another option would be to make app_offline.htm part of your web application (in the root directory), and then after publishing, delete the file. Then you can format the file with a custom look and feel, rather than the default ugly, ambiguous error. It is, afterall, just an html file. – Josh Nov 29 '10 at 15:06
  • Josh: that just defeat the "one-click-and-forgot" purpose of the tool – Eduardo Molteni Nov 29 '10 at 18:03

3 Answers3

2

This question is a duplicate of App_Offline in MSBuild Remote Web Deploy, but I've pasted the answer below. MSDeploy v3 has support for app_offline directly but you need MSDeploy v3 on both sides. We have not updated the VS Web Publish experience to be able to leverage this yet, but we will in an update at some point.

I just recently blogged about this at http://sedodream.com/2012/01/08/HowToTakeYourWebAppOfflineDuringPublishing.aspx. It's more difficult than it should be and I'm working on simplifying that for a later version. In any case I've pasted all the content here for you.

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

enter image description here

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
Community
  • 1
  • 1
Sayed Ibrahim Hashimi
  • 42,483
  • 14
  • 139
  • 172
  • Thanks for the detailed answer and glad to know that you are working in a simplified version! – Eduardo Molteni Aug 30 '12 at 14:19
  • It appears that this almost works, but then I get an ERROR_USER_NOT_ADMIN 401 Unauthorized. Typically this means that the credentials are wrong. Looking at the command that it's using it's not passing the password to msdeploy. How do I fix it? – James Hancock Nov 06 '13 at 18:03
2

app_offline.htm is only pushed when you select Delete all files prior to publish. It will not push the app_offline.htm when running a replace publish.

Martin Brown
  • 22,802
  • 13
  • 71
  • 107
  • That's a good start, because it tells that the tool support pushing the file. If only we can find the way to make it work when you use the more common option to copy only the new files... – Eduardo Molteni Nov 29 '10 at 11:10
1

Web Deploy (MSDeploy) v3 supports adding an app_offline.htm file during deployment, but it has two limitations for your purpose:

  • It uses it's own app_offline.htm template (it supports a custom template, but it does not work)
  • The Web Publishing Pipeline (the MSBuild stack on top of web deploy) doesn't support passing in custom rules to MSDeploy, which rules out using WPP.
Richard Szalay
  • 78,647
  • 19
  • 169
  • 223