Saturday, 6 June 2009

PGP Encryption with C#

Recently at work, I had to come up with a deployment framework for a legacy app we use. The initial requirement was to simply copy the app from a network share onto users desktops. There was also a requirement to provide a level of security around the deployment itself, so to me, an anonymous network share didnt seem to be the correct way of releasing this app... A much more simpler way to provide at least a small level of security would be make the deployment framework download the app as an archive from a webserver on the domain. This, at least, means that only users with authorisation to access the domain would be able to download the app, and they would only be able to download it whilst they were logged in.

Then I was told that there will be some instances that field workers will need to download parts of the app whilst they are out and about doing whatever it is they do. I had already planned to compress the app as an archive, but now I needed a way to secure the archive itself. One of my colleagues suggested that I put a password on the archive, its not a daft suggestion to make, it would imply that only people with the password would be able to extract the data from the archive itself. But, I pointed out that this doesnt prove that the archive was one created by us - all it does it password protect an archive. Plus, we would need to communicate the password to those that needed it in a secure manner, which in turn would mean we couldnt use the same password all the time etc. I asked if we already use some form of secure signature and got told we use PGP - result. I explained to my colleagues that yes, password protecting the archive would mean that only password holders could extract from the archive, but this wouldnt mean they are authorised to hold the password in the first place. I also explained that the password could be cracked, even if it was sufficiently complex, a persistant attacker could spend a lot of time trying to crack it.

With this in mind, I went on to say that as we already encrypt data with PGP, we could do the same thing here with the archives we are planning to create. This way, people using our app (or archives in general) would be able to see with great certainty that they did originate from us and that it hasnt been substituted by a third party.

This is part of a .Net project, so any encryption would ideally take place using the .Net framework. However, right now there is no PGP support in .Net. There is support for cryptograhy in general, for instance RSA is employed as well as DPAPI, but we already use PGP and already have public keys. So it would make more sense to me for us to use these for encryption. To do this, I am going to be using the C# cryptography libraries available from The Legion of the Bouncy Castle: http://www.bouncycastle.org/csharp/ . There are not a lot of good examples of how to use this out on the Internet, the best I could find to outline the concepts involved was here. Unfortunately, the formatting of the page makes it a little hard to understand what is happening - which is the important part

Lets get down to encrypting a file and compressing it to an archive. I am going to assume that if you are searching for PGP encryption methods in C# that you have already generated a set of PGP keys. If you havent, then I can suggest that you nip over to http://www.gnupg.org/ and download a copy of GnuPG to generate some for you (it's what I used to test through my app). The first step we need to take is to understand what we need to get the key values. We need to know the path to the public and private keys, the password for the public key to encrypt with and we need to know the key ID we want to target. The key ID is a 64bit signed integer, using GnuPG you can get this value from the command line using the command:

--list-keys --with-colons <userid>
Simply replace the user ID with the user ID attached to the key (this will probably be the email address used when generating the key itself). You will get something that looks like this back:

pub:u:1024:1:33575CB4BB8193FD:2009-06-06:::u:Your Name(RSA Sign only test) <your@email.address>::scESC:
sub:u:1024:1:31B23C6C6681F46B:2009-06-06::::::e:

The key ID is the bit in bold (or should be bold, but not quite so well - it's the fith column along). This is the key ID in hexidecimal, in the code I am about to show you it needs to be in decimal format, so you can either convert it in c# with the following:

int decAgain = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber);
Or you can use a tool to convert it for you like this one: http://www.parkenet.com/apl/HexDecConverter.html, or if you happen to know what it is anyway (like I did), you can just type it in etc.

I used RSA encryption when generating my key. I used this as it would appear to be the best method for creating keys right now, there are not many examples of how to use PGP encryption on the Internet at the moment, and the peices of code that are here and there seem to use older standards.

Right, now we know our key ID etc, we can start writing the code. I created a class called PGPKeys to do all my key related work for me. First up, we need to declare all our variables etc:
/// <summary>
/// long representing the key ID we want to target.
/// </summary>
private long _keyID;
/// <summary>
/// string to represent the path to the private key file.
/// </summary>
private string _privKeyPath;
/// <summary>
/// string to represent the path to the public key file.
/// </summary>
private string _pubKeyPath;
/// <summary>
/// string to represent the passphrase used with PGP.
/// </summary>
private string _password;
public PgpPublicKey PGPPublicKey { get; private set; }
public PgpPrivateKey PGPPrivateKey { get; private set; }
public PgpSecretKey PGPSecretKey { get; private set; }

Nice and simple. Next up, I want to be able to validate the values I am planning to use:

