Converting a cer file to pfx using the Windows MMC snapin

When using SSL certificates with either Azure or IIS, you'll often have to converting the .cer file obtained from your issuing authority to an password protected encrypted pfx certificate. This can easily be accomplished using the MMC

Opening the certificate snap-in

Load the certificate snap-in

  1. click window + r and type in mmc to run the console
  2. click File | Add / Remove Snap-in...
  3. select certificates
  4. select computer account and next/ok to load the certificate snap-in

Importing the cer certifcate

Once the snap-in is loaded:

  1. Navigate to Certificates (personal computer) | Certificates | Personal right click and select All Tasks | Import
  2. Browse to the file to import and click OK to the personal store

The certificate is now loaded into the store

Exporting the pfx certifcate

The next steps are to export the certificate in the pfx encrypted format

  1. Right click on the loaded certificate and select All Tasks | Export
  2. When prompted select yes, export the private key (note: this is not the default value)
  3. Leave the defaults Personal Information Exchange and the child option Include all certificates in the certificate path if possible selected
  4. Select the Password checkbox and (re)enter a certificate password
  5. Select a file location and click finish

The exported pfx file is not ready to use

You will be prompted will trying to install the certificate on your host of choice

Building and deploying solutions with MSBuild

Automating the the build and deployment process is a real time saver, having a build file also lends itself towards continuous integration. For complex build its ensures no steps are missed and reliable builds are always created.

Below details the common tasks, and tricks I use when creating these build files. Before going into task specifics, there's a few handy options to be aware of first

PropertyGroups

PropertyGroups are an ideal way of storing build properties (which can be thought of as variables). Using a condition parameter allows for configuration specific values

<PropertyGroup Condition="'$(Configuration)'=='UAT'">
  <BuildFolder>UAT</BuildFolder>
  <DeployPath>\\UAT Server\sites\mySite</DeployPath>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Preview'">
  <BuildFolder>Preview</BuildFolder>
  <DeployPath>\\Preview Server\sites\mySite</DeployPath>
</PropertyGroup>

ItemGroups, including and excluding items

An ItemGroup is a collection of items (usually files). The include and exclude parameters provide a powerful way of creating the collection. Wherever possible, use wildcards, so any additional files are automatically included. Any items captured in the exclude will take precedent of the same item being captured in the include

<ItemGroup>
  <files 
    Include="$(path)\**\*.dll;
      $(path)\**\*.svc;
      $(path)\**\*.aspx;
      $(path)\**\*.asax;
      $(path)\**\*.js;
      $(path)\**\*.htm;
      $(path)\**\*.png;"
      
    Exclude="$(path)\obj\**\*.*
      $(path)\**\app_offline.htm;
    " />	
</ItemGroup>

In the above example, all htm files will be included, except app_offline.htm. As new htm files appear, they are automatically picked up by the wildcard matches

Selecting a folder and all its contents (including sub folders)

While it would seem logical to think *.* on a given folder would do this, there's actually a non-intuitive pattern which requires an addtional \**. e.g. to select everything in the $(OutputPath) folder, the syntax is

<ItemGroup>
  <files Include="$(OutputPath)\**\*.*" />
</ItemGroup>

The following explanation is taken from this msdn article MSBuild Items

  1. The ? wildcard character matches a single character
  2. The * wildcard character matches zero or more characters
  3. The ** wildcard character sequence matches a partial path

Feedback with the Message Task

Providing feedback at any point in the build process is beneficial (especially for debugging). This can be achieved using the Message task

<Message Importance="high" Text="--- copying (Destination: $(OutputPath)) ---"/>

The build file implementation

The Task order

Typical task for a build file are below

<Target Name="Build">
  <CallTarget Targets="Clean" />
  <CallTarget Targets="Compile" />
  <CallTarget Targets="Copy" />
  <CallTarget Targets="Configure" />
  <CallTarget Targets="Compress" />
  <CallTarget Targets="Deploy"  />
</Target>
  1. Clean - Clean the contents of the output folder
  2. Compile - Build the solution
  3. Copy - Copy the built solution to the output folder
  4. Configure - Apply the transforms to the web.config file
  5. Compress (optional) - Zip the contents, possibly for a definitive software library
  6. Deploy (optional) - for test or staging environments you may wish to copy over to the site

