Finding Deleted IIS Application Directories

October 13, 2016

If you develop web applications for IIS, or administrate IIS and file systems, you sooner or later end up with orphaned IIS application which point to non-existing directories.

To get an overview of IIS applications, start up PowerShell in administrator mode (I prefer PowerShell ISE) and run

import-module webadministration
dir "IIS:\\sites\Default Web Site"

To exclude any files that reside in IIS root, filter out the “file” entries:

import-module webadministration
dir "IIS:\\sites\Default Web Site" | where { $_.NodeType.Trim() -ne "file" }

Finally, we test the Physical Path property of the resulting entries

import-module webadministration
dir "IIS:\\sites\Default Web Site" 
    | where { $_.NodeType.Trim() -ne "file" 
        -and ![System.IO.Directory]::Exists( $_.PhysicalPath ) }

This command lists all IIS web application and virtual root directories which point to non-existing directories. The result looks like this:

Type        Name         Physical Path 
----        ----         ------------- 
application application1 D:\app\app1
application web2         D:\app\web2

Enabling CORS in IIS7 Web Application

April 8, 2016

Cross-origin HTTP request are restricted to (from MDN)

  • The only allowed methods are:
    • GET
    • HEAD
    • POST
  • The only allowed values for the Content-Type header are:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

I needed to grant a local web application the ability to POST JSON-formatted data, which is clearly beyond the Content-Type restrictions above.

Without modifying the application, I noticed OPTION requests instead of POST requests to the local URL.

As it turns out, an OPTIONS request is sent before a cross-origin POST (if it is not a “simple” request) to check whether the webserver allows a POST, given origin, method, content type, and HTTP headers:

Preflighted requests

Unlike simple requests (discussed above), “preflighted” requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send.  Cross-site requests are preflighted like this since they may have implications to user data.

Fortunately, a single thread on SO helped me enable CORS but simply editing the application’s web.config file. The <system.webServer> section needs to contain the following lines:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule"/>
  </modules>
  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Methods" value="GET,POST,OPTIONS" />
      <add name="Access-Control-Allow-Headers" value="Content-Type" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

The <remove> entry disables the standard handler (WebDAV) for the OPTIONS verb . The <customHeaders> section contains all allowed HTTP header (Access-Control-Allow-*), and Access-Control-Allow-Headers specifies that JSON is recognized as valid Content-Type.


Deploying ASP.Net MVC 5 on Windows Server 2008

December 5, 2014

Developing an MVC application in Visual Studio 2013 (Update 3), I needed to install a demo on a Windows 2008 server.

Since Server 2008 ships with .Net 3, we first need to install .Net 4.5.1, either from the Visual Studio download page, or from MSDN.

After the required reboot and setting up a web application in IIS, browsing to the new site resulted in HTTP errors 403 (refers to directory browsing) and 404 (when navigating to a specific controller action).

Luckily, this issue could be solved by re-adding <modules> to the <system.webServer> section (found on SO):

<system.webServer>
  <modules>
    <remove name="UrlRoutingModule-4.0" />
    <add name="UrlRoutingModule-4.0" 
        type="System.Web.Routing.UrlRoutingModule" 
        preCondition="" />
  </modules>
</system.webServer>

After editing the web.config, the web application could be accessed, but all CSS and JavaScript requests, which are served using Bundling and Minification, would result in a 404.

Again, another module wanted to be included

      <remove name="BundleModule" />
      <add name="BundleModule" type="System.Web.Optimization.BundleModule" />

Finally, the web application looked as expected, so I logged in, and

No owin.Environment item was found in the context.

The internets are full of helpful tips to add

<add key=”owin:AppStartup” value=”[Namespace].Startup, [AssemblyName]” />

but that did not change anything. What really solved that last problem was to add the attribute

<modules runAllManagedModulesForAllRequests="true" />

in web.config.