/// <summary>
/// Constructor to manage variables for us.
/// </summary>
/// <param name="pubKeyPath">string representing the public key path</param>
/// <param name="privKeyPath">string representing the private key path</param>
/// <param name="password">string representing the passphrase</param>
/// <param name="keyID">long representing the key ID we want to use</param>
public PGPKeys(string pubKeyPath, string privKeyPath, string password, long keyID)
{
if (!File.Exists(pubKeyPath))
{
throw new ArgumentNullException("Could not find the public key at " + pubKeyPath);
}
else
{
_pubKeyPath = pubKeyPath;
}
if (!File.Exists(privKeyPath))
{
throw new ArgumentNullException("Could not find the private key at " + privKeyPath);
}
else
{
_privKeyPath = privKeyPath;
}
if (String.IsNullOrEmpty(password))
{
throw new ArgumentNullException("The password must not be null");
}
else
{
_password = password;
}
if (keyID == 0)
{
throw new ArgumentNullException("The key ID must not be null");
}
else
{
_keyID = keyID;
}
PGPPublicKey = getPublicKey(_pubKeyPath);
PGPSecretKey = getSecretKey(_privKeyPath);
PGPPrivateKey = getPrivateKey(_password);
}
You can see here where the public, private and secret keys are going to be held. Simple so far, but now the work begins. I think one of the tough things to get your head around when using the Bouncy Castle libs is that everything is a set of streams that have things done to them. This is a slight over simplification, but the following bits of code will demonstrate this process. Lets look at getting the public key value:
/// <summary>
/// private method to get the public key value.
/// </summary>
/// <param name="_pubKeyPath">string representing the public key path</param>
/// <returns>a PGPPublicKey</returns>
private PgpPublicKey getPublicKey(string _pubKeyPath)
{
PgpPublicKey pubKey;
using (Stream keyin = File.OpenRead(_pubKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpPublicKeyRingBundle pubKeyBundle = new PgpPublicKeyRingBundle(s);
pubKey = pubKeyBundle.GetPublicKey(_keyID);
if (pubKey == null)
throw new Exception("The public key value is null!");
}
return pubKey;
}
This is the on ramp with PGP, getting the public key value represented in the code itself. It also shows how Bouncy Castle likes to work with streams, the first stream here opens the file in which the key is stored, the second stream then goes on to get the value from it. With that done, lets get the secret key:
/// <summary>
/// private method to get the secret key value.
/// </summary>
/// <param name="_privKeyPath">string representing the private key path</param>
/// <returns>a PGPSecretKey</returns>
private PgpSecretKey getSecretKey(string _privKeyPath)
{
PgpSecretKey secKey;
using (Stream keyin = File.OpenRead(_privKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpSecretKeyRingBundle secKeyBundle = new PgpSecretKeyRingBundle(s);
secKey = secKeyBundle.GetSecretKey(_keyID);
if (secKey == null)
throw new Exception("The secret key value is null!");
}
return secKey;
}

Here we pretty much do the same thing as before, but this time we are looking at the private key file path. Dont get confused here, this method gets the secret key value, to do this we point it at the private key file and supply a key ID. To get the private key itself, we use the following:

/// <summary>
/// private method to get the private key value.
/// </summary>
/// <returns>a PGPPrivateKey</returns>
private PgpPrivateKey getPrivateKey(string _password)
{
PgpPrivateKey privKey = PGPSecretKey.ExtractPrivateKey(_password.ToCharArray());
if (privKey == null)
{
return null;
}
else
{
return privKey;
}
}
This one is a lot smaller, but with it we get our private key. And with that, we are done with our PGPKeys class, lets move on to encrypting, signing and compressing using these keys. Remember what I said about streams? Well, this is how it comes into play, lets encrypt some data:
private Stream encrypt(Stream output)
{
PgpEncryptedDataGenerator pgpEncDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Twofish, new SecureRandom());
pgpEncDataGen.AddMethod(_pgpKeys.PGPPublicKey);
Stream encryptedOutput = pgpEncDataGen.Open(output, new byte[BufferSize]);
return encryptedOutput;
}
Thats it done, this will encrypt a stream only. On its own, this isnt so useful, you can see where I am specifying the encryption algorythm (I decided to use TwoFish, apparently this is a very secure algorythm) and where the public key value is applied. If you just hold in your mind for a moment that this represents a stream of encrypted data at its conclusion, we can look at how it is compressed to an archive:
private Stream compress(Stream output)
{
PgpCompressedDataGenerator pgpCompDataGen = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
Stream compressedEncryptedOut = pgpCompDataGen.Open(output);
return compressedEncryptedOut;
}

The stream we just emcrypted is now passed to this method which compresses it to a zip - right now though, this is still a stream, nothing more. The next thing we need to do is apply the literal data to this stream:
private Stream literalOutput(Stream compressedOut, FileInfo file)
{
PgpLiteralDataGenerator pgpLiteralDataGen = new PgpLiteralDataGenerator();
Stream literal = pgpLiteralDataGen.Open(compressedOut, PgpLiteralData.Binary, file);
return literal;
}

Now we have a stream that has been encrypted, compressed and had its literal data applied to it. I am going to point out here that if you dont want to create a PGP encrypted file, but instead just want a PGP encrypted stream, then you could just use the encrypt method. Anyway, now we are ready to write and sign our archive:
private static void writeAndSign(Stream ouput, Stream literalout, FileStream inputFile, PgpSignatureGenerator sigGen)
{
int length = 0;
byte[] buf = new byte[BufferSize];
while ((length = inputFile.Read(buf, 0, buf.Length)) > 0)
{
literalout.Write(buf, 0, length);
sigGen.Update(buf, 0, length);
}
sigGen.Generate().Encode(ouput);
}

The last thing we want to do here, is sign the encrypted data itself:
private PgpSignatureGenerator sigGen(Stream compressedOut)
{
const bool Iscritical = false;
const bool IsNested = false;
PublicKeyAlgorithmTag tag = _pgpKeys.PGPSecretKey.PublicKey.Algorithm;
PgpSignatureGenerator pgpSigGen = new PgpSignatureGenerator(tag, HashAlgorithmTag.Sha1);
pgpSigGen.InitSign(PgpSignature.BinaryDocument, _pgpKeys.PGPPrivateKey);
foreach (string userID in _pgpKeys.PGPSecretKey.PublicKey.GetUserIds())
{
PgpSignatureSubpacketGenerator subPackGen = new PgpSignatureSubpacketGenerator();
subPackGen.SetSignerUserId(Iscritical, userID);
pgpSigGen.SetHashedSubpackets(subPackGen.Generate());
break;
}
pgpSigGen.GenerateOnePassVersion(IsNested).Encode(compressedOut);
return pgpSigGen;
}

Now we are able to encrypt, compress, sign and write our chosen file to an archive. All we need to do now is tie it up nicely:

public void EncryptSignAndZip(Stream output, FileInfo unencryptedinput)
{
if (output == null)
{
throw new ArgumentNullException("The output stream cannot be null");
}
if (unencryptedinput == null)
{
throw new ArgumentNullException("You must supply a filename to encrypt");
}
if (!File.Exists(unencryptedinput.FullName))
{
throw new ArgumentNullException(unencryptedinput + " does not exist");
}
using (Stream encryptedout = encrypt(output))
using (Stream compressedOut = compress(encryptedout))
{
PgpSignatureGenerator signature = sigGen(compressedOut);
using (Stream literalOut = literalOutput(compressedOut, unencryptedinput))
using (FileStream inputfile = unencryptedinput.OpenRead())
{
writeAndSign(compressedOut, literalOut, inputfile, signature);
}
}
}

Again, I am validating the arguments as they go in to make sure that they are not null values. You can also see here how Bouncy Castle works with the streams we are creating. However, thats it - these classes will create an encrypted zip file for you with what ever you want in them. To make use of this class, I used this in testing:
string pubKeyPath = @"C:\GnuPG\pubring.gpg";
string privKeyPath = @"C:\GnuPG\secring.gpg";
string password = "password";
long _keyID = 3699527550217851901;
PGPEncrpyt test = new PGPEncrpyt(pubKeyPath, privKeyPath, password, _keyID);
FileInfo myFile = new FileInfo(@"C:\test\app.config");
FileStream mystream = new FileStream(@"C:\test\somefile.zip", FileMode.Create, FileAccess.Write);
test.EncryptSignAndZip(mystream, myFile);

And thats it, I using the code above I was able to create an encrypted zip, which I was able to decrypt and extract using GnuPG. I have written the code to decrypt it yet as at this time, I dont really need it - the files I am encrypting will normally be sent on to other organisations etc. One thing I may make use of in future is the ability to encrypt just a stream - this may become very useful for me when it comes to sending data over a network to other parts of the business. The complete code is as follows, PGPKeys:

using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.IO;
using Org.BouncyCastle.Bcpg;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Tools.PGP.Crypto
{
/// <summary>
/// Nested class to get information on out PGP Keys
/// </summary>
public class PGPKeys
{
/// <summary>
/// long representing the key ID we want to target.
/// </summary>
private long _keyID;
/// <summary>
/// string to represent the path to the private key file.
/// </summary>
private string _privKeyPath;
/// <summary>
/// string to represent the path to the public key file.
/// </summary>
private string _pubKeyPath;
/// <summary>
/// string to represent the passphrase used with PGP.
/// </summary>
private string _password;
public PgpPublicKey PGPPublicKey { get; private set; }
public PgpPrivateKey PGPPrivateKey { get; private set; }
public PgpSecretKey PGPSecretKey { get; private set; }
/// <summary>
/// Constructor to manage variables for us.
/// </summary>
/// <param name="pubKeyPath">string representing the public key path</param>
/// <param name="privKeyPath">string representing the private key path</param>
/// <param name="password">string representing the passphrase</param>
/// <param name="keyID">long representing the key ID we want to use</param>
public PGPKeys(string pubKeyPath, string privKeyPath, string password, long keyID)
{
if (!File.Exists(pubKeyPath))
{
throw new ArgumentNullException("Could not find the public key at " + pubKeyPath);
}
else
{
_pubKeyPath = pubKeyPath;
}
if (!File.Exists(privKeyPath))
{
throw new ArgumentNullException("Could not find the private key at " + privKeyPath);
}
else
{
_privKeyPath = privKeyPath;
}
if (String.IsNullOrEmpty(password))
{
throw new ArgumentNullException("The password must not be null");
}
else
{
_password = password;
}
if (keyID == 0)
{
throw new ArgumentNullException("The key ID must not be null");
}
else
{
_keyID = keyID;
}
PGPPublicKey = getPublicKey(_pubKeyPath);
PGPSecretKey = getSecretKey(_privKeyPath);
PGPPrivateKey = getPrivateKey(_password);
}
/// <summary>
/// private method to get the public key value.
/// </summary>
/// <param name="_pubKeyPath">string representing the public key path</param>
/// <returns>a PGPPublicKey</returns>
private PgpPublicKey getPublicKey(string _pubKeyPath)
{
PgpPublicKey pubKey;
using (Stream keyin = File.OpenRead(_pubKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpPublicKeyRingBundle pubKeyBundle = new PgpPublicKeyRingBundle(s);
pubKey = pubKeyBundle.GetPublicKey(_keyID);
if (pubKey == null)
throw new Exception("The public key value is null!");
}
return pubKey;
}
/// <summary>
/// private method to get the secret key value.
/// </summary>
/// <param name="_privKeyPath">string representing the private key path</param>
/// <returns>a PGPSecretKey</returns>
private PgpSecretKey getSecretKey(string _privKeyPath)
{
PgpSecretKey secKey;
using (Stream keyin = File.OpenRead(_privKeyPath))
using (Stream s = PgpUtilities.GetDecoderStream(keyin))
{
PgpSecretKeyRingBundle secKeyBundle = new PgpSecretKeyRingBundle(s);
secKey = secKeyBundle.GetSecretKey(_keyID);
if (secKey == null)
throw new Exception("The secret key value is null!");
}
return secKey;
}
/// <summary>
/// private method to get the private key value.
/// </summary>
/// <returns>a PGPPrivateKey</returns>
private PgpPrivateKey getPrivateKey(string _password)
{
PgpPrivateKey privKey = PGPSecretKey.ExtractPrivateKey(_password.ToCharArray());
if (privKey == null)
{
return null;
}
else
{
return privKey;
}
}
}
}


And PGPEncrypt:
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.IO;
using Org.BouncyCastle.Bcpg;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Tools.PGP.Crypto
{
/// <summary>
/// Public class to encrypt and compress files to a zip archive using PGP
/// </summary>
public class PGPEncrpyt
{
/// <summary>
/// instansiate a PGPKeys object
/// </summary>
private PGPKeys _pgpKeys;
private const int BufferSize = 0x10000;
public PGPEncrpyt(string _pubKeyPath, string _privKeyPath, string _password, long _keyID)
{
_pgpKeys = new PGPKeys(_pubKeyPath, _privKeyPath, _password, _keyID);
}
private static void writeAndSign(Stream ouput, Stream literalout, FileStream inputFile, PgpSignatureGenerator sigGen)
{
int length = 0;
byte[] buf = new byte[BufferSize];
while ((length = inputFile.Read(buf, 0, buf.Length)) > 0)
{
literalout.Write(buf, 0, length);
sigGen.Update(buf, 0, length);
}
sigGen.Generate().Encode(ouput);
}
private Stream encrypt(Stream output)
{
PgpEncryptedDataGenerator pgpEncDataGen = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Twofish, new SecureRandom());
pgpEncDataGen.AddMethod(_pgpKeys.PGPPublicKey);
Stream encryptedOutput = pgpEncDataGen.Open(output, new byte[BufferSize]);
return encryptedOutput;
}
private Stream compress(Stream output)
{
PgpCompressedDataGenerator pgpCompDataGen = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
Stream compressedEncryptedOut = pgpCompDataGen.Open(output);
return compressedEncryptedOut;
}
private Stream literalOutput(Stream compressedOut, FileInfo file)
{
PgpLiteralDataGenerator pgpLiteralDataGen = new PgpLiteralDataGenerator();
Stream literal = pgpLiteralDataGen.Open(compressedOut, PgpLiteralData.Binary, file);
return literal;
}
private PgpSignatureGenerator sigGen(Stream compressedOut)
{
const bool Iscritical = false;
const bool IsNested = false;
PublicKeyAlgorithmTag tag = _pgpKeys.PGPSecretKey.PublicKey.Algorithm;
PgpSignatureGenerator pgpSigGen = new PgpSignatureGenerator(tag, HashAlgorithmTag.Sha1);
pgpSigGen.InitSign(PgpSignature.BinaryDocument, _pgpKeys.PGPPrivateKey);
foreach (string userID in _pgpKeys.PGPSecretKey.PublicKey.GetUserIds())
{
PgpSignatureSubpacketGenerator subPackGen = new PgpSignatureSubpacketGenerator();
subPackGen.SetSignerUserId(Iscritical, userID);
pgpSigGen.SetHashedSubpackets(subPackGen.Generate());
break;
}
pgpSigGen.GenerateOnePassVersion(IsNested).Encode(compressedOut);
return pgpSigGen;
}
public void EncryptSignAndZip(Stream output, FileInfo unencryptedinput)
{
if (output == null)
{
throw new ArgumentNullException("The output stream cannot be null");
}
if (unencryptedinput == null)
{
throw new ArgumentNullException("You must supply a filename to encrypt");
}
if (!File.Exists(unencryptedinput.FullName))
{
throw new ArgumentNullException(unencryptedinput + " does not exist");
}
using (Stream encryptedout = encrypt(output))
using (Stream compressedOut = compress(encryptedout))
{
PgpSignatureGenerator signature = sigGen(compressedOut);
using (Stream literalOut = literalOutput(compressedOut, unencryptedinput))
using (FileStream inputfile = unencryptedinput.OpenRead())
{
writeAndSign(compressedOut, literalOut, inputfile, signature);
}
}
}
}
}

Thursday, 4 June 2009

Reporting made easy

I recently found out at work that the MI analysts have a monthly report running up to seventy pages that they need to collate. Nothing unusual there, lots of places have big monthly reports. Then I was told that it takes five days to collate.

I initially though this was a joke, afterall this report had been publised each month for the last two years. Once I was told no, we really do do this, I needed to go and get myself a cup of sweet tea. After I had recovered, I started to speak to some of the analysts to try and find out the complete story behind this seemingly terrifying report, once I started to get some of the details behind this I came to the conclusion that the main reason for the length of time it takes to create this report was down to analysts doing the same thing every twenty-five days by hand. A prime opportunity to automate if ever there was one - and I had been after an excuse to create a .Net DAL for the team as well :).

