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
- click window + r and type in mmc to run the console
- click File | Add / Remove Snap-in...
- select certificates
- select computer account and next/ok to load the certificate snap-in
Importing the cer certifcate
Once the snap-in is loaded:
- Navigate to Certificates (personal computer) | Certificates | Personal right click and select All Tasks | Import
- 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
- Right click on the loaded certificate and select All Tasks | Export
- When prompted select yes, export the private key (note: this is not the default value)
- Leave the defaults Personal Information Exchange and the child option Include all certificates in the certificate path if possible selected
- Select the Password checkbox and (re)enter a certificate password
- 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
- The ? wildcard character matches a single character
- The * wildcard character matches zero or more characters
- 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>
- Clean - Clean the contents of the output folder
- Compile - Build the solution
- Copy - Copy the built solution to the output folder
- Configure - Apply the transforms to the web.config file
- Compress (optional) - Zip the contents, possibly for a definitive software library
- 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.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.
This is because classic asp (and handlers) are not installed. These features need to be added to the web server role.
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.
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.
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
Install the Tracing feature (under health and diagnostics)
Configure the Tracing
Add a Tracing rule
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.
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
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.
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
Rewrites can be entered through the UI, or via the web.config
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>