In the end, the web.config section looks like this

  <system.webServer>
    <modules  runAllManagedModulesForAllRequests="true">
      <remove name="FormsAuthentication" />
      <remove name="UrlRoutingModule-4.0" />
      <add name="UrlRoutingModule-4.0"
          type="System.Web.Routing.UrlRoutingModule" 
          preCondition="" />
      <remove name="BundleModule" />
      <add name="BundleModule" 
          type="System.Web.Optimization.BundleModule" />
    </modules>

Retrieving all IIS Directories using PowerShell

January 4, 2014

To tidy up the applications on the IIS of my development PC, I wanted to find out which IIS application (and their virtual directories) point to which physical paths.

Starting out with this SO answer and some help from MSDN I quickly assembled this PS solution for the task:

$dummy = [System.Reflection.Assembly]::LoadFrom(
    "c:\windows\System32\InetSrv\Microsoft.Web.Administration.dll")

$srvmgr = new-object Microsoft.Web.Administration.ServerManager

write-Output  ("site`tstate`tapp`tvirtdir`tphys.path")

foreach($site in $srvmgr.Sites | Sort-Object Name ) {
  foreach($app in $site.Applications | Sort-Object Path ) {
    foreach($virt in $app.VirtualDirectories | Sort-Object Path ) {

      write-Output  ($site.Name + "`t" + $site.State + "`t" + 
        $app.Path + "`t" + $virt.Path + "`t" + $virt.PhysicalPath)

    }
  }
}

This piece of code generates a tab-separated list of physical paths referenced by IIS sites, applications, and virtual directories. The application’s physical path is contained in a virtual directory with Path=”/”.

Note that the script must be “run as administrator” from the PS prompt or the PowerShell ISE.


Browsing Web Directories using C#

October 31, 2012

To allow directory browsing in IIS, you need to navigate to the web site in IIS Manager, click the Browse Directory icon, and click the Activate button to the right.

Directory browsing generates an HTML view on the file system. Unfortunately, the HTML is not standardized and may different between different versions of IIS, and other web servers.

In case of IIS 7.5, the typical output looks like this:

<A HREF="/path/">[To Parent Directory]</A><br>
18.09.2011 13:44 25266 <A HREF="/path/images/foo.jpg">foo.jpg</A><br>
18.09.2011 13:44 &lt;dir&gt; <A HREF="/path/images/subdir/">subdir</A><br>
  • The first line links to the parent directory.
  • The seconds line is a file contained in the requested directory.
  • The third line is a subdirectory below the requested directory.

To parse the files and directories, we need two separate regular expressions:

&lt;dir&gt;\s*\<A\s+HREF=""(?<url>.+?)""\>(?<name>.+?)\</A\>
\d+\s*\<A\s+HREF=""(?<url>.+?)""\>(?<name>.+?\.(png|gif|jpg|jpeg))\</A\>

This solution is based on an answer on SO, but with the following changes

  • added the distinction between files and directories
  • fixed the regex to non-greedy
  • added “name” and “url” group names
var request = (HttpWebRequest)WebRequest.Create(url);
using (var response = (HttpWebResponse)request.GetResponse())
{
  using (var reader = new StreamReader(response.GetResponseStream()))
  {
    string html = reader.ReadToEnd();

    var rexDir = new Regex(
      @"&lt;dir&gt;\s*\<A\s+HREF=""(?<url>.+?)""\>(?<name>.+?)\</A\>", 
      RegexOptions.IgnoreCase);
    var rexFile = new Regex(
      @"\d+\s*\<A\s+HREF=""(?<url>.+?)""\>(?<name>.+?\.(png|gif|jpg|jpeg))\</A\>", 
      RegexOptions.IgnoreCase);

    var matchedDirs = rexDir.Matches(html);
    if (matchedDirs.Count > 0)
    {
      Console.WriteLine("dirs");
      foreach (Match m in matchedDirs)
      {
        Console.WriteLine(m.Groups["name"] + ": " + m.Groups["url"]);
      }
    }

    var matchedFiles = rexFile.Matches(html);
    if (matchedFiles.Count > 0)
    {
      Console.WriteLine("files");
      foreach (Match m in matchedFiles)
      {
        Console.WriteLine(m.Groups["name"] + ": " + m.Groups["url"]);
      }
    }
  }
}

