September, 2008

Posts
  • CarlosAg Blog

    Using IIS Manager Users Authentication in your Web Application

    • 2 Comments

    Today in the IIS.NET Forums a question was asked if it was possible to use the same IIS Manager Users authentication in the context of a Web Application so that you could have say something like WebDAV using the same credentials as you use when using IIS Manager Remote Administration.

    The IIS Manager Remote Administration allows you to connect to manage your Web Site using credentials that are not Windows Users, but instead just a combination of User and Password. This is implemented following a Provider model where the default implementation we ship uses our Administration.config file (%windir%\system32\inetsrv\config\administration.config) as the storage for this users. However, you can easily implement a base class to authentication against a database or any other users store if needed. This means you can build your own application and call our API's (ManagementAuthentication).

    Even better in the context of a Web Site running in IIS 7.0 you can actually implement this without having to write a single line of code.

    Disclaimer: Administration.config out-of-the box only has permissions for administrators to be able to read the file. This means that a Web Application will not be able to access the file, so you need to change the ACL's in the file to provide read permissions for your Application, but you should make sure that you limit the read access to the minimum required such as below.

    Here is how you do it:

    1. First make sure that your Web Site is using SSL to use this. (Use IIS Manager and right click your Web Site and Edit Bindings and add an SSL binding).
    2. So that we can restrict permissions further, make your application run in its own Application Pool, this way we can change the ACL's required to only affect your application pool and nothing else. So using IIS Manager go to Application Pools and add a new Application running in Integrated Mode, and give it a name you can easily remember, say WebMgmtAppPool (we will use this in the permissions below).
    3. Disable Anonymous Authentication in your application. (Use IIS Manager, drill-down to your application, double click the Authentication feature and disable Anonymous Authentication and any other authentication module enabled).
    4. Enable the Web Management Authentication Module in your application, you can add a Web.config file with the following contents on it:
      <configuration>
       
      <system.webServer>
         
      <modules>
           
      <add name='WebManagementBasicAuthentication' 
                 type
      ='Microsoft.Web.Management.Server.WebManagementBasicAuthenticationModule, Microsoft.Web.Management, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' />
          </
      modules>
       
      </system.webServer> 
      </configuration> 
    5. Modify the ACL's in the required configuration files:
      1. Give read access to the config directory so we can access the files using the following command line (note that we are only giving permissions to the Application Pool)
        icacls %windir%\system32\inetsrv\config /grant "IIS AppPool\WebMgmtAppPool":(R)
      2. Give read access to the redirection.config:
        icacls %windir%\system32\inetsrv\config\redirection.config /grant "IIS AppPool\WebMgmtAppPool":(R)
      3. Finally give read access to administration.config:
        icacls %windir%\system32\inetsrv\config\administration.config /grant "IIS AppPool\WebMgmtAppPool":(R)
    6. At this point you should be able to navigate to your application using any browser and you should get a prompt for credentials that will be authenticated against the IIS Manager Users.

    What is also nice is that you can use URL Authorization to further restrict permissions in your pages for this users, for example, if I didn't want a particular IIS Manager User (say MyIisManagerUser) to access the Web Site I can just configure this in the same web.config:

    <configuration>
     
    <system.webServer>
           
    <security>
               
    <authorization>
                   
    <add accessType="Deny" users="MyIisManagerUser" />
                </
    authorization>
           
    </security>
     
    </system.webServer>
    </configuration>

    If you want to learn more about remote administration and how to configure it you can read: http://learn.iis.net/page.aspx/159/configuring-remote-administration-and-feature-delegation-in-iis-7/

  • 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:

Page 1 of 1 (4 items)