Cleaning (deleting) previous build output

using a combination of Delete and MakeDir you output folder can be prepared

<ItemGroup>
  <filesToDelete Include="$(OutputPath)\**\*" />
</ItemGroup>

<Delete Files="@(filesToDelete)" />
<MakeDir Directories = "$(OutputPath)" Condition = "!Exists('$(OutputPath)')" />

Building the solution using the MSBuild task

When using the MSBuild task, you can pass in the path to the solution file (.sln) and all projects within that solution will be built. This will work with smaller solutions, but with the larger (or legacy) solutions, there are often projects you do not wish to include in the build. A more flexible approach its to explicitly list which projects you wish to build in an item group

<ItemGroup>
  <ProjectReferences 
    Include="..\**\*.*proj" 
    Exclude="..\**\*Utility.*proj;
      ..\**\*UnloadedProject.*proj;"
       />
</ItemGroup>

<MSBuild 
  Projects="@(ProjectReferences)" 
  BuildInParallel="false"
  Targets="Build" 
  Properties="Configuration=$(Configuration);Optimize=true"
  ContinueOnError="false" 
  StopOnFirstFailure="false" />

Copy build output to an output folder

Using the Copy task and the %(RecursiveDir) metadata the whole output tree can be copied

<ItemGroup>
  <files 
    Include="$(path)\**\*.dll;
      $(path)\**\*.svc;
      $(path)\**\*.aspx;
      $(path)\**\*.asax;
      $(path)\**\*.js;
      $(path)\**\*.htm;
      $(path)\**\*.png;"
      
    Exclude="$(path)\obj\**\*.*
      $(path)\**\app_offline.htm;
    " />	
</ItemGroup>

<Copy 
  SourceFiles="@(files)" 
  DestinationFolder="$(OutputPath)\%(RecursiveDir)"
  />

The msdn article MSBuild Well-known Item Metadata states If the Include attribute contains the wildcard **, this metadata specifies the part of the path that replaces the wildcard.. This translates to the %(RecursiveDir) metadata being replaced with the path being derived from the ** wildcard.

Leveraging Visual Studio's build transformations

Visual studio's web.config transformations can be incorporated into your build file by referencing the TransformXml task. This allows you to leverage all existing transforms. Using these transforms is often easier than using xml update tasks

Web.config Transformation file
web.release.config
<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <connectionStrings>
    <add name="cnn" connectionString="Data Source=;Initial Catalog=;User ID=;Password=" xdt:Transform="Replace"/>
  </connectionStrings>
  
  <system.web>
    <compilation xdt:Transform="RemoveAttributes(debug)" />

    <customErrors  mode="On" defaultRedirect="~/Error/Generic" xdt:Transform="Replace">
      <error statusCode="404" redirect="~/Error/NotFound" />
    </customErrors>
    
  </system.web>
</configuration>

For the above transform, this can be incorporated by referencing the task at the top of the build file

build.xml
<Project 
  ToolsVersion="4.0" 
  DefaultTargets="Default" 
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  
<UsingTask 
    TaskName="TransformXml"
    AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

Then, adding and configuring the TransformXml task

<TransformXml
  Source="$(SourcePath)\web.config"
  Transform="$(TransformPath)\Web.$(Configuration).config"
  Destination="$(DestinationPath)\web.config"
  StackTrace="false" />

Zipping the build output

Using the MSBuild Community Tasks Project Zip task, this is as simple as

<Zip 
    Files="@(zipFiles)"
    WorkingDirectory="$(OutputPath)\"
    ZipFileName="$(OutputPath)\$(DeployName) $(Version) - compiled.zip"/>

Running the build file

The build file can be called from a batch file, passing in the required arguments. The options details below can be found at MSBuild Command-Line Reference

ReleaseBuild.bat
@echo off
call "%VS100COMNTOOLS%\vsvars32.bat" 

set /p version=Enter version (n.n.n.n):
set /p deploy=Deploy (y/n):
 
 
@echo on
msbuild build.xml /p:Configuration=Release /p:Version=%version% /p:Deploy=%deploy%  /t:build /verbosity:m /nodeReuse:True 
  /fl /flp1:logfile=errors.txt;errorsonly