There is nothing outstanding about this project at all, there isnt really any cutting edge tech being employed nor is there any new, fancy way of doing things being discussed. There is rarely anything taxing when it comes to generating reports. The most complicated thing you need to do is validate all the information that you are presenting, once the formula is there you are safe in the knowledge that your report will continue to churn out the required data for ever, the only time you will ever need to test it is when a new requirement is added to the schema of the report itself.


The uber report I am going to tackle just now is intended for publication via printed media. Because of this, I am confident in the knowledge that once the report has been created, no one will need to copy and past the data from it into another application - I know it gets distributed on paper. So, I know that I dont really need to provide a spreadsheet, csv, .doc or peice of XML initially. This is good news for me, as I only need to worry about one format, and I have decided to go for PDF. I had a look through the report a few days ago, when initially this task was given to me. Basically, its just a collection of graphs and tables, very little text, this just gets better as far as I am concerned. One format to worry about and very little wording to go into it - bliss!

So, what do I need for this to work? I need:
  • A graphing lib, I am going for ZedGraph. Its open source and does everything I need.


  • A PDF generation lob, for this I am going to use PFDJet. This is a commercial product, but there is also an open source version. As I dont need to do anything that fancy, it will do for me.

And thats pretty much it, all I will need to do is get the data I need from the db and use it to create some graphs and charts which go into a PDF report. Couldnt be simpler really, could it?

