Tuesday, October 19, 2010

Modifying a Visual Studio web setup program to include a physical directory

When I was working on creating a new web application for a customer, I ran into a problem with the setup program. The default Web Setup project for VS2008 does not allow you to specify the physical install location for your application, but instead installs it in c:\inetpub\wwwroot. This wasn't going to work for my customer, who needed to be able to specify the physical directory. I could have used a regular setup project, which allows the end user to specify the physical install directory, but then the setup program wouldn't take care of creating the virtual directory in IIS. After some hunting, I found a solution that allows you to modify the setup MSI file so that the user can specify the physical install directory.

The process of modifying the MSI file is rather tricky, since it essentially involves modifying the MSI file once while Orca records your actions. I screwed it up the first time and had to start again. The shortcut is that you're welcome to use the MST file I created with this process to save yourself the trouble of creating the MST.

Now for the steps:

Obtain Orca.exe
Orca is part of the Windows SDK which can be downloaded at this link. After downloading and installing the SDK, browse to "Program Files\Microsoft SDKs\Windows\v7.0\Bin" and run Orca.msi to install the Orca utility.

Create the MST to be used to modify the MSI
These instructions are from this codeproject link. This assumes you've already created the MSI for the Web Setup project.
We'll use Orca to essentially record the changes you make to an MSI file; these changes will be saved in an MST file which you can then apply to other MSI files to perform the same changes.
  1. Run orca.exe
  2. Use File - Open to open the MSI
  3. Under Tools - Options - Database check Copy embedded streams during 'Save As'
  4. Under the Transform menu select New Transform to start recording a transform.
  5. In the screen shot image on the right (click to view it full-size) are shown 5 tables. Add the rows outlined in green to each table (Control, ControlEvent, CustomAction, InstallExecuteSequence, InstallUISequence).
  6. In the Control table, the Control_Next must refer to another control on that dialog, and all the controls must refer to each other in a loop. Since we added some extra controls to the WebFolderForm dialog, we need to modify one of the existing controls to refer to one of our new controls. Modify the AppPoolsCombo control (on the WebFolderForm dialog) and set the Control_Next column to be InstallLabel.
  7. We also have to remove some rows that would otherwise set the TARGETDIR back to the default:
    • In ControlEvent delete the row that has Dialog=WebFolderForm and Control="Next" and Event="DoAction"
    • In InstallExecuteSequence delete the WEBCA_EvaluateURLs action.
    • In InstallUISequence delete the WEBCA_EvaluateURLsNoFail action.
    • You're now done recording the Transform. From the menu choose Transform - Close Transform and save the transform (it will have a .MST extension).
A shortcut
This MST file was generated using the steps above for a VS2008 web setup project and should be able to be used as is to apply this transform to web setup MSI files.

Applying the transform
Now that you have an MST (transform) file, you need to use it to modify the setup MSI file.

You can apply a transform using Orca; open the MSI you want to transform, then from the menu choose Transform - Apply Transform, select the .MST file to use and it will modify the MSI. You can then save the MSI file.

You can also apply the transform via the command line using MsiTran.exe, which is installed with the Windows SDK (in Program Files\Microsoft SDKs\Windows\v7.0\Bin).

Applying the transform as part of a TFS (Team Foundation Server) build
In my case, I wanted to have this automatically done as part of the TFS (Team Foundation Server) build process we use. That's actually fairly simple. To apply the transform as part of a TFS build, copy the msitran.exe and your MST transform file to the build server. You can then modify the TFSBuild.proj file by adding an Exec such as the following to the AfterCompile target:

<Exec Command="c:\bin\msitran.exe -a c:\bin\WebSetupWithPhysicalDir.mst "$(OutDir)Setup\WrtServicesSetup.msi""/>

When the setup program is installed on a Windows server, which can have multiple websites and application pools, the install screen should look something like this:

Tuesday, September 7, 2010

Buttons tweak for Thunderbird 3.1.x

Like a few other Thunderbird users, I've found the Buttons! add-on to be extremely useful. Unfortunately, it hasn't been updated in a while, and it won't work with the latest update to Thunderbird. (It worked in 3.0.x, but not in 3.1.x)

I decided to try the easy way out to see if I could get it to work. I unzipped the Buttons XPI file, modified the maximum compatible version in the install.rdf file, and rezipped it. Seems to be working fine for me. It's nice to have my Previous! and Next! buttons back again!

For those interested in using the version I tweaked, here's a link to the buttons XPI. (yes, I added a "b" to the version number)

Saturday, July 11, 2009

Using OpenSSL to create a certificate authority on Windows

I've used OpenSSL to create certificates for testing on my windows machines. I'll outline the basic steps I followed to setup OpenSSL and use it to sign certificate requests. I found Dylan Beattie's how-to very useful in getting this working.

These instructions assume Windows XP, and that you have some basic knowledge of the IIS web server.

