Showing posts with label IIS. Show all posts
Showing posts with label IIS. Show all posts

Using MsDeploy publish profile .pubxml to create an empty folder structure on IIS and skip deleting it with MsDeploySkipRules

I’m going to share here a pretty nice deployment automation solution for when you need to have a defined set of folders in place when first deploying an app to IIS. On subsequent re-deployments the folder structure will be kept intact with any files users may have added to them.

Let’s work with a simple example: given an ASP.NET MVC app, we need a folder called Files and inside this folder there will be some pre-defined folders named: Folder 1, Folder 2 and Folder 3 with a child folder called Test. Something like this:

App root
|
---Files
   |
   ---Folder 1
   ---Folder 2
   ---Folder 3
      |
      ---Test

When deploying for the 1st time these folders are empty but the folder structure is mandatory let’s say because I’m using a file manager like elFinder.Net that expects that these folders exist on the server. Why? Because the ASP.NET MVC app has links pointing to these folders in some views. The folders should be ready to store files when the app is released. What also comes to my mind is the case where we need an existing Downloads/Uploads folder.

What’s more? We also want all this to happen while using Publish Web command from within Visual Studio and still keeping the option Remove additional files at destination checked:

Figure 1 - Visual Studio Publish Web with File Publish Options => Remove additional files at destinationFigure 1 - Visual Studio Publish Web with File Publish Options => Remove additional files at destination

This setting is nice because when you update jQuery NuGet package for example (jquery-2.1.1.js) it will send the new files to IIS server and will remove the old version (jquery-2.1.0.js) that exists there. This is really important so that the app keeps working as expected and don’t load the wrong version/duplicate files. If we don’t check that option we have to go to the server and delete the old files manually. What a cumbersome and error prone task!

What to do in this case where we want the deployment task do the work “automagically” for us with no human intervention? It’s seems like a lot of requirements and a task not so simple as “it appears to be”… Yep, it requires a little bit of MsDeploy codez.

Here’s what is working for me at the moment after finding some code pieces from here and there:

Given a  publish profile named Local.pubxml that sits here:
C:\Company\Company.ProjectName\Company.ProjectName.Web\Properties\
PublishProfiles\Local.pubxml

Let’s add the code blocks necessary to make all the requirements come to life:

<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit http://go.microsoft.com/fwlink/?LinkID=208121. 
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
<AfterAddIisSettingAndFileContentsToSourceManifest>AddCustomSkipRules</AfterAddIisSettingAndFileContentsToSourceManifest>
    <WebPublishMethod>MSDeploy</WebPublishMethod>
    <LastUsedBuildConfiguration>Local</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>
    <SiteUrlToLaunchAfterPublish />
    <ExcludeApp_Data>False</ExcludeApp_Data>
    <MSDeployServiceURL>localhost</MSDeployServiceURL>
    <DeployIisAppPath>SuperCoolAwesomeAppName</DeployIisAppPath>
    <RemoteSitePhysicalPath />
    <SkipExtraFilesOnServer>False</SkipExtraFilesOnServer>
<MSDeployPublishMethod>WMSVC</MSDeployPublishMethod> <AllowUntrustedCertificate>True</AllowUntrustedCertificate>
<EnableMSDeployBackup>False</EnableMSDeployBackup> <UserName /> <_SavePWD>False</_SavePWD> <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish> </PropertyGroup> <PropertyGroup> <UseMsDeployExe>true</UseMsDeployExe> </PropertyGroup> <Target Name="CreateEmptyFolders"> <Message Text="Adding empty folders to Files" /> <MakeDir Directories="$(_MSDeployDirPath_FullPath)\Files\Folder 1" /> <MakeDir Directories="$(_MSDeployDirPath_FullPath)\Files\Folder 2" /> <MakeDir Directories="$(_MSDeployDirPath_FullPath)\Files\Folder 3\Test"/> </Target> <Target Name="AddCustomSkipRules" DependsOnTargets="CreateEmptyFolders"> <Message Text="Adding Custom Skip Rules" /> <ItemGroup>
      <MsDeploySkipRules Include="SkipFilesInFilesFolder">
        <SkipAction>Delete</SkipAction>
        <ObjectName>filePath</ObjectName>
        <AbsolutePath>$(_DestinationContentPath)\\Files\\.*</AbsolutePath>
        <Apply>Destination</Apply>
      </MsDeploySkipRules>

      <MsDeploySkipRules Include="SkipFoldersInFilesFolders">
        <SkipAction></SkipAction>
        <ObjectName>dirPath</ObjectName>
        <AbsolutePath>$(_DestinationContentPath)\\Files\\.*\\*</AbsolutePath>
        <Apply>Destination</Apply>
      </MsDeploySkipRules>

</ItemGroup>
</
Target> </Project>

This is self explanatory. Pay attention to the highlighted parts as they are the glue that make all the requirements happen during the publish action.

What is going on?

The property

<AfterAddIisSettingAndFileContentsToSourceManifest>
AddCustomSkipRules
</AfterAddIisSettingAndFileContentsToSourceManifest>

calls the target

<Target Name="AddCustomSkipRules"
DependsOnTargets="CreateEmptyFolders">
that in turn depends on the other task
<Target Name="CreateEmptyFolders">

CreateEmptyFolders take care of adding/creating the folder structure on the server if it doen’t exist yet.