After a good planning meeting with the team lead and analyst I would be working with, it turns out that there is a similar system already in place for creating the basis for some of the data needed to create this report. After this session, I found that there is a monster Oracle DB somewhere in the depths of the core business that holds all the information we need. With the interesting bits to one side, I was then told a fairly comical story about Oracle, MySql and Access all doing a funny dance...

The only challenging thing really is the design of the report. Now that people have learned that I am automating some report generation, it seems like everyone has a preference as to how it should look. Right now, I am not concerned with fancy graphics or logos, right now the only important thing is getting the correct data into the report in a logical manner...

Saturday, 23 May 2009

Encrypting Connections strings in .Net with NAnt

I have been working a lot with databases over the last couple of weeks, one of the things that is a novelty to the people I am working with now is the ability to store multiple connection strings in our .config files. Its a really simple and flexible thing to do, allowing us to transport any of our apps from environment to environment with very little hassle. The only downer is that the connection strings are stored in plain-text, which naturally is a massive security hazard.



That is, of course, unless you encrypt them.



There are loads of examples on the Internet explaining how this can be done, and they are very simple to follow and implement - but I wonder how many people make use of this fairly simple feature? One thing that became apparent to me was, even though I can encrypt the file - just when and how do I want to do this? The when is simple, I want my connection strings encrypted when the application moves into a testing environment an I want them to stay encrypted from then on. The how is slightly different, there is no point in having the encryption take place as I am developing the app, as I want to have the flexibility of changing the connection strings as and when I need them. So, the ideal time for me to encrypt them would be during the build itself. Its a very straight forward thing to do, simply write a small program that encrypts the connection strings section of a web or app .config and have Visual Studio process this as a post build event. Its possibly the easiest way to do this. But, this isnt so flexible, it would only really encrypt one file relevant to a project, and whilst the code is reusable, it isnt very agile.

So, why not move this from being a post build event in the soloution file out to being a post build event in your larger build process? Using NAnt (or MSBUILD), we can create a custom task that will encrypt the connectionStrings section in any .config file. This could be used over and over again as and when requirements dictate a section in a config file should be encrypted.

There are a couple of things we need to capture:
  • The path of the .config file
  • What sort of encryption we want to use.

It would appear that .Net offers two methods to encrypt a .config file, DPAPI and RSA. DPAPI is provided by Microsoft, there is a nice article on Wikipedia discussing this encryption method. For a lot of people though, RSA may be a more suitable encryption method (its the one I am using if that is worth anything).

Back to the task. To start with, I want to get the location of the .config file and decide which encryption method I want to use. This is easy enough:


[TaskName("config-encrypt")]
class Encryption : Task
{
private string strProvider;
private string _filePath;
[TaskAttribute("configfile", Required = true)]
public string FilePath
{
get { return _filePath; }
set { _filePath = value; }
}
private string _rsa;
[TaskAttribute("RSAencryption", Required = false)]
public string RSA
{
get { return _rsa; }
set { _rsa = value; }
}
private string _dpapi;
[TaskAttribute("DPAPIencryption", Required = false)]
public string DPAPI
{
get { return _dpapi; }
set { _dpapi = value; }
}
protected override void ExecuteTask()
{
if (checkFileExists())
{
if (FilePath.Contains("web.config"))
{
encryptWebConfig();
rename();
}
else
{
encryptAppConfig();
rename();
}
}
else
{
throw new BuildException("The .config nominated does not seem to exist");
}
}

I have left the encryption methods as optional - this way, you dont need to worry about which type you need, however by doing this I need to set up a default encryption provider. A simple method like the one below will take care of that for me, I have established that I want to use RSA as the default encryption method if no other is specified:
private void chooseEncryptionProvider()
{
if (RSA == "true")
{
strProvider = "RSAProtectedConfigurationProvider";
}
else if (RSA == "" & DPAPI == "")
{
strProvider = "RSAProtectedConfigurationProvider";
}
else if(DPAPI == "true")
{
strProvider = "DataProtectionConfigurationProvider";
}
}

I also need to make sure that the file I want to apply encryption to exists in the first place. Again, this is very simple to do:


private bool checkFileExists()
{
if (File.Exists(FilePath))
{
return true;
}
else
{
return false;
}
}

Now I am all set to get encrypting. I want my task to be able to handle both web and app.config files, so I have two methods to handle each. They are pretty much identical in their operation, the only real difference is down to the way .Net handles web.config files. To encrypt a web.config I have done the following:
private void encryptWebConfig()
{
chooseEncryptionProvider();
try
{
Configuration _configFile = WebConfigurationManager.OpenWebConfiguration(FilePath);
if (_configFile != null)
{
try
{
ConnectionStringsSection _section = (ConnectionStringsSection)_configFile.GetSection("connectionStrings");
_section.SectionInformation.ProtectSection(strProvider);
_section.SectionInformation.ForceSave = true;
_configFile.Save();
}
catch (Exception e)
{
throw new BuildException("Failed to encrypt the connection strings sectiion",
e.InnerException);
}
}
}
catch (Exception e)
{
throw new BuildException(e.Message.ToString(), e.InnerException);
}
}

You can see where I am choosing the encryption provider at the start of the method. One thing I havent done here is implement any logging. For the sake of an audit trail, it would be a very wise thing to log what sort of encryption is being used. This way, you can look back across the builds you have completed for a specific environment and see which encryption provider was used. The following method encrypts an app.config:


private void encryptAppConfig()
{
string path = FilePath.Replace(".config", "");
chooseEncryptionProvider();
Configuration _configFile = ConfigurationManager.OpenExeConfiguration(FilePath);
if (_configFile != null)
{
try
{
ConnectionStringsSection _section = (ConnectionStringsSection)_configFile.GetSection("connectionStrings");
_section.SectionInformation.ProtectSection(strProvider);
_section.SectionInformation.ForceSave = true;
_configFile.Save();
}
catch (Exception e)
{
throw new BuildException("Failed to encrypt the connection strings section"
, e.InnerException);
}
}
}

As you can see, it is pretty much the same idea for this operation compared to encrypting a web.config. With all this in place, we now have all we need to automatically encrypt a .config file as part of an automated build. This can be added on to the end of a build target in a NAnt script, or better yet added to its own target to be used as a post build event. One thing I found when using this is that after the .config had been encrypted a .config.config file was created leaving the un-encrypted .config in the same directory. Naturally, this defeats the point so I put the following method together to clean up for me:
private void rename()
{
if (File.Exists(FilePath + ".config"))
{
string oldname = FilePath;
string newname = FilePath + ".config";
File.Delete(FilePath);
File.Copy(newname, oldname);
File.Delete(newname);
}
}

And there we go, all done. The syntax for the build script is insanely simple - if you dont mind using RSA encryption all you need to do is point it at the .config you want to encrypt:
</target>
<target name="encrypt">
<config-encrypt configfile="C:\test\web.config" />
</target>

I have been using this task for the past week or so, and it has been working like a dream :). The complete code for the task is below:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Configuration;
using NAnt.Core;
using NAnt.Core.Tasks;
using NAnt.Core.Attributes;