First, install OpenSSL. You can download a Windows installer and binaries here.

Initial Setup
Create a working directory, we'll assume c:\openSSL\work. Then create the following folder structure:

Create a config file, openssl.conf in the work directory using this content:

# SSLeay example configuration file.
# This is mostly being used for generation of certificate requests.



[ ca ]
default_ca = CA_default # The default ca section

[ CA_default ]

certs = certs # Where the issued certs are kept
crl_dir = crl # Where the issued crl are kept
database = database.txt # database index file.
new_certs_dir = certs # default place for new certs.

certificate = cacert.pem # The CA certificate
serial = serial.txt # The current serial number
crl = crl.pem # The current CRL
private_key = private\cakey.pem # The private key
RANDFILE = private\private.rnd # private random number file

x509_extensions = x509v3_extensions # The extentions to add to the cert
default_days = 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md = md5 # which md to use.
preserve = no # keep passed DN ordering

# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy = policy_match

# For the CA policy
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = match
commonName = supplied
emailAddress = optional

# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ req ]
default_bits = 1024
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes

[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
localityName = Locality Name (eg, city)
0.organizationName = Organization Name (eg, company)
organizationalUnitName = Organizational Unit Name (eg, section)
commonName = Common Name (eg, your website's domain name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 40

[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20

[ x509v3_extensions ]
# under ASN.1, the 0 bit would be encoded as 80
# nsCertType = 0x40

Create an empty database.txt file in the work directory.

Create a serial.txt file in the work directory that contains the following (the numbers 01 followed by RETURN):


Create Certificate Authority Stuff

  1. Create a 1024-bit private key for use in creating the CA (this will prompt you for a password; remember it, as you'll need it when you're signing certs!):
    ..\bin\openssl genrsa -des3 -out keys\ca.key 1024
  2. Create a master certificate based on this key (for use in signing other certs):
    ..\bin\openssl req -config openssl.conf -new -x509 -days 1001 -key keys\ca.key -out certs\ca.cer
  3. Export the CA cert in a DER file for windows users to import into their Trusted Root Store:
    ..\bin\openssl x509 -in certs\ca.cer -outform DER -out certs\ca.der

At this point your CA should now be setup.

Create a batch file for handling certificate requests

Since you'll presumably be handling many certificate requests, here's a windows batch file to automate the process (we'll name it "ca_create_server_cert.bat"):

@echo off
REM This batch file is used to create server certificates from certificate request files.
REM USAGE: ca_create_server_cert.bat [inputfilename] [outputfile]
REM If either of the command line paramters are missing, you will be prompted for it.

SET basedir=c:\openssl\
if "%1"=="" (
SET /P requestfile="Enter certificate request filename (should already be in %basedir%\work\requests): "
) ELSE (
SET requestfile=%1

if "%2"=="" (
SET /P outputfile="Enter output filename (with no extension): "
) ELSE (
SET outputfile=%2

REM change to the work directory
cd %basedir%\work

echo requestfile=%requestfile%
echo outputfile=%outputfile%
echo binpath=%binpath%

REM create the certificate
%basedir%bin\openssl ca -policy policy_anything -config openssl.conf -cert certs\ca.cer -in requests\%requestfile% -keyfile keys\ca.key -days 730 -out certs\%outputfile%.cer.TMP

REM convert it to an x509 format cert for IIS
%basedir%bin\openssl x509 -in certs\%outputfile%.cer.TMP -out certs\%outputfile%_x509.cer

echo If there were no error messages, the new certificate is located in:
echo %basedir%work\certs\%outputfile%_x509.cer

Signing a server certificate request

We'll use IIS as the server in this example.

  • First create the server certificate request in IIS
  • Place the request file in the "requests" directory
  • Run ca_create_server_cert.bat and follow the prompts
  • Take the output certificate (.cer) file and install it in IIS

Friday, July 10, 2009

Easy management of windows certificates

Yes, you can access certificate management through IE via tools--internet options--content--certificates, but there's a quicker way:
  1. Start--Run--type "MMC" and hit [ENTER]
  2. File--Add/Remove Snap-In
  3. Click the Add button
  4. Select "Certificates" and click Add
  5. Select "My User Account" and click Finish
  6. Select "Certificates" and click Add
  7. Select "Computer Account" and click Finish
  8. Click OK
  9. Your window should now display certificates for the current user and the computer
  10. Use File--Save As and save it in Administrative Tools (the default) as Certificates.msc.
Now you'll have the Certificates.msc under Administrative Tools (in your Start menu) that will bring you straight to this screen:

Sunday, June 28, 2009

Creating Custom Data Generators in Visual Studio 2008

Recently I was working on creating some test data in Visual Studio 2008. I created a Data Generation Plan, and used the Regular Expression Data Generator quite a bit, but found myself wanting more options to create data. VS2008 has that option available: write some custom Data Generators.

I quickly headed down that path, which was pretty straightforward. But when it came time to install my custom data generators into VS2008, I ran into problems. Apparently there are multiple versions of VS2008 out there, some of which expect the DLL and XML files in different places. A question I posted on the MSDN Forums resulted in an answer that proved to be quite helpful.

I was using VS2008 Team Suite, and here's the process I used:

Create a new project
The first step is to create a new VS2008 DLL project. In that project, create your custom data generator, such as this sample custom date generator:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TeamSystem.Data.DataGenerator;

namespace CustomDataGenerators
/// This class generates random DateTimes in the specified range.
/// User specifies the StartingDays (the number of days to add or subtract to today's
/// date to get the beginning of the range) and the DateRange (number of days past
/// that date in which random DateTimes will be selected).
public class DateGenerator : Generator
private DateTime _rndDate;
private DateTime _secondDate;
private int _startDays = -30;
private int _dayRange = 60;
private int _daysToSecondDate = 30;
private int _secondDateDayRange = 0;
Random _rnd = null;

private Random RandomObject
if (_rnd == null)
_rnd = new Random(base.Seed);
return _rnd;

[Input(Name="Starting Days",
Description="Number of days to add (or subtract) from today's date to set the bottom end of the date range",
public int StartingDays
set { _startDays = value; }
get { return _startDays; }

[Input(Name = "Days in Range",
Description = "Number of days in the date range",
DefaultValue = 60)]
public int DateRange
set { _dayRange = value; }
get { return _dayRange; }

[Input(Name = "Second Date Days",
Description = "Number of days after the first date for the second date",
DefaultValue = 30)]
public int DaysToSecondDate
set { _daysToSecondDate = value; }
get { return _daysToSecondDate; }

[Input(Name = "Second Date Random Days",
Description = "Number of days after 'Second Date Days' to create a range in which the Second Date will be randomized. Leave at zero for a set second date.",
DefaultValue = 0)]
public int DayRangeSecondDate
set { _secondDateDayRange= value; }
get { return _secondDateDayRange; }

[Output(Name="Output", Description="A random date/time string in the specified date range")]
public DateTime Output
get { return _rndDate; }

[Output(Name="SecondDate", Description="A second date/time string X number of days past the first random output date")]
public DateTime SecondDate
get { return _secondDate; }

protected override void OnGenerateNextValues()
_rndDate = DateTime.Now;
_rndDate = _rndDate.AddDays(_startDays);
int randomHours = RandomObject.Next(0, _dayRange*24);
_rndDate = _rndDate.AddHours(randomHours);
_secondDate = _rndDate.AddDays(_daysToSecondDate);
if (_secondDateDayRange != 0)
{ //randomize the 2nd date
int hrs2 = RandomObject.Next(0, _secondDateDayRange * 24);
_secondDate = _secondDate.AddHours(hrs2);
Compile the project to make sure it works.

Write the XML descriptor
Add an XML file to your project, name it DLLNAME.extensions.xml (where DLLNAME is the exact filename of your DLL, without the ".DLL") and structure it as follows:

<extensions assembly="CustomDataGenerators, Version=, Culture=neutral, PublicKeyToken=95510968c816b5ce" version="1" xmlns="urn:Microsoft.VisualStudio.TeamSystem.Data.Extensions" xsi="http://www.w3.org/2001/XMLSchema-instance" schemalocation="urn:Microsoft.VisualStudio.TeamSystem.Data.Extensions Microsoft.VisualStudio.TeamSystem.Data.Extensions.xsd">
<extension type="CustomDataGenerators.DateGenerator" enabled="true">

If you create more data generator classes, add a new "" tag for each class.

Copy the DLL and XML file into your VS2008 application directory
Now it's time to copy the DLL and the extensions XML file into the VS2008 application directory so that VS2008 can use them in your data generation plan. In my case, I have CustomDataGenerators.DLL (should be in bin\debug under your project directory) and CustomDataGenerators.Extensions.XML. They go into the following directories (assuming that you installed VS2008 in c:\Program Files):

CustomDataGenerators.Extensions.xml: c:\Program Files\Microsoft Visual Studio 9.0\DBPro
CustomDataGenerators.DLL: c:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\Private Assemblies

Restart VS2008 and use your custom data generator
VS2008 needs to be restarted for it to see the custom data generator. If there's a problem with it, you should get an error message. Otherwise, open your data generation plan and you should have your custom data generator(s) available in the drop downs for appropriate fields.

Saturday, June 27, 2009


Over my years in the tech industry, I've benefited from tips I've found on countless blogs, usually through googling a specific problem I was facing. I've also found that after I solve a problem, if I don't have to deal with it again for a couple months, I've usually forgotten exactly what I did to solve it.

So I'm starting this blog to give me a place to record my thoughts in a place that has better retention than my memory! And perhaps others will find the solutions and musings occasionally useful as well.