@pause

Configuring IIS8 for hosting a classic ASP application

When porting legacy a legacy asp application to IIS8 there is a number of additional configuration steps required. Classic asp doesn't automatically run. Once you have this configure, you'll need to test (and possibly debug) the application before deploying to production. When you first try to browse a asp application in IIS8, you'll be presented with a screen citing an extension configuration error.

IIS8 Extension Configuration Error

This is because classic asp (and handlers) are not installed. These features need to be added to the web server role.

Adding ASP feature to web Server role dialog

Once these features are added, you'll need to test you site works as expected. Unfortunately if any errors are encountered, you are prompted with an user friendly message, rather than the error itself.

IIS8 Classic asp script error message

To send error messages to the browser, additional ASP configuration is required within Internet Information Services Manager. This can be configured at the server or site level. Select the "ASP" button, drill down into the debugging properties and set the Send errors to browser property to true.

ASP configuration button in IIS8 Send errors to browser property

You'll now receive the familiar error feedback when refreshing the browser

Enabling Failed Request Tracing

This can be be taken a step further by installing the Tracing feature and enabling Failed Request Tracing for a given status code (typically 500)

Breaking this down

  1. Install the Tracing feature (under health and diagnostics)

    Adding the tracing feature in IIS8
  2. Configure the Tracing

    Configure tracing in site actions Failed tracing request button in IIS8 Enable failed request tracing in IIS8
  3. Add a Tracing rule

    New failed request trace rule - specify content New failed request trace rule - define conditions Failed trace request site rules

Once this is configured there should be a new folder within C:\inetpub\logs called FailedReqLogFiles which will capture all the failed requests in XML log files.

FailedReqLogFiles folder within logs

Now, when you refresh the site, you'll be present with the standard HTTP Status code 500, but if you browse to the FailedReqLogFiles you get a detailed breakdown of whats going on

HTTP Error Status 500 Classic asp failed tracing request log

A detailed post Using Failed Request Tracing to troubleshoot Classic ASP errors explains how to make the most of this tracing feature

Removing SSL2 support from your secure sites

Modern browsers will attempt to negotiate the highest possible version of SLL, however a Man in the middle attack may try to downgrade your connection to the weakest protocol, making you site vulnerable.

By adding the reg keys below, support for SSL2 will be removed from IIS

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0]

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Client]
"Enabled"=dword:00000000

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server]
"Enabled"=dword:00000000

You can then test the security of your site by using SSL Labs, hopefully it looks something like below.

SSL Labs Report Card

Note: You should always back up your registry before making any changes, the author does not accept any responsibility for system or data loss.

Url Rewrites with Microsoft URL Rewrite Module 2.0 for IIS 7

Common rewriting tasks include redirecting to a naked domain (without the www prefix) or redirecting to https. Using the Microsoft URL Rewrite Module for IIS, your site can implement redirect rules in the web.config.

At the time of writing, the module download was here, failing that, searching for Microsoft URL Rewrite Module 2.0 for IIS 7 should return the correct download

After installing, a new Url Rewrite feature will be available

Default website in IIS7

Rewrites can be entered through the UI, or via the web.config

Edit Url Rewrite Inbound Rule in IIS7

There are plenty of resources available on the web for the authoring the rules. Below are 2 rules I have implemented recently, which use a common pattern

Redirect to https

<system.webServer>
        <rewrite>
            <rules>
                <rule name="Redirect to https" stopProcessing="true">
                    <match url=".*" />
                    <conditions>
                        <add input="{HTTP_HOST}" pattern="^(www\.)(.*)$" />
                    </conditions>
                    <action 
                      type="Rewrite" 
                      url="https://{C:2}/{R:0}" 
                      redirectType="Permanent" />
                </rule>
            </rules>
        </rewrite>
</system.webServer>

Redirect to naked

<system.webServer>
    <rewrite>
      <rules>
        <rule name="Redirect to naked" stopProcessing="true">
          <match url=".*" ignoreCase="true" />
          <conditions>
            <add input="{HTTP_HOST}" pattern="^(www\.)(.*)$" />
          </conditions>
          <action 
            type="Redirect" 
            url="http://{C:2}/{R:0}" 
            redirectType="Permanent" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>