namespace Custom.NantTasks.Encryption
{
[TaskName("config-encrypt")]
class Encryption : Task
{
private string strProvider;
private string _filePath;
[TaskAttribute("configfile", Required = true)]
public string FilePath
{
get { return _filePath; }
set { _filePath = value; }
}
private string _rsa;
[TaskAttribute("RSAencryption", Required = false)]
public string RSA
{
get { return _rsa; }
set { _rsa = value; }
}
private string _dpapi;
[TaskAttribute("DPAPIencryption", Required = false)]
public string DPAPI
{
get { return _dpapi; }
set { _dpapi = value; }
}
protected override void ExecuteTask()
{
if (checkFileExists())
{
if (FilePath.Contains("web.config"))
{
encryptWebConfig();
rename();
}
else
{
encryptAppConfig();
rename();
}
}
else
{
throw new BuildException("The .config nominated does not seem to exist");
}
}
private void encryptWebConfig()
{
chooseEncryptionProvider();
try
{
Configuration _configFile = WebConfigurationManager.OpenWebConfiguration(FilePath);
if (_configFile != null)
{
try
{
ConnectionStringsSection _section = (ConnectionStringsSection)_configFile.GetSection("connectionStrings");
_section.SectionInformation.ProtectSection(strProvider);
_section.SectionInformation.ForceSave = true;
_configFile.Save();
}
catch (Exception e)
{
throw new BuildException("Failed to encrypt the connection strings sectiion",
e.InnerException);
}
}
}
catch (Exception e)
{
throw new BuildException(e.Message.ToString(), e.InnerException);
}
}

private void encryptAppConfig()
{
string path = FilePath.Replace(".config", "");
chooseEncryptionProvider();
Configuration _configFile = ConfigurationManager.OpenExeConfiguration(FilePath);
if (_configFile != null)
{
try
{
ConnectionStringsSection _section = (ConnectionStringsSection)_configFile.GetSection("connectionStrings");
_section.SectionInformation.ProtectSection(strProvider);
_section.SectionInformation.ForceSave = true;
_configFile.Save();
}
catch (Exception e)
{
throw new BuildException("Failed to encrypt the connection strings section"
, e.InnerException);
}
}
}

private void chooseEncryptionProvider()
{
if (RSA == "true")
{
strProvider = "RSAProtectedConfigurationProvider";
}
else if (RSA == "" & DPAPI == "")
{
strProvider = "RSAProtectedConfigurationProvider";
}
else if(DPAPI == "true")
{
strProvider = "DataProtectionConfigurationProvider";
}
}

private bool checkFileExists()
{
if (File.Exists(FilePath))
{
return true;
}
else
{
return false;
}
}

private void rename()
{
if (File.Exists(FilePath + ".config"))
{
string oldname = FilePath;
string newname = FilePath + ".config";
File.Delete(FilePath);
File.Copy(newname, oldname);
File.Delete(newname);
}
}
}
}

Tuesday, 19 May 2009

Using log4net with C# and MySql

Logging your application is a basic unwritten requirement. I cannot think of one application I have ever worked with that does not provide any form of logging, be it a logfile or a log in a database.

Recently at work, I came across a situation where someone described a log as a "nice to have". No, it's not nice to have - its essential to have it. As I was working in .Net (c# to be exact, hence the title), I decided to take a look at log4net. This is a logging framework maintained by the Apache project and is a port of the popular log4j to .Net, I am not going to bore people with an explanation of it here, all I will say about it is that it is a very powerful and flexible logging service. You can check out the official site here: http://logging.apache.org/log4net/index.html.

I spent a few weeks working with log4net (as it apparently wasnt essential to have a log, it was a low priority task). Creating a logfile with it is incredibly easiy, as is logging to the console. There are wide range of methods for creating a log, called appenders, however the most useful for me was appending the log to a database. Whilst log4net is popular, powerful and active, there isnt that much in the way of documentation or howto's, which could put some people off using it, this is especially apparent when logging to a database. The examples that already exist on the interweb, while useful, are sometimes out of date using deprecated syntax.

For my logging scenario, I needed to maintain a log in a MySql table, this compounded my explotation of log4net, as it isnt readily obvious how this can be done. My aim is to use log4net for all of my logging needs, so I want to create an assembly that can be referenced etc wherever I need it. Due to the extremely felxible nature of log4net, this is achievable - the majority of the configuration for it lives within the app.config (or web.config if you are that way inclined). It is possible to programatically configure log4net from within your code - but what would be the point? If you do that, then you loose all of the flexibility offered. One way to highlight this feature - right now I need to log to a MySql table, but what about in future if we decide to migrate to another DB? If I configured my logger programtically, I would then need to rewrite code. Whereas if I configured it via a .config file, all I need to do is deploy that file wherever it needs to go...

Anyway, enough waffle - you are all grownups and have myriad needs. Lets take a look at how I got log4net logging to my MySql table.