Windows Update killed my ABCpdf

April 2, 2012

An application I develop (dev environment: Win7 Pro, IE9, ASP.Net 3.5) suddenly showed problems running on Windows Server 2008: the web application could not create PDF documents anymore, and

  • The browser only displayed a “Service Unavailable” message
  • The browser user was automatically logged out of the web application
  • The web request did not show in the IIS log
  • No error in the Application events

A process serving application pool ‘MyAppPool’ suffered a fatal communication error with the Windows Process Activation Service. The process id was ‘2688’. The data field contains the error number.

The details tab displayed the following information:

ProcessID 2688
6D000780

Binary data:

In Words

0000: 8007006D

In Bytes

0000: 6D 00 07 80

Teh Internets wanted me to debug IIS and my .Net application with IIS Diagnostics Toolkit, and Debug Diagnostic Tool, and IISState, and awe-inspiring how-to instructions.

Instead, I chose to add logging statements to the code that was affected by the error, and found that cause to be the statement

int theID = doc.AddImageHtml(html);

As it turns out (thank you SO, also here), you need to activate the Gecko engine in ABCpdf to work on (some) IE9 machines (as I said, the code works ok on Win7Pro, and I had recently updated the server):

doc.HtmlOptions.Engine = EngineType.Gecko;

Trying out the Gecko engine, the next result I got was

WebSupergoo.ABCpdf8.Internal.PDFException: ABCpdf cannot detect any printers. Gecko Engine requires a printer installed in the system. Usually, XPS Document Writer would suffice. Try also running the service as an interactive user. —> System.ComponentModel.Win32Exception: The RPC server is unavailable
at System.Drawing.Printing.PrinterSettings.get_InstalledPrinters()
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.get_DefaultPrinter()
— End of inner exception stack trace —
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.get_DefaultPrinter()
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.Preflight(Doc doc)
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.AddHtml(Doc doc, String html, Boolean paged, Int32 width, String& err)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html, Boolean paged, Int32 width, Boolean disableCache)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html)

After creating an XPS printer in control panel, the web application was finally able to generate PDF documents again.

According to the ABCpdf support page (6.29), the MSHTML rendering engine should be avoided for future applications:

Unfortunately with the official release of IE9 Microsoft released new documentation which says the IHTMLElementRender::DrawToDC function that was required has been deprecated. This is especially unfortunate given that there is no replacement for this function.

Given that Microsoft appears to be unwilling to support these interfaces we would strongly recommend that on new deployments you consider a move to the new Gecko-based HTML engine available in ABCpdf 8.


Running 32-bit ABCpdf Pro on 64-bit Windows

December 21, 2011

An application I have been developing needed to add professional generation of PDF documents (read: my home-brew translation of HTML to PDF using iTextSharp did not produce decent results). I decided to use ABCpdf which itself uses the MSHTML engine to render HTML and miraculously converts the result to PDF.

The application consists of a web application and a Windows service which both reference a library implementing the HTML generation and the invocation of ABCpdf.

Development and deployment on 32-bit server were successful, but a production system running Windows Server 64-bit caused several errors:

Exception System.DllNotFoundException

Unable to load DLL ‘ABCpdf8-64.dll’. The specified module could not be found (Exception from HRESULT: 0x8007007E)

This error was caused by the service running in 64-bit mode. (The web application had already been switched to 32-bit only by restricting the application pool to 32-bit execution). After setting the service exe’s “Platform target” property to x86, the error went away.

Exception WebSupergoo.ABCpdf8.Internal.PDFException

Cannot activate MSHtml engine. Please refer to documentation for more information.

The solution for this exception is to set the AppPool’s “Load User Profile” property to true in the Advanced Settings dialog.

After applying these settings, PDF generation also executed successfully on the 64-bit machine.