Using PowerShell to find IIS Sites without https enabled

March 21, 2017

To retrieve all certificates registered in IIS, run this command in PowerShell Administrator mode (source)

import-module WebAdministration
get-childitem iis:SslBindings

To retrieve the bindings (i.e. http and https properties) of IIS sites, run (source)

import-module WebAdministration
get-website | select name,id,state, physicalpath,
@{n="Bindings"; e= { ($_.bindings | select -expa collection) -join ';' }}

I used the last code snippet to find the IIS sites without https / SSL certificate:

import-module WebAdministration 
get-website | where {$_.State -eq "Started"} | 
select name, 
  @{n="Bindings"; e= { ($_.bindings | select -expa collection) -join ';' }} | 
where {$_.Bindings -notmatch "ssl" } 

There is also a nice script finding IIS certificates and whether they are used in an IIS site or not.


Adding SSL Wildcard Certificates to IIS Webs

March 21, 2017

As web browsers start to issue warnings on plain http websites if you are asked to input username/password, it’s time to add SSL certificates even on dev/test servers. We can expect more aggressive warnings in the future ūüėČ

Apparently there is a way to create a self-signed certificate built into IIS (screenshot from Windows Server 2008)

iis create certificate

but this seems to create cerficates only for the host name, not for any domain hosted on the machine.

Back to square one, start up a current Linux machine, and make sure your openssl is newer than version 1.0.1f. (Remember Heartbeed?).

The instructions I found to create self-signed certificates are nearly identical (source, source, source)

openssl genrsa 2048 > my-host.key
openssl req -new -x509 -nodes -sha1 -days 3650 -key my-host.key > my-host.cert
# make sure Common Name starts with "*.", e.g. *.my-host.com
openssl x509 -noout -fingerprint -text < my-host.cert > my-host.info
cat my-host.cert my-host.key > my-host.pem

For use in IIS, you need to create a .pfx from these certificate files:

openssl pkcs12 -inkey my-host.pem -in my-host.cert -export -out my-host.pfx

Copy the .pfx to your IIS machine.

In IIS Manager, select “Server Certificates” on the server node, click “Import…” to import the .pfx certificate.

Start up mmc, “File”, “Add/Remove Snap-in”, select “Certificates”, “Add”, “Computer account”, “Finish”, “OK”, (this click orgy shows you how important certificates were in 2008, as compared to Start/Administrative Tools/Data Sources (ODBC) ūüėČ ) and find the imported certificate(s) under

Console Root\Certificates\Personal\Certificates

Right-click each of them, select Properties, and make sure that the Friendly Name starts with “*.” for wild-card certificates. Otherwise, you cannot assign a host name for https web sites.

Back in IIS Manager, select each site you want to add https support, click Bindings, Add, select Type: https and select the wild-card SSL certificate. Only if the friendly name starts with *, you can/must set the site’s Host name. Click OK and you are done.

If you want your sites to redirect http to https automatically, make sure the Require SSL box is not checked in the site’s SSL Settings.

The minimal web.config to perform these redirects looks like this (source, source)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Redirect-HTTP-HTTPS-IIS">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTPS}" pattern="^OFF$" ignoreCase="true" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" 
            redirectType="Permanent" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Be aware that while these steps enable https for your IIS sites, self-signed certificates still require the users to explicitly accept the certificates in their browsers, which will raise an “Unknown issuer” warning at their first visit.

Update: There also seems to be a Powershell way to do it ūüėČ


Installing AWStats on Windows Server 2012

March 7, 2017

To install AWStats on Windows, first download the current version from awstats.org. If you don’t have Perl on your machine, get Strawberry Perl for Windows, as ActivePerl requires an annual Business License for production use.

On the server, create a web directory and a data directory for awstats. Follow the steps in the AWStats Setup Guide.

To access the log files of a remote IIS, I created a read-only share on c:\inetpub\LogFiles, and had to run

icacls c:\inetpub\LogFiles /reset /t

to allow non-admin access to the IIS log files.

To get Strawberry Perl to run on IIS, follow this Installation Guide:

  • In the Web Server role, you need to have the CGI feature installed.
  • In IIS Administrator, create a web site or application hosting AWStats. In the site or application, you need to add a Script Map for *.pl executing
C:\path\to\perl.exe "%s" %s

Things should be running by now if you browse to

http://myHost/awstats/cgi-bin/awstats.pl?config=mySite

I noticed that the stats only included data from the installation date (IIS logs are configured to daily log files).

Answers on the internetz suggest to merge old log files using logresolvemerg.pl, a script that ships with awstats.

C:\awstats\tools>perl logresolvemerge.pl [all my log files] > merged.log

Replace the LogFile entry in your config file(s) to point to the merged log file

LogFile="C:\awstats-data\merged.log"
#LogFile="\\path\to\LogFiles\W3SVC1\u_ex%YY-1%MM-1%DD-1.log"

and run

perl awstats.pl -config=mySite

again after deleting the previously generated data files.

Unfortunately, the merged log only resulted in “dropped” and “corrupted” records:

Phase 1 : First bypass old records, searching new record...
Searching new records from beginning of log file...
Jumped lines in file: 0
Parsed lines in file: 30376
Found 16100 dropped records,
Found 0 comments,
Found 0 blank records,
Found 14276 corrupted records,
Found 0 old records,
Found 0 new qualified records.

This may be caused by a number of reasons, but it turned out that the merged log requires a specific LogFormat:

LogFormat="%time2 %other %method %url %query %other %logname %host %ua %code %other %other %other"

Finally, I created a batch file awstats.cmd to update all my statistics

net use z: \\host\LogFiles awstats /user:awstats
d:
cd D:\wwwroot\awstats\wwwroot\cgi-bin
perl awstats.pl -update -config=mySite1
perl awstats.pl -update -config=mySite2
...
net use z: /delete

and created a scheduled task to automatically execute the script every day.


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.