Setting up log4net to log to the console or to a file is dead simple, there is very little taking place in the code, most of it is done in the config file. The same can go for logging to a db, but with one small difference - if you rely on the config file to control your logging when using a database you have to be comfortable with the fact that your connection string will be visible. I am not sure how much of a security risk that is for everyone else, but for me it wasnt something I was willing to do at all. Even if you use a seperate database for logging, you are still exposing a lot of information about your infrastructure here. So, instead of placing the connection string in the log4net config, I am going to place it in the connectionStrings section and encrypt it. Once this is done, I need to set the connection programatically. This isnt as easy as you would first imagine (well, at least I didnt find it that easy), whilst there is a lot of documentation on what log4net can do, I found it quite tricky to figure out how to make it work with MySql - and even when I had I still had one small issue... Anyway, on to getting this setup.

All of my config for my logger is going to live in my app.config - inlcuded my encrypted db connection string. I need to get this string and then provide it to log4net, this bit is done in the code. It sounds simple but there are a number of steps I need to take first. To start with I need to get the default repository that my logger is going to use with log4net:
private Hierarchy hierachy()



{



Hierarchy h = (Hierarchy)log4net.LogManager.GetRepository();



return h;



}




It is possible to use different configuration repositories with log4net - but I dont need to so I havent had to worry about this. To be honest, I am not sure that a lot of people will need to either. Once this has been done, we need to create an ADOAppeneder - it should also be pointed out by now that I am using the MySql .Net connector to work with my db.
private AdoNetAppender adoAppender()



{



Hierarchy myHierarchy = hierachy();



if (myHierarchy != null)



{



AdoNetAppender adoApp = (AdoNetAppender)myHierarchy.Root.GetAppender("ADONetAppender");



return adoApp;



}



return null;



}




With this code, I am able to get the section of my .config file that holds all of the config for logging to a db through log4net, log4net relies on one or more 'appenders' being configured for use - my db appender is simply called 'ADONetAppender', so I have specified this in the above method. With this done, I am now ready to configure my appender programtically:
private void createAdoSettings(string dbConn)



{



AdoNetAppender myAppender = adoAppender();



if (myAppender != null)



{



myAppender.ConnectionString = dbConn;



myAppender.UseTransactions = true;



myAppender.ActivateOptions();



}



}




After all of this, we are done. All I need to do is tell my logging class what my connection string is. This isnt so difficult (thankfully, is very easy):
string connstring = ConfigurationManager.ConnectionStrings["SQLLogDB"].ConnectionString.ToString();



SetUpLog _setup = new SetUpLog(connstring);




Cant get any simpler really :). All I need to do is drop this into any class I want to log from and I am set. There is only one other addition I need to make, and that is to implement the ILog interface. This is the most important bit, regardless of any db logging, without it you cant log anything at all! I got a nice little gem from here: http://www.ondotnet.com/pub/a/dotnet/2003/06/16/log4net.html that give a good tip on implementing the interface. Basically, popping this line in at the start of your class ensures that log4net reports the name of the class duing the logging process:
protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);




This is so useful and provides a very concise log. With all this in place, we are now able to use log4net to log our application. The following code is the test app I wrote when to prove that it works:
using Logger.Setup;



using System;



using System.Configuration;



using System.Collections.Generic;



using System.Linq;



using System.Text;







namespace TestClient



{



class Program



{



protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);







static void Main(string[] args)



{



Program test = new Program();



test.logtest();



}



private void logtest()



{



string connstring = ConfigurationManager.ConnectionStrings["LogDB"].ConnectionString.ToString();



SetUpLog _setup = new SetUpLog(connstring);



log.Info("Hello there");



}



}



}








On execution, it provides the following on the command line:
2009-05-23 23:21:37,536 [12260] INFO  Hello there 23 TestClient.Program





You can also see how I am specifying the connection string, which is also held in the .config file. This is driven by my .config file, it is just a console appender and lives inside the log4net section:

<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">



<layout type="log4net.Layout.PatternLayout">



<conversionPattern value="%date [%thread] %-5level %message%newline %L %C" />



</layout>



</appender>




Now I know it works, I can get down to logging the same information to my db, first I create my schema:
CREATE TABLE logdb.mylog(



`DateTime` DATETIME DEFAULT NULL,



Thread VARCHAR (20) DEFAULT NULL,



Level VARCHAR (20) DEFAULT NULL,



Message VARCHAR (20) DEFAULT NULL,



Exception VARCHAR (20) DEFAULT NULL,



line VARCHAR (20) DEFAULT NULL,



object_ref VARCHAR (20) DEFAULT NULL,



taskID VARCHAR (20) DEFAULT NULL



)



ENGINE = INNODB



CHARACTER SET latin1



COLLATE latin1_swedish_ci;





The object_ref and taskID columns are things I want to capture as part of my wider logging process - they dont really have anything to do with log4net or its configuration. With my schema in place all I need to do now is configure log4net to log my messages to my new table. This can be a bit frustrating when working with MySql - if you get something wrong in the config you will get an error message, but they dont always seem to be that helpful to me. For instance, log4net requires you to define a connection type in order to log to a db. When I first started working with log4net, my connection type was incorrect which resulted in an error. However, the error I got complained about connection strings being null - not that there was an error with the connection type I had configured. Granted, they are closely related, but it would have just been nice if I get an error back telling me that my connection type was wrong... Anyway, I got passed it. When configuring log4net there are a couple sections that you need to work with. I am going to assume that if you are reading this then you have already looked at the log4net site and looked at how it is configured. I am not going to explain how to configure log4net here in general, if you want to see how this works then there are a lot of other articles out there that do this well, one good place to start on the topic of configuration is the log4net site - even though it is very general it will give you an idea of how all the appenders look: http://logging.apache.org/log4net/release/manual/configuration.html

Now that my schema is in place and my logic is complete, all I need to do now is configure a log4net appender. Appenders in log4net control how information is formatted as it is logged, I am going to use an ADO appender and it looks like this:
<appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
<connectionType value="MySql.Data.MySqlClient.MySqlConnection, MySql.Data, Version=6.0.3.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
<commandText value="insert into mylog (thread,line,object_ref,taskID,message,level,datetime)
values(?thread,?line,?objectref,?taskid,?message,?level,?log_date)" />
<parameter>
<parameterName value="?thread"/>
<dbType value="String"/>
<size value="20"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread"/>
</layout>
</parameter>
<parameter>
<parameterName value="?line"/>
<dbType value="String"/>
<size value="20"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%L"/>
</layout>
</parameter>
<parameter>
<parameterName value="?objectref"/>
<dbType value="String"/>
<size value="20"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%C"/>
</layout>
</parameter>
<parameter>
<parameterName value="?taskid"/>
<dbType value="String"/>
<size value="20"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%property{taskid}"/>
</layout>
</parameter>
<parameter>
<parameterName value="?message"/>
<dbType value="String"/>
<size value="20"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message"/>
</layout>
</parameter>
<parameter>
<parameterName value="?level"/>
<dbType value="String"/>
<size value="20"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level"/>
</layout>
</parameter>
<parameter>
<parameterName value="?log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout"/>
</parameter>
</appender>

