Posts
  • CarlosAg Blog

    Modifying IIS 7.0 Administration.config using Javascript and AHADMIN

    • 0 Comments

    AHADMIN is the COM API that IIS 7.0 uses for reading and writing its configuration system. One of the not so well known features is that you can also use the same API to manage Administration.config by calling the SetMetadata method and specifying that you will be targeting Administration.config. What this ends up doing is using an IAppHostPathMapper built-in mapper that will re-map the files so that you can manage Administration.config easily.

    Here is an example of a common operation that adds a ModuleProvider (UI Extensibility Module) and its corresponding Module.


    // Create a Configuration System for modifying Administration.config
    var adminManager = new ActiveXObject("Microsoft.ApplicationHost.WritableAdminManager");
    adminManager.CommitPath = "MACHINE/WEBROOT";
    adminManager.SetMetadata("pathMapper", "AdministrationConfig");

    // add the module in the moduleProviders section
    AddModuleProvider(adminManager, "myIISModule", "myIISModuleUI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=YourPublicKey");

    // add the module in the modules section
    AddModule(adminManager, "myIISModule");

    // commit changes
    adminManager.CommitChanges();


    function AddModuleProvider(adminManager, name, type) {
       
    var moduleProvidersSection = adminManager.GetAdminSection("moduleProviders", "MACHINE/WEBROOT");

       
    var moduleProviders = moduleProvidersSection.Collection;

       
    // if already exists, do nothing
        var addElementPos = FindElement(moduleProviders, "add", ["name", name]);
       
    if (addElementPos != -1) return;

       
    var moduleProvider = moduleProviders.CreateNewElement("add");
       
    moduleProvider.Properties.Item("name").Value = name;
       
    moduleProvider.Properties.Item("type").Value = type;

       
    moduleProviders.AddElement(moduleProvider);
    }

    // if already exists, do nothing
    function AddModule(adminManager, name) {
       
    var modulesSection = adminManager.GetAdminSection("modules", "MACHINE/WEBROOT");

       
    var modules = modulesSection.Collection;

       
    // See if already exists
        var addElementPos = FindElement(modules, "add", ["name", name]);
       
    if (addElementPos != -1) return;

       
    var module = modules.CreateNewElement("add");
       
    module.Properties.Item("name").Value = name;

       
    modules.AddElement(module);
    }

    // Helper function to find an element in a collection based on the specified attributes
    function FindElement(collection, elementTagName, valuesToMatch) {
       
    for (var i = 0; i < collection.Count; i++) {
           
    var element = collection.Item(i);

           
    if (element.Name == elementTagName) {
               
    var matches = true;
               
    for (var iVal = 0; iVal < valuesToMatch.length; iVal += 2) {
                   
    var property = element.GetPropertyByName(valuesToMatch[iVal]);
                   
    var value = property.Value;
                   
    if (value != null) {
                       
    value = value.toString();
                   
    }
                   
    if (value != valuesToMatch[iVal + 1]) {
                       
    matches = false;
                       
    break;
                   
    }
               
    }
               
    if (matches) {
                   
    return i;
               
    }
           
    }
       
    }

       
    return -1;
    }

    I'll use this time to also mention that it is very important to add it to the Modules section in case you want your module to be used from Site and Application delegated connections, otherwise only Server connections will get them.

  • CarlosAg Blog

    "EXCEL.EXE is not a valid Win32 application" problem in Windows Vista

    • 3 Comments

    Every now and then after leaving my computer running for several weeks I would get a weird error message when trying to launch Excel saying something like:

    C:\PROGRA~1\MICROS~1\Office12\EXCEL.EXE is not a valid Win32 application.

    or

    This file does not have a program associated with it for performing this action. Create an association in the Set Associations control panel.

    I tried several things to make it run again, but only a restarting would solve the problem. Finally, I decided to investigate a bit more and turns out there is a fix that solves the problem that you can download from Microsoft support:

    http://support.microsoft.com/kb/952709

    This update improves the reliability of Windows Vista SP1-based computers that experience issues in which large applications cannot run after the computer is turned on for extended periods of time. For example, when you try to start Excel 2007 after the computer is turned on for extended periods of time, a user may receive an error message that resembles the following:

    EXCEL.EXE is not a valid Win32 application

    I just installed it and so far so good, no more weird errors but I guess I need to wait a few weeks before I can testify it works. Either way I though this could be helpful for others.

    Direct links for the fix download are:

    Windows Vista, 32-bit versions
    Download the Update for Windows Vista (KB952709) package now. (http://www.microsoft.com/downloads/details.aspx?FamilyId=DF72A9B0-564E-4326-894E-05CBA709CB39)
    Windows Vista, 64-bit versions
    Download the Update for Windows Vista for x64-based Systems (KB952709) package now. (http://www.microsoft.com/downloads/details.aspx?FamilyId=C3536CAA-7B71-4525-9D23-21A5B3D4507F)

  • CarlosAg Blog

    IIS 7.0 and URL Rewrite, make your Web Site SEO

    • 29 Comments

    In the past few days I've been reading a bit about SEO and trying to understand more about what makes a Web Site be SEO (Search-Engine-Optimized) and what are some of the typical headaches when trying to achieve that as well as how we can implement them in IIS.

    Today I decided to post how you can make your Web Site running IIS 7.0 a bit "friendlier" to Search Engines without having to modify any code in your application. Being SEO is a big statement since it can include several things, so for now I will scope the discussion to 3 things that can be easily addressed using the IIS URL Rewrite Module:

    1. Canonicalization
    2. Friendly URL's
    3. Site Reorganization

    1) Canonicalization

    Basically the goal of canonicalization is to ensure that the content of a page is only exposed as a unique URI. The reason this is important is because even though for humans it's easy to tell that http://www.carlosag.net is the same as http://carlosag.net, many search engines will not make any assumptions and keep them as two separate entries, potentially splitting the rankings of them lowering their relevance. Another example of this is http://www.carlosag.net/default.aspx and http://www.carlosag.net/. You can certainly minimize the impact of this by writing your application using the canonical forms of your links, for example in your links you can always link to the right content for example: http://www.carlosag.net/tools/webchart/ and remove the default.aspx, however that only accounts for part of the equation since you cannot assume everyone referencing your Web Site will follow this carefully, you cannot control their links.

    This is when URL Rewrite comes into play and truly solves this problem.

    Host name.

    URL Rewrite can help you redirect when the users type your URL in a way you don't unnecessarily want them to, for example just carlosag.net. Choosing between using WWW or not is a matter of taste but once you choose one you should ensure that you guide everyone to the right one. The following rule will automatically redirect everyone using just carlosag.net to www.carlosag.net. This configuration can be saved in the Web.config file in the root of your Web Site.Note that I'm only including the XML in this blog, however I used IIS Manager to generate all of these settings so you don't need to memorize the XML schema since the UI includes several friendly capabilities to generate all of these..

    <configuration>
     
    <system.webServer>
       
    <rewrite>
         
    <rules>
           
    <rule name="Redirect to WWW" stopProcessing="true">
             
    <match url=".*" />
              <
    conditions>
               
    <add input="{HTTP_HOST}" pattern="^carlosag.net$" />
              </
    conditions>
             
    <action type="Redirect" url="http://www.carlosag.net/{R:0}" redirectType="Permanent" />
            </
    rule>
         
    </rules>
       
    </rewrite>
     
    </system.webServer>
    </configuration>

    Note that one important thing is to use Permanent redirects (301) , this will ensure that if anybody links your page using a non-WWW link when the search engine bot crawls their Web Site it will identify the link as permanently moved and it will treat the new URL as the correct address and it will not index the old URL, which is the case when using Temporary (302) redirects. The following shows how the response of the server looks like:

    HTTP/1.1 301 Moved Permanently
    Content-Type: text/html; charset=UTF-8
    Location: http://www.carlosag.net/tools/
    Server: Microsoft-IIS/7.0
    X-Powered-By: ASP.NET
    Date: Mon, 01 Sep 2008 22:45:49 GMT
    Content-Length: 155

    <head><title>Document Moved</title></head>
    <body><h1>Object Moved</h1>This document may be found <a HREF=http://www.carlosag.net/tools/>here</a></body>

    Default Documents

    IIS has a feature called Default Document that allows you to specify the content that should be processed when a user enters a URL that is mapped to a directory and not an actual file. In other words, if the user enters http://www.carlosag.net/tools/ then they will actually get the content as if they entered http://www.carlosag.net/tools/default.aspx. That is all great, the problem is that this feature only works one way by mapping a Directory to a File, however it does not map the File to the Document, this means that if some of your links or other users enter the full URL, then search engines will see two different URL's. To solve that problem we can use a configuration very similar to the rule above, following is a rule that will redirect the default.aspx to the canonical URL (the folder).

            <rule name="Default Document" stopProcessing="true">
             
    <match url="(.*)default.aspx" />
              <
    action type="Redirect" url="{R:1}" redirectType="Permanent" />
            </
    rule>

    This again, uses a Permanent redirect to extract everything before Default.aspx and redirect it to the "parent" URL path, so for example, if the user enters http://www.carlosag.net/Tools/WindowsLiveWriter/default.aspx it will be redirected to http://www.carlosag.net/Tools/WindowsLiveWriter/ as well as http://www.carlosag.net/Tools/default.aspx to http://www.carlosag.net/Tools/. You can place this rule at the root of your site and it will take care of all the default documents (if you have a default.aspx in every folder)

    2) Friendly URL's

    Asking your user to remember that www.contoso.com/books.aspx?isbn=0735624410 is the URL for the IIS Resource Kit is not the nicest thing to do, first of all why do they care about this being an ASPX and the fact that it takes arguments and what not. It seems that providing them with a URL like www.contoso.com/books/IISResourceKit will truly resonate with them and be easier for them to remember and pass along. Most importantly it really doesn't tie you to any Web technology.

    With URL Rewrite you can easily build this kind of logic automatically without having to modify your code using Rewrite Maps:

    <configuration>
     
    <system.webServer>
       
    <rewrite>
         
    <rules>
           
    <rule name="Rewrite for Books" stopProcessing="true">
             
    <match url="Books/(.+)" />
              <
    action type="Rewrite" url="books.aspx?isbn={Books:{R:1}}" />
            </
    rule>
         
    </rules>
         
    <rewriteMaps>
           
    <rewriteMap name="Books">
             
    <add key="IISResourceKit" value="0735624410" />
              <
    add key="ProfessionalIIS7" value="0470097825" />
              <
    add key="IIS7AdministratorsPocketConsultant" value="0735623643" />
              <
    add key="IIS7ImplementationandAdministration" value="0470178930" />
            </
    rewriteMap>
         
    </rewriteMaps>
       
    </rewrite>
     
    </system.webServer>
    </configuration>

    The configuration above includes a rule that uses a Rewrite Map to translate a URL like: http://www.contoso.com/books/IISResourceKit into http://www.contoso.com/books.aspx?isbn=0735624410 automatically. Using maps is a very convenient way to have a "table" of values that can be transformed into any other value to be used in the result URL. Of course there are better ways of doing this when using large catalogs or values that change frequently but is extremely useful when you have a consistent set of values or when you can't make changes to an existing application. Note that since we use Rewrite the end users never see the "ugly-URL" unless they knew it already and typed it, and of course this means you can use the inverse approach to ensure the canonicalization is preserved:

        <rewrite>
         
    <rules>
           
    <rule name="Redirect Books to Canonical URL" stopProcessing="true">
             
    <match url="books\.aspx" />
              <
    action type="Redirect" url="Books/{ISBN:{C:1}}" appendQueryString="false" />
              <
    conditions>
               
    <add input="{QUERY_STRING}" pattern="isbn=(.+)" />
              </
    conditions>
           
    </rule>
         
    </rules>
         
    <rewriteMaps>
           
    <rewriteMap name="ISBN">
             
    <add key="0735624410" value="IISResourceKit" />
              <
    add key="0470097825" value="ProfessionalIIS7" />
              <
    add key="0735623643" value="IIS7AdministratorsPocketConsultant" />
              <
    add key="0470178930" value="IIS7ImplementationandAdministration" />
            </
    rewriteMap>
         
    </rewriteMaps>
       
    </rewrite>

    The rule above does the "inverse" by matching the URL books.aspx, extracting the ISBN query string value and doing a lookup in the ISBN table and redirecting the client to the canonical URL, so again if user enters http://www.contoso.com/books.aspx?isbn=0735624410 they will be redirected to http://www.contoso.com/books/IISResourceKit.

    This Friendly URL to me is more of a user feature than a SEO feature, however I've read in every SEO guide to reduce the number of parameters in your Query String, however, I have not find yet any document that clearly states if there is truly a limit in the search engine bot's that would truly impact the search relevance. I guess it makes sense that they wouldn't keep track of thousands of links to a catalog.aspx that has zillions of permutations based on hundreds of values in the query string (category, department, price range, etc) even if all of them were linked, but again I don't have any prove.

    3) Site Reorganization

    One complex tasks that Web Developers face sometimes is trying to reorganize their current Web Site structure, whether its moving a section to a different path, or something as simple as renaming a single file, you need to take into consideration things like, Is this move a temporary thing?, How do I ensure old clients get the new URL?, How do I prevent losing the search engine relevance?. URL Rewrite will help you perform these tasks.

    Rename a file

    If you rename a file you can very easily just write a Rewrite or Redirect Rule that ensures that your users continue getting the content. If your intent is to never go back to the old name you should use a Redirect Permanent so everyone starts getting the new content with its new "Canonical URL", however, if this could be a temporary thing you should use a Redirect Temporary. Finally a Rewrite is useful if you still want both URL's to continue to be valid (though this breaks the canonicality).

          <rule name="Rename File.php to MyFile.aspx" stopProcessing="true">
             
    <match url="File\.php" />
              <
    action type="Redirect" url="MyFile.aspx" redirectType="Permanent" />
          </
    rule>

    Moving directories

    Another common scenario is when you need to move an entire directory to another place of the Web Site. It could also be that based on some criteria (say Mobile browsers or other User Agent) get a different set of pages/images. Either way, URL rewrite helps with this. The following configuration will redirect every call to the /Images directory to the /NewImages directory.

          <rule name="Move Images to NewImages" stopProcessing="true">
             
    <match url="^images/(.*)" />
              <
    action type="Redirect" url="NewImages/{R:1}" redirectType="Permanent" />
          </
    rule>

    A related scenario is if you wanted to show different smaller images whenever a user of Windows CE was accessing your site, you could have a "img" directory where all the small images are stored and use a rule like the following:

            <rule name="Use Small Images for Windows CE" stopProcessing="true">
             
    <match url="^images/(.*)" />
              <
    action type="Rewrite" url="/img/{R:1}" />
              <
    conditions>
               
    <add input="{HTTP_USER_AGENT}" pattern="Windows CE" ignoreCase="false" />
              </
    conditions>
           
    </rule>

    Note, that in this case the use of Rewrite makes sense since we want the small images to look as the original images to the browser and it will save a "round-trip" to it.

    Moving multiple files

    Another common operation is when you randomly need to relocate pages for whatever reason (such as Marketing Campaigns, Branding, etc). In this case if you have several files that have been moved or renamed you can have a single rule that catches all of those and redirects them accordingly. Similarly, another sample could include an incremental migration from one technology to another where say you are moving from Classic ASP to ASP.NET and as you rewrite some of the old ASP pages into ASPX pages you want to start serving them without breaking any links or the search engine relevance.

        <rewrite>
         
    <rules>
           
    <rule name="Redirect Old Files and Broken Links" stopProcessing="true">
             
    <match url=".*" />
              <
    conditions>
               
    <add input="{OldFiles:{REQUEST_URI}}" pattern="(.+)" />
              </
    conditions>
             
    <action type="Redirect" url="{C:0}" />
            </
    rule>
         
    </rules>
         
    <rewriteMaps>
           
    <rewriteMap name="OldFiles">
             
    <add key="/tools/WebChart/sample.asp" value="tools/WebChart/sample.aspx" />
              <
    add key="/tools/default.asp" value="tools/" />
              <
    add key="/images/brokenlink.jpg" value="/images/brokenlink.png" />
            </
    rewriteMap>
         
    </rewriteMaps>
       
    </rewrite>

    Now, you can just keep adding to this table any broken link and specify its new address.

    Others

    Other potential use of URL Rewrite is when using RIA applications in the browser, whether using things like AJAX, Silverlight or Flash, that are not easy to parse and index by search engines, you could use URL Rewrite to rewrite the URL to static HTML versions of your content, however you should make sure that the content is consistent so you don't misguide users and search engines. For example the following rule will rewrite all the files in the RIAFiles table to their static HTML counterpart but only if the User Agent is the MSNBot or the GoogleBot:

        <rewrite>
         
    <rules>
           
    <rule name="Rewrite RIA Files" stopProcessing="true">
             
    <match url=".*" />
              <
    conditions>
               
    <add input="{HTTP_USER_AGENT}" pattern="MSNBot|Googlebot" />
                <
    add input="{RIAFiles:{REQUEST_URI}}" pattern="(.+)" />
              </
    conditions>
             
    <action type="Rewrite" url="{C:0}" />
            </
    rule>
         
    </rules>
         
    <rewriteMaps>
           
    <rewriteMap name="RIAFiles">
             
    <add key="/samples/Silverlight.aspx" value="/samples/Silverlight.htm" />
              <
    add key="/samples/MyAjax.aspx" value="/samples/MyAjax.htm" />
            </
    rewriteMap>
         
    </rewriteMaps>
       
    </rewrite>

    Related to this is that you might want to prevent search engines from crawling certain files (or your entire site), for that, you can use the Robots.txt semantics and use a "disallow", however, you can also use URL Rewrite to prevent this with more functionality such as blocking only a specific user agent:

        <rewrite>
         
    <rules>
           
    <rule name="Prevent access to files" stopProcessing="true">
             
    <match url=".*" />
              <
    conditions>
               
    <add input="{HTTP_USER_AGENT}" pattern="SomeRandomBot" />
                <
    add input="{NonIndexedFiles:{REQUEST_URI}}" pattern="(.+)" />
              </
    conditions>
             
    <action type="AbortRequest" />
            </
    rule>
         
    </rules>
         
    <rewriteMaps>
           
    <rewriteMap name="NonIndexedFiles">
             
    <add key="/profile.aspx" value="block" />
              <
    add key="/personal.aspx" value="block" />
            </
    rewriteMap>
         
    </rewriteMaps>
       
    </rewrite>

    There are several other things you can do to ensure that your Web Site is friendly with Search Engines, however most of them require changes to your application, but certainly worth the effort, for example:

    • Ensure your HTML includes a <title> tag.
    • Ensure your HTML includes a <meta name="description".
    • Use the correct HTML semantics, use H1 once and only once, use the alt attribute in your <img>, use <noscript> etc.
    • Redirect using status code 301 and not 302.
    • Provide Site Map's and/or Robots.txt.
    • Beware of POST backs and links that require script to run. 

    Resources

    For this entry I read and used some of the resources at several Web Sites, including:

  • CarlosAg Blog

    Configuring SSL using Javascript in IIS 7.0

    • 1 Comments

    In IIS 7.0 the configuration system has a nice feature that lets you extend it using what we sometimes refer as dynamic properties. This properties rather than being hard-coded in XML in some .config file they are implemented by COM interface that whenever a tool queries its value, the configuration system will create an instance of such a COM object and will query its value (through the IAppHostPropertyExtension interface). In our configuration system we used this capability to surface some runtime features such as the state of an Application Pool. We also used this feature to expose the SSL Certificate configured in a site binding. Furthermore, this extensibility also allows us to expose methods that provide functionality in a very similar way (through the IAppHostMethodExtension interface). We used this capability to provide functionality such as recycling an Application Pool, restarting a Site, or adding a certificate to a binding.

    So in this post I just want to show how using this capability from Javascript you can easily provision an HTTPS web site by leveraging our configuration system and the extensions we provide for HTTP.SYS (HttpQueryServiceConfiguration/HttpSetServiceConfiguration) configuration below. Note that functionality we expose as properties include reading the certificate hash, reading the certificate store, reading the DS Mapper flag. And also as Methods we expose the ability to configure a certificate, enable and disable the DS Mapper functionality.

    try {
       
    // Setup SSL in default web site, port 443, certificate hash
        SetupSsl("Default Web Site", "*:443:", "3efb3448636941cde7dae47c377f14188cbeb740");
    }
    catch(e) {
       
    WScript.Echo(e.description);
    }

    function SetupSsl(siteName, bindingInformation, certificateHash) {
       
    var adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager');

       
    var sitesSection = adminManager.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST"); 

       
    //-------------------------------------------------------------
        // Find the Site
        var siteElementPos = FindElement(sitesSection.Collection, "site", ["name", siteName]);
       
    if (siteElementPos == -1) throw new Error( "Site not found!");
       
    var siteElement = sitesSection.Collection.Item(siteElementPos);

       
    //-------------------------------------------------------------
        // Verify if the binding already exists...
        var bindingsCollection = siteElement.ChildElements.Item("bindings").Collection;
       
    var bindingElementPos = FindElement(bindingsCollection, "binding", ["protocol", "https", "bindingInformation", bindingInformation]);
       
    if (bindingElementPos != -1) throw new Error( "Binding Already Exists!!!");
        
       
    //-------------------------------------------------------------
        // Create the binding in the IIS configuration system
        var bindingElement = bindingsCollection.CreateNewElement("binding");
       
    bindingElement.Properties.Item("protocol").Value = "https";
       
    bindingElement.Properties.Item("bindingInformation").Value = bindingInformation;
       
    bindingsCollection.AddElement(bindingElement, -1);

       
    //-------------------------------------------------------------
        // Configure HTTP.Sys SSL certificate using the method extension
        var methodInstance = bindingElement.Methods.Item("AddSslCertificate").CreateInstance();
       
    methodInstance.Input.Properties.Item("certificateHash").Value = certificateHash;
       
    methodInstance.Input.Properties.Item("certificateStoreName").Value = "MY";
       
    methodInstance.Execute();
        
       
    //-------------------------------------------------------------
        // You can also use the certificateHash extension property to read the certificate Hash
        WScript.Echo(bindingElement.Properties.Item("certificateHash").Value);

       
    //-------------------------------------------------------------
        // You could also Configure HTTP.Sys DS Mapper functionality if needed:
        WScript.Echo(bindingElement.Properties.Item("isDsMapperEnabled").Value);
       
    //bindingElement.Methods.Item("EnableDsMapper").CreateInstance().Execute();
        //bindingElement.Methods.Item("DisableDsMapper").CreateInstance().Execute();

       
    adminManager.CommitChanges();
    }

        //-------------------------------------------------------------
        // Helper method to find an element in a collection based on valuesToMatch
    function FindElement(collection, elementTagName, valuesToMatch) {
       
    for (var i = 0; i < collection.Count; i++) {
           
    var element = collection.Item(i);
            
           
    if (element.Name == elementTagName) {
               
    var matches = true;
               
    for (var iVal = 0; iVal < valuesToMatch.length; iVal += 2) {
                   
    var property = element.GetPropertyByName(valuesToMatch[iVal]);
                   
    var value = property.Value;
                   
    if (value != null) {
                       
    value = value.toString();
                   
    }
                   
    if (value != valuesToMatch[iVal + 1]) {
                       
    matches = false;
                       
    break;
                   
    }
               
    }
               
    if (matches) {
                   
    return i;
               
    }
           
    }
       
    }
        
       
    return -1;
    }

    Some of the nice side effects of exposing this as extensions in the IIS Configuration system is that it is now possible to call this method from a remote machine as well through the DCOM support of AHADMIN.

  • CarlosAg Blog

    Application Request Routing and the IIS 7.0 Web Management Service

    • 1 Comments

    Yesterday I was having a conversation with Anil Ruia who happens to be the ARR (Application Request Routing) developer and based on customer feedback we discussed the idea of using ARR in the context of Remote Management in IIS which solves a question that several people asked me before and thought it would be fun to try it out.

    Basically the question that I got asked was "Can I have a single entry-point exposed for Remote Management?", or in other words "Can I provide users with remote administration giving them a single server name like management.myhostingcompany.com, instead of having to give them the specific machine name where their site lives?". So far the answer to these questions was "not easily", however with the use of ARR and URL Rewriter we will see how easy it is to achieve this.

    The only thing you need for this to work is install the new URL Rewrite and ARR Module both available here http://blogs.iis.net/bills/archive/2008/07/09/new-iis7-releases-url-rewrite-application-routing-and-load-balancing-and-powershell-cmd-lets.aspx.

    Background

    The Web Management Service (WMSvc) is the service that enables remote administration for IIS 7.0 Manager, providing an HTTPS end-point that exposes functionality similar to Web Services to manage the Web Server (IIS) remotely. This service uses HTTPS for its communication and exposes several configuration options that support giving access to Non-Windows Users (What we call IIS Manager Users), provide a list of IP Restrictions, support only local connections and many more that can be managed using the Management Service feature inside IIS Manager.

    To enable remote administration typically you need to: 1) Configure a valid Certificate for SSL, 2) Allow Remote Connections and 3) Start the WMSvc Service, all of which can be performed in IIS Manager. Once you have successfully enabled the remote service you should be able to go to a different machine and be able to connect remotely.

    Note: If you are using Windows Vista, Windows XP, or Windows 2003 to connect to a Windows Server 2008 you need to download and install the client to do this: http://www.iis.net/downloads/default.aspx?tabid=34&g=6&i=1626

    However, one of the drawbacks is that in order to be able to connect to a Web Site, the end-user needs to know the machine name, as well as the name of the Web Site they will be connecting to, which sometimes it would be better to be dynamic. The following image shows the information required to enter when connecting to a Web Site. Note that if connecting to an application you will also need to enter the name of the application.

    ConnectingToSite

    However, this can potentially reduce the flexibility for deployment options, since now your customers have specific knowledge of the physical machine and will limit the flexibility of moving the site to different machines or even changing the name of the site where it is being hosted.

    ARR and URL Rewrite to the rescue.

    ARR has several very interesting capabilities that are really useful for this scenario. First, we can configure it to act as a proxy and basically forward the requests to another server where they actually get processed. This is the simplest configuration option and what it allows you is to have something similar to the next image:

    WMSvcRouting

    To set up this configuration where a front-end management server forwards the IIS Remote Management requests to another server running WMSVC you have to:

    1. Install ARR and URL Rewrite in the Server that is intended to be used as the front-end for management requests. Lets call this ServerA.
    2. Create a new Web Site.
      1. Navigate to IIS Manager->Site
      2. Click Add Web Site.
      3. In the dialog set: Site name:ManagementSite, Binding: https, port: 8172 and choose a valid SSL certificate, specify a phisical path. Click OK
    3. Configure URL Rewrite to Route requests to the IIS Management Service running in the other computer.
      1. Navigate to IIS Manager->Sites->Management Site->URL Rewrite Module
      2. Click Add Rule
      3. Set: Name: RouteWMSvc, Pattern:.*, Rewrite URL:https://<RemoteServer>:8172/{R:0}, Stop Processing rules: Checked.
      4. This should generate a web.config with similar content (note that my backend, ie the RemoteServer in my case is carlosag2-iis below):

        <configuration>
           
        <system.webServer>
               
        <rewrite>
                   
        <rules>
                       
        <rule name="RouteWMSvc" stopProcessing="true">
                           
        <match url=".*" />
                            <
        action type="Rewrite" url="https://carlosag2-iis:8172/{R:0}" />
                        </
        rule>
                   
        </rules>
               
        </rewrite>
           
        </system.webServer>
        </configuration>

    4. Now you can run IIS Manager in any client machine, specify the ServerA as the machine name and specify any web site in the remote RemoteServer, the result will be that all requests will be forwarded to the WMSvc running in the remote server.

    Now, that is interesting and the scenario it allows you to do is potentially have WMSvc IP Request Filtering in the RemoteServer and only allow calls from the Management Server where you can do further configuration. Note that this also means that you can have a single public SSL Certificate in the management server and use privately issued certificates (or potentially even self-signed certificates in the remoteserver since you can control installing the certificate into the management server). It also means that the customers no longer use the physical name of the RemoteServer machine but instead connect to the Management Server allowing you to completely move them to another machine and not have to update your clients.

    Troubleshooting: If you are having troubles testing this, the best thing to do is enable Failed Request Tracing in the ManagementSite, which will tell you exactly what is going on. For example you will get entries like:

    Warning: ModuleName="ApplicationRequestRouting", Notification="EXECUTE_REQUEST_HANDLER", HttpStatus="502", HttpReason="Bad Gateway", HttpSubStatus="3", ErrorCode="2147954575", ConfigExceptionInfo=""

    If you lookup the ErrorCode, it is actually: ERROR_WINHTTP_SECURE_FAILURE, this means that you have most likely issues with the certificate. In my case, just to test this what I did is generate a self-signed certificate in the RemoteServer with the name of the machine (carlosag2-iis) and then I installed that certificate using the MMC certificates snap-in in the management server into the Trusted Root Certification Authority. Disclaimer Warning!! this is something you should only do for testing purposes or if you know what you are doing.

    More Advanced Stuff... Dynamically choosing the machine

    Now, trying to push the capabilities of this I decided to solve another requests that we've heard which is closely related "Can I have a single management server and dynamically route the requests to the machine where a particular site lives?"

    The following picture represents this, where the Management Server dynamically resolves the server that it should talk to using the URL Rewrite Maps functionality.

    WMSvcRoutingMultiple

    Turns out this is really simple using URL Rewrite, basically you can write a Rewrite Rule that matches the Site name that is included as part of the Query String and use the Rewrite Maps support for figuring out the machine where this site lives. The following shows such a rule:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
     
    <system.webServer>
       
    <rewrite>
         
    <rules>
           
    <rule name="RouteWMSvc" stopProcessing="true">
             
    <match url=".*" />
              <
    conditions>
               
    <add input="{QUERY_STRING}" pattern="Site=([^&amp;]+)" />
              </
    conditions>
             
    <action type="Rewrite" url="https://{ServersTable:{C:1}}:8172/{R:0}" appendQueryString="true" />
            </
    rule>
         
    </rules>
         
    <rewriteMaps>
           
    <rewriteMap name="ServersTable">
             
    <add key="CarlosAgWebSite" value="carlosag2-iis" />
              <
    add key="SomeOtherUserSite" value="carlosag1-iis" />
              <
    add key="SomeOtherUserSite2" value="carlosag3-iis" />
            </
    rewriteMap>
         
    </rewriteMaps>
       
    </rewrite>
     
    </system.webServer>
    </configuration>

    Basically, URL Rewrite matches every request and uses the condition entry to parse the Query String and find the Site name within it. With it, it and using the Map ServersTable to resolve the machine name based on the Site name it rewrites the request to the machine where its currently located. This makes it basically route "https://localhost:8172/Service.axd?...&Site=CarlosAgWebSite" into https://carlosag2-iis:8172/Service.axd?...&Site=CarlosAgWebSite. The end result is that can dynamically at any time just update this table and make ARR route the requests to the right machine giving you complete flexibility on the deployment of sites.

    One thing to note is that URL Rewrite is one of the ways you can make the ARR scenario work, however, you could also write your own module that uses any dynamic behavior such as going to a database or a provisioning system or anything else and rewrite the URL programmatically in a way that ARR will understand it and do the routing automatically.

    Also, worth to mention that ARR has way more features than just this, making it possible to load-balance requests and many more interesting stuff that I will try to get back in a future post.

    With all this you can imagine several benefits, such as single public end-point for remote management of multiple servers, only one valid certificate is needed facing public machines, you can relocate sites at your own will since customers will never really know the real machine name where their site lives, you can use a similar technique to rewrite even the Site Name and give them some friendly name such as their user name or whatever.

    Acknowledgements: I want to thank Anil Ruia and Daniel Vasquez Lopez who helped figuring out a few issues during this blog and Ruslan Yakushev and Won Yoo for reviewing its technical accuracy.

  • CarlosAg Blog

    Back again

    • 2 Comments

    For the past month its been pretty quiet in my blog since I have been a little bit busy with the best thing that has happened in my life, my first baby Sofia was borne and today we are celebrating her first month. Days have been incredibly full of love, fun and joy (but certainly not of sleep). Now nights are much better, sleeping for a few hours now and back at work with lots of enthusiasm for the upcoming work in IIS and the incredibly exiting times at the team. So now by blog should show a bit more activity moving forward.

    Here is a picture of my first baby, named Sofia celebrating her first month of life.

    Sofy

  • CarlosAg Blog

    Very funny blog to read, Linux Hater's blog

    • 6 Comments

    One of the things I try to regularly do is to read blogs that are not necessarily pro-Microsoft and one of my favorite ones is Miguel de Icaza's blog. The other day I was reading one blog post that caught my attention about a blog he says he is a "fan of" named "Linux Hater's blog". So of course I decided to give that a read and went and started reading the entries in there, and I just could not stop laughing and laughing, and before I noticed I had been reading for almost an hour. I do have to warn though, the vocabulary used is their entries is ...um... lets say fluid?.

    Disclaimer: I'm not saying that their entries are right or wrong, I just literally couldn't stop laughing about some of the comments and their replies, and in some cases I do think they are a bit biased and in some cases they do have a good message that could help advance the software industry in general.

    Anyway, if you want to read a very "funny-colorful-passionate" blog I think its worth a read: http://linuxhaters.blogspot.com/

  • CarlosAg Blog

    IIS 7.0 Remote Administration and Database Manager Video

    • 5 Comments

    DiscountASP.net published a very nice video that shows how you can enable IIS Manager and Database Manager and other modules for their customers.

    If you don't use DiscountASP.net as your ISP at least its interesting to see how IIS 7.0 and its Remote Administration capabilities over HTTPS and Delegated Management look like. Also you can see the Database Manager in action that you can download for free from http://learn.iis.net/page.aspx/416/basics-of-database-manager/

    First couple of minutes show how they expose this functionality to their customers, but If you just care to see the IIS 7.0 features running seek to minute 2:00.

    http://iis7test.com/iis7_modules/iis7_modules.html

  • CarlosAg Blog

    Adding ASP.NET Tracing to IIS 7.0 Failed Request Tracing

    • 2 Comments

    IIS 7.0 Failed Request Tracing (for historical reasons internally we refer to it as FREB, since it used to be called Failed Request Event Buffering, and there are no "good-sounding-decent" acronyms for the new name) is probably the best diagnosing tool that IIS has ever had (that doesn't require Debugging skills), in a simplistic way it exposes all the interesting events that happen during the request processing in a way that allows you to really understand what went wrong with any request. To learn more you can go to http://learn.iis.net/page.aspx/266/troubleshooting-failed-requests-using-tracing-in-iis7/.

    What is not immediately obvious is that you can use this tracing capabilities from your ASP.NET applications to output the tracing information in our infrastructure so that your users get a holistic view of the request.

    When you are developing in ASP.NET there are typically two Tracing infrastructures you are likely to use, the ASP.NET Page Tracing and the System.Diagnostics Tracing. In recent versions they have been better integrated (attribute writeToDiagnosticsTrace)  but still you want to know about both of them.

    Today I'll just focus on logging ASP.NET Tracing to FREB, and in a future post I will show how to do it for System.Diagnostics Tracing.

    To send the ASP.NET Tracing to FREB you just need to enable ASP.NET tracing, use the ASPNET trace provider and you will get those entries in the FREB log. The following web.config will enable FREB and ASP.NET Tracing. (Note that you need to go to the Default Web Site and Enable Failed Request Filtering so that this rules get executed)

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
     
    <system.web>
       
    <trace enabled="true" pageOutput="false" />
      </
    system.web>
     
    <system.webServer>
       
    <tracing>
         
    <traceFailedRequests>
           
    <add path="*.aspx">
             
    <traceAreas>
               
    <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
                <
    add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,Compression,Cache,RequestNotifications,Module,Rewrite" verbosity="Verbose" />
              </
    traceAreas>
             
    <failureDefinitions statusCodes="100-600" />
            </
    add>
         
    </traceFailedRequests>
       
    </tracing>
     
    </system.webServer>
    </configuration>

    Now if you have a sample page like the following:

    <%@ Page Language="C#" %>
    <script runat="server">
       
    void Page_Load() {
           
    Page.Trace.Write("Hello world from my ASP.NET Application");
           
    Page.Trace.Warn("This is a warning from my ASP.NET Application");
       
    }
    </script>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
       
    <title>Untitled Page</title>
    </head>
    <body>
       
    <form id="form1" runat="server">
       
    <div>
       
    </div>
       
    </form>
    </body>
    </html>

    The result is that in \inetpub\logs\FailedReqLogsFiles\ you will get an XML file that includes all the details of the request including the Page Traces from ASP.NET. Note that we provide an XSLT transformation that parses the Xml file and provides a friendly view of it where it shows different views of the trace file. For example below only the warning is shown in the Request Summary view:

    TraceOutput1

    There is also a Request Details view where you can filter by all the ASP.NET Page Traces that includes both of the traces we added in the Page code.

    TraceOutput2

  • CarlosAg Blog

    Mapping a different file extension for ASPX Pages in IIS 7.0

    • 3 Comments

    Today I read a question in one of the IIS.NET forums - although I'm not sure if this is what they really wanted to know - I figured it might be useful to understand how to do this anyway. Several times users does not like exposing their ASP.NET pages using the default .aspx file extension (sometimes because of legacy reasons, where they try to minimize the risk of generating broken links when moving from a different technology, to preserve the validity of previous search-engines-indexes and sometimes for the false sense of security or whatever).

    Regardless of why, the bottom line, to map a different file extension so they behave just like any other ASP.NET page requires you to add a couple of entries in configuration, especially if you want those to be able to work in both Pipeline Modes "Classic and Integrated".

    For this exercise lets assume you want to assign the file extension .IIS so that they get processed as ASPX pages and that you only want this to be applicable for Default Web Site and its applications.

    The following Web.Config file will enable this to work for both Pipeline Modes:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
       
    <system.webServer>
           
    <handlers>
               
    <add name="ASPNETLikeHandler-Classic" path="*.iis" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" requireAccess="Script" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
                <
    add name="ASPNETLikeHandler" path="*.iis" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" modules="ManagedPipelineHandler" requireAccess="Script" preCondition="integratedMode" />
            </
    handlers>
           
    <validation validateIntegratedModeConfiguration="false" />
        </
    system.webServer>
       
    <system.web>
           
    <compilation>
               
    <buildProviders>
                   
    <add extension=".iis" type="System.Web.Compilation.PageBuildProvider" />
                </
    buildProviders>
           
    </compilation>
           
    <httpHandlers>
               
    <add path="*.iis" type="System.Web.UI.PageHandlerFactory" verb="*" />
            </
    httpHandlers>
       
    </system.web>
    </configuration>

    The following command lines uses AppCmd.exe to enable this for both Pipeline Modes and basically generate the .config file above.


    echo To make it work in integrated mode
    appcmd.exe set config "Default Web Site" -section:system.webServer/handlers /+"[name='ASPNETLikeHandler',path='*.iis',verb='GET,HEAD,POST,DEBUG',type='System.Web.UI.PageHandlerFactory',modules='ManagedPipelineHandler',requireAccess='Script',preCondition='integratedMode']"
    appcmd.exe set config "Default Web Site" -section:system.web/compilation /+"buildProviders.[extension='.iis',type='System.Web.Compilation.PageBuildProvider']"

    echo To make it work in classic mode
    appcmd.exe set config "Default Web Site" -section:system.webServer/handlers /+"[name='ASPNETLikeHandler-Classic',path='*.iis',verb='GET,HEAD,POST,DEBUG',modules='IsapiModule',scriptProcessor='%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll',requireAccess='Script',preCondition='classicMode,runtimeVersionv2.0,bitness32']"

    appcmd.exe set config "Default Web Site" -section:system.web/httpHandlers /+"[path='*.iis',type='System.Web.UI.PageHandlerFactory',verb='*']"

    appcmd.exe set config "Default Web Site" -section:system.web/compilation /+"buildProviders.[extension='.iis',type='System.Web.Compilation.PageBuildProvider']"

    appcmd.exe set config "Default Web Site" -section:system.webServer/validation /validateIntegratedModeConfiguration:"False"

    So what does this actually do?

    Lets actually describe the AppCmd.exe lines since it breaks nicely the different operations.

    1. Integrated Mode. When running in Integrated mode conceptually only the IIS Pipeline gets executed and not the ASP.NET pipeline, this means that the HttpHandlers section (in system.web) is actually not used at all.
      1. So just by adding a new handler (ASPNETLikeHandler above) to the System.WebServer/Handlers will cause IIS to see this extension and correctly execute the page. Note that we use the preCondition attribute to tell IIS that this handler should only be used when we are running in an Integrated Pipeline Application Pool.
      2. The second line only tells the ASP.NET compilation infrastructure how to deal with files with this extension so that it can compile it as needed.
    2. Classic Mode. In classic mode the ASP.NET pipeline keeps running as previous versions of IIS as a simple ISAPI and the new IIS pipeline gets executed as well, this means that we first need to tell IIS to route the request to ASP.NET ISAPI and then we need to tell ASP.NET how to handle this extension as well by using the system.web/httpHandlers to process the request.
      1. The first line adds a handler (ASPNETLikeHandler-Classic above) to IIS so that IIS correctly routes this request to the aspnet_isapi.dll. Note that in this case we also use the preCondition attribute to tell IIS that this only applies when running in an Application Pool in classic mode, furthermore we also use it to specify that it should only be used in an Application Pool running in 32 bit mode and that is using version 2.0 of the runtime. If you wanted to support also 64 bit application pools you would add another handler pointing to the 64bit version of aspnet_isapi.dll in Framework64 folder and use bitness64 instead.
      2. The second line tells ASP.NET that the .iis extension is handled by the PageHandlerFactory, so that the ASP.NET pipeline understands what to do when a file with the .iis extension is requested.
      3. Again, just as 2 in the Integrated Mode case we need to tell the ASP.NET compilation infrastructure how to deal with this extension.
      4. Finally since we are adding a system.web/httpHandler entry and to make sure that this will not break when someone changes the App pool to integrated mode we tell IIS to "not complain" if we get run in Integrated mode since we also included the handler in the IIS sections in the right way.

    Hopefully this helps understanding a bit how to re-map extensions to ASP.NET extensions, and in doing that learn a bit more about preConditions, Handlers and AppCmd.

Page 5 of 10 (91 items) «34567»