AddCustomSkipRules contains two  <MsDeploySkipRules...>. One is to prevent deleting Files and the other prevents deleting the child folders.

Check the targets’ logic. They’re pretty easy to understand…

Note: make sure you don’t forget the

<UseMsDeployExe>true</UseMsDeployExe>

otherwise you may see this error during deployment:

Error    4    Web deployment task failed. (Unrecognized skip directive 'skipaction'. Must be one of the following: "objectName," "keyAttribute," "absolutePath," "xPath," "attributes.<name>.")        0    0    Company.ProjectName.Web

Simple as pie after we see it working. Isn’t it? Winking smile

Hope it helps!

As an interesting point, see the command executed by msdeploy.exe that gets written to the output window in Visual Studio:

C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe -source:manifest='C:\Company\Company.ProjectName\Company.ProjectName.Web\obj\
Local\Package\Company.ProjectName.Web.SourceManifest.xml' -dest:auto,IncludeAcls='False',AuthType='NTLM' -verb:sync -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension -
skip:skipaction='Delete',objectname='filePath',absolutepath='\\Files\\.*' -skip:objectname='dirPath',absolutepath='\\Files\\.*\\*' -setParamFile:"C:\Company\Company.ProjectName\Company.ProjectName.Web\obj\Local\ Package\Company.ProjectName.Web.Parameters.xml" -retryAttempts=2 -userAgent="VS12.0:PublishDialog:WTE2.3.50425.0"

IIS 8 Web Site in Windows 8 externally accessible to the World

Today I needed to allow a per to access an application that’s under development. Instead of buying a cheap ASP.NET hosting account I decided to host the app on my local IIS 8 server. The process to get this configured and working was somewhat exciting since I learned new things along the way.

I’ll show in this post the steps I followed to have this working as expected…

My setup:

  • Windows 8 running inside a virtual machine in Parallels 8 with IP address 192.168.1.107;
  • ASP.NET MVC 4 app deployed in the Default Web Site in IIS 8 that is configured to accept incoming connections in the standard port 80;
  • Linksys WAG200G router/modem connected to the outside/external World.

Steps to follow:
1 - Go to the router’s management interface. In my case it’s located in the address 192.168.1.1.

2 - You’ll need to set a port forwarding configuration. In my case it’s located in Applications & Gaming / Single Port Forwarding. It’ll vary slightly depending on your router vendor and model.

Application: HTTP
External Port: 80
Internal Port: 80
Protocol: TCP
IP Address: 192.168.1.107 (your Windows/IIS machine IP address)
Enabled: True (checked)

Linksys WAG200G Single Port Forwarding configurationFigure 1 - Linksys WAG200G Single Port Forwarding configuration

Make sure to hit the Save Settings button way bellow the page.

3 - Go to Windows 8 Control Panel / System and Security / Windows Firewall / Turn Windows Firewall on or off.
Turn off Windows Firewall for Private Networks.

Turning off Windows 8 Firewall for Private NetworksFigure 2 - Turning off Windows 8 Firewall for Private Networks

4 - Take note of your internet IP address. You can see it in the router’s status page. In my case it’s located in Status / Gateway. Again where you’ll find this info will vary depending on your router vendor.

Taking note of the internet/Gateway IP that uniquely identifies the machine on the internetFigure 3 - Taking note of the internet/Gateway IP that uniquely identifies the machine on the internet

You can also see your current IP Address using Gmail’s Last Account Activity report if you happen to have a Gmail account of course.

5 - Open a browser window and type your internet Gateway IP address taken in step 4. You should be presented with the beautiful IIS 8 default web page if you have no app deployed in the Default Web Site; otherwise you should see your app’s default page/login view.

IIS 8 Default Web Site pageFigure 4 - IIS 8 Default Web Site page

That’s it! Now the web site/app is available externally to any user in any part of the WORLD directly from my development machine.

Anytime I want I can hit Publish from within Visual Studio 2012 and deploy directly to the local IIS 8 server. The user that knows my IP address can then see my ongoing work remotely and free of charge for now.

Note 1
I do not have a static IP address, that is, my ISP here in Brazil (Oi Velox) gives me a dynamic IP address. This means that if there’s a power outage or if the router resets for whatever reason I’ll get a different address – it’s really important to know about this. To overcome such limitation there are some services that can help. One of them is No-IP. It basically allows you to access your computer by a hostname instead of an IP address by using dynamic DNS. They have a free utility app that runs in the background and that automatically syncs your current dynamic IP address with your custom hostname defined in No-IP service.

Excerpts from No-IP site:

What is a hostname?
A hostname is a name given to a computer to make connecting to it easier. Instead of typing out a long IP address you can enter the hostname followed by the domain name, such as myhostname.no-ip.com.

What is dynamic DNS?
Dynamic DNS makes it possible to connect to computers with dynamic IP addresses without needing to know the actual IP address.

They have a free account available with some limitations but it’s worth trying anyway.

Note 2
If for some reason your ISP blocks the default IIS port 80 (something common – hooray! not in my case today) you can try forwarding from a different external port like 8888 in step 2 above. In this case, you’d have to specify this port address when trying to access your IIS server. For example:

189.xxx.xxx.xxx:8888

where 189.xxx.xxx.xxx is your internet IP address.