The most notable thing about my config here is the use of a property. This lets me capture custom information for my log and place this in my db. This is really useful, I am sure you can all understand why and goes to show the power of log4net. With all of this in place, I am now able to log to my db - but with one small caveat, I think I have found a problem with log4net when using an ADO appender with MySql. For some reason, log4net generates an error telling me that there is a null system exception, but carries on and logs the data to the db anyway. I spent a long time trying to figure out what the problem was, but couldnt spot it. So, after some advice, I started working with the log4net source to spot where the error was being generated. Stepping through the code, I found that log4net (when used with the MySql .Net connector) seems to loose the connection string, but logs data anyway. I couldnt replicate this with SQL Server and have still been unable to resolve the error. I dont like errors hanging around like this, the data is logged, but I get this nasty message.... I have posted my findings to the log4net support group, but have not had anything back about it really - which is a shame. However, in saying that, log4net is still a truly useful tool to use in any project that requires any form of logging.

Saturday, 9 May 2009

NAnt, .Net, Sandcastle and Documentation

Documentation is great, it makes people feel good about themselves. After they have created a particularly cunning peice of work, it's nice to throw together some documentation so that others can see your genius. But wait, that means you need to go through all your work and come up with some documentation of your own! Gasp, shock - horror!!

Lets face it, for a lot of people, creating standardised documentation after they have finished their project etc isnt at the fore front of their minds. This is a shame, because with flippancy aside, people do really like documentation. It enables developers to get on with their work without asking people for info on an API etc and it lets managers see that there is "stuff being done with documentation". And dont forget, standardisation is key here - you dont want a hundred different developers all creating documentation in different ways, it would be a nightmare.

There are a lot of ways documentation can be generated at build time. One popular way is to use NDoc and this is catered for within NAnt. However, this doesnt support .Net 2.0 let alone 3.5 and it hasnt seen a release since 2005... So, there appears to be a massive gap for generation of documentation, that is if you don't count Sandcastle. This is a fairly useful, if slightly complex method of generating documentation. There are lots of examples on how to use Sandcastle via batch files and powershell scripts (the version I downloaded came packed with them). In an effort to understand the generation process, I decided to put together a NAnt task so this could be automated as part of continuous integration.

Sandcastle itself is not really one application. Right now, Sandcastle is a number of programs working together. It consits of:

  • MRefBuilder.exe
  • XslTransform.exe
  • BuildAssembler.exe
  • CHMBuilder.exe
  • DBCSFix.exe

Each of these programs are used in turn to generate first XML documentation followed by CHM (should you need it.). The following examples give an indication of how you could put a NAnt task together to make use of these programs for all your documentation needs.

All we need to do is create a suite of tasks that represent each of these programs. Essentially, all we are doing here is capthuring the arguments we need to run the apps themselves, there isnt much going on in task per se. For example, here is the complete code for MRefBuilder:

using System;
using System.Collections.Generic;
using NAnt.Core;
using NAnt.Core.Tasks;
using NAnt.Core.Attributes;
using Custom.NantTasks.Common;

namespace Custom.NantTasks.Sandcastle
{
/// <summary>
/// NAnt task to make use of Sandcastle from MS
/// </summary>
[TaskName("mrefbuilder")]
class MRefBuilder : ExternalProgramBase
{
private string _assemblyPath;
[TaskAttribute("assembly-path", Required = true)]
public string AssemblyPath
{
get { return _assemblyPath; }
set { _assemblyPath = value; }
}
private string _outputPath;
[TaskAttribute("output-path", Required = true)]
public string OutPutPath
{
get { return _outputPath; }
set { _outputPath = value; }
}
private string _executable;
[TaskAttribute("executable", Required = true)]
public string Executable
{
get { return _executable; }
set { _executable = value; }
}
public override string ExeName
{
get
{
return _executable;
}
set
{
base.ExeName = value;
}
}
public override string ProgramArguments
{
get { return AssemblyPath + " /out:" + OutPutPath; }
}
protected override void ExecuteTask()
{
Log(Level.Info, "Running MRefBuilder");
Task();
}

private void Task()
{
try
{
base.ExecuteTask();
}
catch (Exception e)
{
throw new BuildException(e.Message);
}
}
}
}


As you can see, this is pretty much just an arguments gathering exercise. Things become a little trickier for XslTransform. This app specifies multiple transformations, so we need to be able to specify at least one transformation in the task itself - but reserve the ability to specify a second. I am not going to go into exactly how Sandcastle works here, all I would be doing is copying information. For an in depth explanation of how Sandcastle works, take a look at the Sandcastle blog. So, for the XslTransform task, I simply specify two attributes for the .xsl files:


private string _xslPath;
[TaskAttribute("xsl-path", Required = true)]
public string XslPath
{
get { return _xslPath; }
set { _xslPath = value; }
}
private string _xslPath2;
[TaskAttribute("xsl-path2", Required = false)]
public string XslPath2
{
get { return _xslPath2; }
set { _xslPath2 = value; }
}

You may wish to use more than one .xsl - I don't, the example batch file in the version I downloaded didnt seem to make use of more than two. However, if you need to add more then its a very simple task to add this to the task itself. Once the arguments have been captured, we need to then use them with the app itself. We need to be able to provide at least one .xsl in the argument we use with the app and if a second is specified, then we need to be able to provide this also. To achieve this, I used the following:


private string checkXSLPath()
{
string _arg;
if (XslPath2 == null)
{
_arg = "/xsl:\"" + XslPath + "\" " + InputFile + " /out:" + OutputPath;
Log(Level.Info, _arg);
return _arg;
}
else
{
_arg = "/xsl:\"" + XslPath + "\" " + "/xsl:\""
+ XslPath2 + "\" " + InputFile + " /out:" + OutputPath;
Log(Level.Info, _arg);
return _arg;
}

}

All this does is simply check to see if a second xsl file has been specified. If it has, then it returns an argument that makes use of it, if not, then it returns an argument that doesnt. The full source looks like this:


using System;
using System.Collections.Generic;
using NAnt.Core;
using NAnt.Core.Tasks;
using NAnt.Core.Attributes;

namespace Custom.NantTasks.Sandcastle
{
[TaskName("xsltransform")]
class XslTransform : ExternalProgramBase
{
private string _xslPath;
[TaskAttribute("xsl-path", Required = true)]
public string XslPath
{
get { return _xslPath; }
set { _xslPath = value; }
}
private string _xslPath2;
[TaskAttribute("xsl-path2", Required = false)]
public string XslPath2
{
get { return _xslPath2; }
set { _xslPath2 = value; }
}
private string _inputFile;
[TaskAttribute("inputfile", Required = true)]
public string InputFile
{
get { return _inputFile; }
set { _inputFile = value; }
}
private string _outPutPath;
[TaskAttribute("output", Required = true)]
public string OutputPath
{
get { return _outPutPath; }
set { _outPutPath = value; }
}
private string _executable;
[TaskAttribute("executable", Required = true)]
public string Executable
{
get { return _executable; }
set { _executable = value; }
}
public override string ExeName
{
get
{
return _executable;
}
set
{
base.ExeName = value;
}
}
public override string ProgramArguments
{
get { return checkXSLPath(); }
}
protected override void ExecuteTask()
{
Log(Level.Info, "Starting XslTransform");
Task();
}

private void Task()
{
try
{
base.ExecuteTask();
}
catch (Exception e)
{
throw new BuildException(e.Message);
}
}
private string checkXSLPath()
{
string _arg;
if (XslPath2 == null)
{
_arg = "/xsl:\"" + XslPath + "\" " + InputFile + " /out:" + OutputPath;
Log(Level.Info, _arg);
return _arg;
}
else
{
_arg = "/xsl:\"" + XslPath + "\" " + "/xsl:\""
+ XslPath2 + "\" " + InputFile + " /out:" + OutputPath;
Log(Level.Info, _arg);
return _arg;
}

}
}
}


Looking good so far. Now we have two NAnt tasks that will generate documentation as xml and then transform it. Now we get to the meat of the whole process, BuildAssembler. Again, I am not going to go into detail about what this does here - instead, check the Sandcastle blog entry on BuildAssembler here for a complete explanation. Whilst the process behind BuildAssembler is very involved, the NAnt task for it is quite simple, the complete source is below:


using System;
using System.Collections.Generic;
using NAnt.Core;
using NAnt.Core.Tasks;
using NAnt.Core.Attributes;

namespace Custom.NantTasks.Sandcastle
{
[TaskName("buildassembler")]
class BuildAssembler : ExternalProgramBase
{
private string _manifest;
[TaskAttribute("manifest", Required = true)]
public string Manifest
{
get { return _manifest; }
set { _manifest = value; }
}
private string _config;
[TaskAttribute("config", Required = true)]
public string Config
{
get { return _config; }
set { _config = value; }
}
private string _executable;
[TaskAttribute("executable", Required = true)]
public string Executable
{
get { return _executable; }
set { _executable = value; }
}
public override string ExeName
{
get
{
return _executable;
}
set
{
base.ExeName = value;
}
}
public override string ProgramArguments
{
get { return " /config:\"" + Config + "\" " + "\"" + Manifest + "\""; }
}
protected override void ExecuteTask()
{
Log(Level.Info, "Starting BuildAssember");
base.ExecuteTask();
}
}
}


Essentially, all we are doing is giving BuildAssembler a config file to work with as well as a manifest file we generated with the previous task, XslTransform. Now even though there are more steps to go through, we need to start looking at the NAnt script itself. On its own, Sandcastle wont do everything you want, there are things you need to initiate, some of these are the creation of directories as well as copying files etc. Lets take a look at the NAnt script, at this stage it should look something similar to this:


<target name="Sandcastle">
<echo message="Trying out my Sandcastle task" />
<echo message="Checking for output directory.." />
<mrefbuilder assembly-path="Custom.NantTasks.dll"
output-path="C:\somefolder\sandcastletest.xml"
executable="C:\Program Files\Sandcastle\ProductionTools\MrefBuilder.exe" />
<xsltransform xsl-path="C:\Program Files\Sandcastle\ProductionTransforms\ApplyVsDocModel.xsl"
xsl-path2="C:\Program Files\Sandcastle\ProductionTransforms\AddFriendlyFilenames.xsl"
inputfile="C:\somefolder\sandcastletest.xml"
output="C:\somefolder\refelction.xml"
executable="C:\Program Files\Sandcastle\ProductionTools\xsltransform.exe" />
<xsltransform xsl-path="C:\Program Files\Sandcastle\ProductionTransforms\ReflectionToManifest.xsl"
inputfile="C:\somefolder\refelction.xml"
output="C:\somefolder\manifest.xml"
executable="C:\Program Files\Sandcastle\ProductionTools\xsltransform.exe" />
<mkdir dir="C:\somefolder\html" />
<mkdir dir="C:\somefolder\icons" />
<copy todir="C:\somefolder\icons">
<fileset basedir="C:\Program Files\Sandcastle\Presentation\vs2005\icons">
<include name="*" />
</fileset>
</copy>

<mkdir dir="C:\somefolder\scripts" />
<copy todir="C:\somefolder\scripts">
<fileset basedir="C:\Program Files\Sandcastle\Presentation\vs2005\scripts">
<include name="*" />
</fileset>
</copy>
<mkdir dir="C:\somefolder\styles" />
<copy todir="C:\somefolder\styles">
<fileset basedir="C:\Program Files\Sandcastle\Presentation\vs2005\styles">
<include name="*" />
</fileset>
</copy>
<mkdir dir="C:\somefolder\media" />
<mkdir dir="C:\somefolder\intellisense" />
<buildassembler config="C:\Program Files\Sandcastle\Presentation\vs2005\configuration\sandcastle.config"
manifest="C:\somefolder\manifest.xml"
executable="C:\Program Files\Sandcastle\ProductionTools\buildassembler.exe" />
</target>

As you can see, my script is very static. It specifies one model for Sandcastle (VS2005) and specifies one assembly to generate from. Also, as you can see all of the paths are static - but this doesnt stop you from creating a generic task to handle multiple assemblies as part of a CI process. Points to note in this script - simply the creation and population of directories prior to using BuildAssembler. These come directly from the location Sandcastle has been installed to (or from wherever you will choose to store them).

So, we are nearly there. By now, we have xml documentation generated, following the provided scripts in the Sandcastle download, the next step is to create some CHM file. Personally, I dont really need .chm files generated - I want my help files to go straight into a website/wiki.... To do that though, requires a bit more thought and code than I cover here. So stay tuned!!

Sunday, 3 May 2009

Cycle paths, great...

I have been getting on my bike lately. This isnt such a novel thing for me, it was a past time for me prior to my move up to Scotland. Cycling up here is a lot different to my previous venue, the weather, for one thing, is completely different. Down south I could be content in the knowledge that it would be relatively sunny from March to September and also had the added benefit of being on the doorstep to a national park. In Edinburgh though, things are different by quite some degree, for starters, I have to travel quite a bit before I get to anywhere interesting. And as I dont have a car, let alone a license, it means a period of cycling through a rather boring Edinburgh.

But, thats all forgotten once you hit one of the cycle routes that cross through Edinburgh. I was out near a place called Auchendinny a few days ago and managed to get myself lost (wasnt on a cycle route), on the way back into Edinburgh I had the misfortune to pass through Bonnyrigg. I have never been there before and if ever I go back I will be sure to spend as little time as possible there. I am sure there are a lot of lovely people there, but unfortunately the only natives I met decided to through a vodka bottle at me and shredded my front tire in the process.

Charming.

It was in such a poor state, I had to get a lift back into Edinburgh. Due to Easter, it meant that the buses were on a crap schedule - by pure chance I was lucky enough to find a cab, not only an available one but one who would admit my rather mucky bike and me in it. I didnt catch his name, but he was a life saver and has my undying thanks.

And no, that wasnt in Bonnyrigg, it was a few miles down the road in Dalkeith!