Wednesday, October 3, 2012

Reading and writing large binary data in T-SQL and ADO.NET.

Introduction

In some cases, you may want to read/write binary data from/to your relational database. This can be a problem when the data set size exceeds the memory address size (eg. 32-bit processes) or the managed large object heap cannot load it all without throwing an OutOfMemoryException. In those cases, you will need to load the data into memory in chunks. Fortunately, the T-SQL syntax supports this operation.

Latest Standard

The old standard for reading and writing raw text data was by using the UPDATETEXT, READTEXT and TEXTPTR commands. Although, these are being obsoleted for the newer command SUBSTRING and the addition of the .WRITE argument to the UPDATE command. I found the newer syntax much easier to use and requiring less parameters. Below is an example schema and implementation.

Example Schema

For this example, we will use a very simple table setup with a primary key and binary data column.

CREATE TABLE [dbo].[DataTable] (
 [Id] [int] IDENTITY(1,1) NOT NULL,
 [Data] [varbinary](max) NOT NULL
)

Example Code

The Read method simply provides an offset into the data and the size of the chunk it wishes to read. The same applies for the Write method, except that a data argument is also supplied.

private const int ReadChunkSize = 1 << 21;   // 2MB Chunks
private const int WriteChunkSize = 1 << 21;  // 2MB Chunks

...

public static void Read(string connectionString, string readPath)
{
    using (var connection = new SqlConnection(connectionString))
    {
        int offsetIndex = 0;
        var buffer = new byte[1];
        var offset = new SqlParameter("@Offset", SqlDbType.Int, 32);
        var command = new SqlCommand(@"SELECT SUBSTRING([Data], @Offset, " + ReadChunkSize + ") FROM [dbo].[DataTable] WHERE [Id] = 1", connection);

        command.Parameters.Add(offset);

        connection.Open();

        using (var stream = File.Create(readPath))
        {
            using (var writer = new BinaryWriter(stream))
            {
                while (buffer.Length > 0)
                {
                    offset.Value = offsetIndex;
                    buffer = (byte[])command.ExecuteScalar();
                    if (buffer.Length > 0)
                    {
                        writer.Write(buffer);
                        offsetIndex += ReadChunkSize;
                    }
                }
            }
        }
    }
}

public static void Write(string connectionString, string writePath)
{
    using (var connection = new SqlConnection(connectionString))
    {
        int offsetIndex = 0;
        var buffer = new byte[1];
        var offset = new SqlParameter("@Offset", SqlDbType.Int, 32);
        var data = new SqlParameter("@Data", SqlDbType.Binary, WriteChunkSize);
        var command = new SqlCommand(@"UPDATE [dbo].[DataTable] SET [Data] .WRITE(@Data, @Offset, " + WriteChunkSize + ") WHERE [Id] = 1", connection);

        command.Parameters.Add(offset);
        command.Parameters.Add(data);

        connection.Open();

        using (var stream = File.OpenRead(writePath))
        {
            using (var reader = new BinaryReader(stream))
            {
                while (buffer.Length > 0)
                {
                    buffer = reader.ReadBytes(WriteChunkSize);
                    if (buffer.Length > 0)
                    {
                        offset.Value = offsetIndex;
                        data.Value = buffer;
                        command.ExecuteNonQuery();
                        offsetIndex += WriteChunkSize;
                    }
                }
            }
        }
    }
}

Performance Considerations

When benchmarking my code against the simple database schema, execution time was drastically different if the data was already read into memory. When a read was performed after a write, it took 2 seconds. A read without a write beforehand took about 10 seconds. This is also the same time that the write took before a read. The chunk size did not seems to have any significant impact when adjusted between 512K and 4MB sizes. Your results may vary though depending on your system's memory layout.

These time benchmarks are very informal and highly dependent on system architecture, network latency and hardware specifications. Although, the relative performance gain between the two benchmarks can be gleaned from these tests.

A further performance gain would be to front load the data into memory on a separate thread and process the loaded data into and out of the database on another. This would require some tweaking and knowledge of the system's memory constraints to ensure it doesn't become a performance bottleneck. If properly done, a lot can be gained from not having to block on I/O between every chunk read and write.

Thursday, September 20, 2012

Understanding compression and Huffman Coding.

Definitions

In a lot of texts regarding compression you will run across various conventions of syntax to describe the coding of data. Much of them are used in fields like automata theory, group theory and statistics. Although, the field of coding theory itself has a long and rich history in the 20st century; much of the terminology is borrowed from fields that gave rise to it.

I find the best way to demonstrate the following definitions is by example. Let us take the text "abbcc" as a sample.

  • Alphabet: The set of symbols that we will be encoding.
    • Form:
    • Example: where
  • String: A permutation of symbols from our alphabet. From our example alphabet, strings like "a", "aba", "abc" or "cccb" could all be possible. Using regular expression syntax, we can describe this in our example.
    • Form:
    • Example:
  • Probabilities: The set of probabilities for the alphabet. This is also known as their weights and can sometimes be denoted as w instead of p.
    • Form:
    • Example: where since 'a' occurs once, 'b' occurs twice and 'c' occurs twice out of 5 symbols all together.
  • Codewords: The set of codewords that we will create for every symbol in our alphabet.
    • Form:
    • Example: where for symbols respectively. Another way to look at it is that we are providing a bijective function: .

Code Width

Standard coding of information for most computers today is byte aligned. For example, in character codes there is ASCII, which codes the entire English alpha-numeric system in a single byte. While UTF-8 has proven to be a much better standard due to better localization support and variable width codes, ASCII serves our purposes for this discussion. ASCII is considered a fixed width coding scheme:

When performance is at stake, it is best to code information atomically aligned to the computer architecture that it is being used for. This means allocating and defining information in terms of bytes (defined as 8-bits) for the majority of systems today. Although, if space is the priority, performance can take a hit to shift around bits along the byte boundaries and create variable width codes that do not byte align.

Note: The ellipses in the alphabet denote the control characters (eg. CR, LB) and grammatical structure symbols (eg. #, $, %) contained in the ASCII code page. The alphabets will reflect this in the ellipses.

Data Compression

Variable Width Savings

Coming back to the example of ASCII, if we wish to encode a string, w, like "aaabb", it will take up 5 bytes. Although we only really need one bit to encode our information if we assign 0 to 'a' and 1 to 'b'.

We just took a 5 byte ASCII encoded string and compressed it down to under a byte. Now let us take another string, w, and give it "aaabbcc". Three alphabet symbols need to be coded; it will take at least 2 bits. At first glance, this assignment would make sense:

Although this gives us an ambiguous string since it could either be "aaabbcc", "aaabbabab", "aacbcc" or any other permutation of our codeword set when we attempt to decompress it. The reason for this ambiguity is due to the fact that the codeword for 'a' (0), is used to start the codeword for 'c' (01).

Prefix Codes

The way to get around this ambiguity is to ensure that all of your codes are prefix type. Something like this would work:

This ensures that we maintain the one-to-one mapping between our alphabet and codewords; we can translate alphabet symbols to codewords and codewords back to alphabet symbols without confusion.

Codeword Assignment

So how do you decide which symbols, get which codewords? Also, since the size of the codeword is critical to the compression ratio (average codeword length to average symbol length), designing an algorithm to weigh the more frequently used symbols is important. In our previous example, it would be poor design to give 'a' the longest codeword because it is the most used symbol in the string. This is where the Huffman Coding algorithm comes into play.

Huffman Coding Algorithm

  1. Count the number of times each symbol occurs in the data source.
  2. Put [symbol:count] pairing as nodes in a minimum priority queue based on the symbol's count.
  3. Evaluate number of queue nodes.
    • If queue has at least two nodes:
      • Dequeue the two nodes.
      • Create a new node with a null symbol and set the count equal to the sum of the two dequeued node's counts.
      • Attach the two nodes as children to the new node.
      • Put the newly created node into the queue.
      • Go to step 3.
    • If queue has only one node, dequeue and set as the root of the tree.
  4. Assign each left branch a 0 and each right branch a 1, or vice versa if you choose.
  5. The symbol nodes will be the children of the tree and their codeword will be the bits that are traversed to get there.

Algorithm Example

Given our previous example string of "aaaabbcc", let us perform the algorithm on it:

  1. Initial queue state with only symbol nodes in it.



  2. New node created from first two symbol nodes and requeued.



  3. New node created from non-symbol and symbol node, becoming the tree's root node.



  4. The final alphabet to codeword mapping is:

If you need further examples, you can look here, here and here. Once the you get a grasp of the algorithm, you will see the simplicity and beauty of it.

Working Code Example

If you want to see a naive implementation of the Huffman Coding algorithm, I posted some source code on my GitHub account. Below is the central code to the algorithm execution.

More Reading & Resources

If you are interested in coding algorithms, these links might be of interest to you.

Tuesday, September 18, 2012

IMAPI version 2 managed wrapper.

Introduction

Microsoft introduced native image mastering support in Vista, going forward. The library itself is not too complex but the latest version of the image mastering API (IMAPI) has some quirks. This is well documented by Eric Haddan in his article describing how to build an interop file and use it from .NET.

Implementation

I wanted to take it a step further and provide a library wrapper that was CLI compliant. The end result is a class that provides 3 methods, called in order, to burn an image. The project can be found on my GitHub account.

Properties

  • Media: A read only property that populates after the LoadMedia call and reports what type of physical media is connected (eg. CD-R, DVD-R, DVD+RW, etc.).
  • MediaStates: A read only collection of states in which the media is under, that populates after the LoadMedia call (eg. is blank, overwriteable, unsupported).
  • MediaCapacity: A read only property that populates after the LoadMedia call and reports the size capacity of the media.
  • Nodes: A root list of file and directory nodes that are to be written to the media.
  • Recorders: A read only selectable collection. A recorder must be selected for the LoadRecorder call.
  • VolumeLabel: A property that sets the volume label of the media to be written to.

Methods

  • LoadRecorder: Verifies a recorder is selected, loads the recording drive resources and verifies that it is working.
  • LoadMedia: Loads the media resource and verifies that it is valid for the recorder. An exception will occur if the media isn't supported (eg. closed session disc, no disc present).
  • FormatMedia: Formats the media of any previous data.
  • WriteImage: The final step to write your files to disc. This is a blocking call.

Events

  • FormatEraseUpdate: Reports progress of FormatMedia call.
  • FormatWriteUpdate: Reports progress of files being written to the media.
  • ImageUpdate: Reports progress of files being added to the media image.

Caveats

I use this library for the simple single purpose of writing finalized images to a blank CD. It has not been fully tested to accomodate all media and recorder state scenarios; I haven't tried it yet with DVD's or multi-session discs. If you would like to extend the library, feel free to push some changes to the GitHub project and I will wrap them in.

I decided to leave the method calls as blocking (synchronous) since there could be many different synchronization models that may use this library. For my purposes, I used this in an WPF application that would call the WriteImage method from a Task and then post back the progress events of the write using the Dispatcher from my WPF window. Below is a code snippet demonstrating this.

private ImageMaster _burner;

...

private void _CreateCd()
{
 // files added to _burner and _burner.WriteImage called
}

private void _burner_FormatWriteUpdate(IMAPI2.ImageMaster sender, IMAPI2.FormatWriteUpdateEventArgs e)
{
 this.Dispatcher.Invoke(() => this.statusProgressBar.Value == (e.ElapsedTime / e.TotalTime) * 100);
}

private void CreateCdWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
 System.Threading.Tasks.Task.Factory.StartNew(_CreateCd);
}

Thursday, September 13, 2012

Implementing a bit reader/writer in C.

Introduction

I was working on implementing a simple compression tool based on naive Huffman coding and initially assumed all of my prefix codes would be at or under a byte long. Although I failed to account for the worst case of Huffman coding, which can yield codes up to 255 bits in length.

This required a bit reading and writing function that would allow for variable length codes. I could have used other methods to balance worst case Huffman trees, but this issue presented a unique challenge. How to write a bit reader/writer in C.

Bit Reader/Writer Specifications

  • Read and write using reader and writer ADT's.
  • Handle any possible bit length given.
  • Remember bits read from file but are outside the requested bit length (eg. 2 bytes read in but only 12 bits requested, leaving 4 bits in the buffer).
  • Flush a partially written block to disk on a free of the bit writer ADT.
  • Return number of bits read and written on the read and write functions.

Design

The underlying structure to the ADT will contain fields for the: file pointer, the byte long buffer and the buffer bit length. The examples given for every case are in steps; what is originally in the buffer, what bits are requested, what bits are read from or written to the file, and finally what bits are left over and buffered.

Reader

There are two main cases to consider when designing the read function.
  1. Empty Buffer: The is simple since the file read bits will already be byte aligned to your output bits. You just need to simply mask out the unneeded bits and buffer them for the next read (eg. no bits buffered, 12 bits requested, 16 bits read in, 4 bits buffered).
  2. Populated Buffer: The can get a little trickier, especially in the last two cases of this.
    1. Requested bits already buffered. This is very easy since it doesn't require any file read and the buffer just needs to shift out the read bits (eg. 7 bits in the buffer and 3 are requested; buffer serves out 3 and truncates to 4).
    2. Not all requested bits buffered; end of read bits not in same byte block as end of output bits. The last output byte block must mask out the unneeded bits, put them in the buffer and then append the additional unneeded read bits from the next byte block (eg. 4 bits buffered, 13 bits requested, 16 bits read in, 8 bits buffered).
    3. Not all requested bits buffered; end of read bits in same byte block as end of output bits. The last output byte block must mask out the unneeded bits and put them in the buffer (eg. 4 bits buffered, 10 bits requested, 8 bits read in, 2 bits buffered).

Writer

There are only two cases to consider when designing the write function.
  1. Full Buffer: Shift in write bits, commit to disk and repeat until all bits are processed. If the last bit to write is not on the buffer byte boundary, it will wait until the next write (eg. 6 bits buffered, 12 bits to write, 16 bits written out, 2 bits buffered).
  2. Partial Buffer: Shift in write bits (eg. 2 bits buffered, 4 bits to write, no bits written out, 6 bits buffered).

Caveats

The code below is purely a demonstration of how a bit reader/writer works in a generic sense. There is room for plenty of optimization if you know what type of architecture your targeting and the domain of your reader/write arguments.

The Code

Header File

Source File

Tuesday, August 14, 2012

Licensing using XML digital signing.

Motivation

One of the main concerns in any licensing implementation is that authored license files are coming from the trusted authority (ie. your company) and not some third party. This can be accomplished in software by encrypting and decrypting the license with a single key, but symmetric key storage can be cumbersome and have added complexity.

Assymetric cryptography, on the other hand, allows for two keys: a private one to author files with and a public one to verify its authenticity. This is well suited for licensing, where we want to ensure that the authored license has not been altered in anyway and the content can be relied on.

Implementation

I ran across a great article that details the use of XML digital signatures in the .NET framework. It describes the type of digital signatures that can be implemented and lays out the classes and methods needed to author and verify license files.

This specific implementation allows for an: authentication key to compare against some unique value tying the license to the system, expiration date to set a time span on the software use and set of available features the user is authorized for.

Base Class

This forms the runtime object representation of the license and will be inherited by the license reader and writer.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;

namespace Licensing
{

public abstract class LicenseBase
{ 
 private byte[] _authenticationKey; 
 private DateTime _expiration; 
 private List _features;
    private int _id;

 public byte[] AuthenticationKey {
  get { return _authenticationKey; }
  protected set { _authenticationKey = value; }
 }
 public DateTime Expiration {
  get { return _expiration; }
  protected set { _expiration = value; }
 }
 public int Id {
  get { return _id; }
        protected set { _id = value; }
 }
 protected List Features {
        get { return _features; }
 } 

 public LicenseBase()
 {
  _features = new List();
 }

    public void Clear()
    {
        _id = 0;
        _authenticationKey = null;
        _features.Clear();
    }
}

}

Writer Class

This class will be used internally to author the license files that will be distributed with the released software. Note that the private key is added as an embedded resource in the project and should not be distributed since the public key can be derived from it.

using System;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;

namespace Licensing
{
    class License : LicenseBase
    {

        License(int id, byte[] authenticationKey, DateTime expiration, string[] features)
        {
            this.AuthenticationKey = authenticationKey;            
            this.Expiration = expiration;
            this.Id = id;
            this.Features.AddRange(features);
        }

        /// 
        /// Generates a key pair for digital signing and verification.
        /// 
        /// 
        /// 
        static internal void GenerateAssymetricKeys()
        {
            string datestamp = null;
            StreamWriter output = null;
            RSA key = RSA.Create();
            key.KeySize = 1024;

            datestamp = DateTime.UtcNow.ToString("yyyyMMdd");

            // Generate private key to only be used internally (DO NOT DISTRIBUTE).
            output = File.CreateText("private-" + datestamp + ".key");
            output.Write(key.ToXmlString(true));
            output.Close();

            // Generate public key to be used by customers (distribute).
            output = File.CreateText("public-" + datestamp + ".key");
            output.Write(key.ToXmlString(false));
            output.Close();
        }

        /// 
        /// Digitally sign an XML document.
        /// 
        /// The XML document to sign.
        /// The private key to sign it with.    
        /// 
        private static void _SignXmlDocument(System.Xml.XmlDocument document, RSA privateKey)
        {
            SignedXml signedDocument = new SignedXml(document);
            signedDocument.SigningKey = privateKey;
            signedDocument.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl;

            // Add reference to XML data
            Reference @ref = new Reference("");
            @ref.AddTransform(new XmlDsigEnvelopedSignatureTransform(false));
            signedDocument.AddReference(@ref);

            // Build the signature.
            signedDocument.ComputeSignature();

            // Attach the signature to the XML document.
            XmlElement signature = signedDocument.GetXml();
            document.DocumentElement.AppendChild(signature);
        }

        /// 
        /// Write the contents and digitally sign the document.
        /// 
        /// The file path to the digitally signed document.
        public void WriteDocument(string filepath)
        {
            XmlDocument document = new XmlDocument();
            document.AppendChild(document.CreateXmlDeclaration("1.0", "UTF-8", null));

            XmlElement root = document.CreateElement("License");
            document.AppendChild(root);

            XmlElement id = document.CreateElement("Id");
            id.InnerText = this.Id.ToString();
            root.AppendChild(id);

            XmlElement authenticationKey = document.CreateElement("AuthenticationKey");            
            authenticationKey.InnerText = Convert.ToBase64String(this.AuthenticationKey);
            root.AppendChild(authenticationKey);

            XmlElement expiration = document.CreateElement("Expiration");
            expiration.InnerText = this.Expiration.ToString();
            root.AppendChild(expiration);

            XmlElement options = document.CreateElement("Features");
            XmlElement featureItem = null;
            foreach (string feature in this.Features)
            {
                featureItem = document.CreateElement("Feature");
                featureItem.InnerText = feature;
                options.AppendChild(featureItem);
            }
            root.AppendChild(options);

            XmlElement publickey = document.CreateElement("Certificate");
            publickey.SetAttribute("DateCode", KeyCreationDateCode);
            publickey.InnerXml = File.ReadAllText(PublicKeyFilename);
            root.AppendChild(publickey);

            RSA privateKey = RSA.Create();
            privateKey.FromXmlString(Licensing.Properties.Resources.privatekey);
            _SignXmlDocument(document, privateKey);

            File.WriteAllText(filepath, document.InnerXml);            
        }

    }
}

Reader Class

This class will be used in the field to verify the license files that will be distributed with the released software. It is best to wrap the license read and authenticate methods in try-catch blocks since they represent exceptional behavior that fall outside the post-conditions of the method. Handling the exceptions will allow for better error reporting in the field so you can decide what information you will share with the end user (eg. bad authentication key, malformed/tampered licenses). Note again that the public key is an embedded resource for the project. It should never be distributed with the license since an attacker could generate their own keypair and envelope their own public key, with signature, in the license.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;

namespace Licensing
{
    class LicenseReader : LicenseBase
    {
        LicenseReader()
        {
        }

        public void Authenticate()
        {
            // Check the hardware key value against the AuthenticationKey        
            if (!_AuthenticationKeyMatches(hardwareKey))
                throw new LicenseAuthenticationException("The license failed to authenticate the hardware key.");

            // ... or check some node locking ID (eg. MAC ID, hard drive serial number) against the AuthenticationKey.
            if (!_AuthenticationKeyMatches(nodeLockedId))
                throw new LicenseAuthenticationException("The license failed to authenticate the node locked ID.");

            if (this.Expiration > DateTime.UtcNow)
                throw new LicenseAuthenticationException("The license has expired.");
        }

        /// 
        /// Compare byte array against authentication key byte array.
        /// 
        /// Byte array to compare against.
        /// True if a match, else false.
        private bool _AuthenticationKeyMatches(byte[] compare)
        {
            if (this.AuthenticationKey == null)
                return false;

            int upperBound = Math.Min(this.AuthenticationKey.Length, compare.Length);
            for (int i = 0; i < upperBound; i++)
            {
                if (this.AuthenticationKey[i] != compare[i])
                    return false;
            }
            return true;
        }

        public bool IsFeature(string featureName)
        {
            return this.Features.Contains(featureName);
        }

        /// 
        /// Read the digitally signed document and load its contents.
        /// 
        /// The file path to the digitally signed document.
        protected virtual void ReadDocument(string filepath)
        {
            this.Clear();

            XmlDocument document = new XmlDocument();
            document.Load(filepath);

            RSA publicKey = RSA.Create();
            publicKey.FromXmlString(Licensing.Properties.Resources.publickey);

            if (_VerifyXmlDocument(document, publicKey))
            {
                this.Id = int.Parse(document.SelectSingleNode("//License/Id").InnerText);
                this.AuthenticationKey = Convert.FromBase64String(document.SelectSingleNode("//License/AuthenticationKey").InnerText);
                this.Expiration = DateTime.Parse(document.SelectSingleNode("//License/Expiration").InnerText);

                XmlNodeList features = document.SelectNodes("//License/Features/Feature");
                foreach (XmlNode feature in features)
                {
                    this.Features.Add(feature.InnerText);
                }
            }

        }

        /// 
        /// Verify the digital signature of an XML document.
        /// 
        /// The XML document containing the signature.
        /// The public key to verify signature authenticity.
        /// True if the signature is authentic, else false.
        /// 
        private bool _VerifyXmlDocument(XmlDocument document, RSA publicKey)
        {
            SignedXml signedDocument = new SignedXml(document);
            try
            {
                XmlNode signature = document.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0];
                signedDocument.LoadXml((XmlElement)(signature));
            }
            catch
            {
                return false;
            }
            return signedDocument.CheckSignature(publicKey);
        }

    }
}

Additions & Caveats

Symmetric encryption could also be used to hinder casual viewing of the license file. The issue of symmetric key storage must be revisited but will no longer be critical to maintaining the integrity of the license files.

Strong naming of the license reading assembly is encouraged to prevent dissasembly and re-embedding of an attacker's own public key. Although, this is by no means an attack-proof method since strong naming can be removed by changing information in the CLI header of the portable executable (PE). Even if the application was able to secure the public key outside the application in a PKI, there is still the issue of code injection or recompilation once strong naming is stripped. Indirection of the public key or obfuscation of the assembly can also help but is not a reliable security measure.

Monday, July 30, 2012

Automate IIS enable on Windows 7.

Background

There are plenty of sites that will tell you how to enable IIS through the Control Panel. Although, if you are tasked with enabling IIS automatically (eg. for an installer), it can be a nightmare. This is only really needed though in special cases where the installer has more control over the hardware specifications. Determining an upgrade or uninstall scenario with IIS can be very tough since you are not installing your own software but rather enabling a feature of Windows. With that in mind, we can propose a solution for automated enabling of IIS.

Requirements

Windows Editions

The edition of Windows 7 for IIS must be either Ultimate or Professional if you plan on using ASP.NET. You can try this on other editions, or even Vista, but your results may differ since this is only scenario I have tested against. More information can be found at the IIS TechNet site.

DISM Tool

To enable IIS features through the command line, DISM can be used. This can be reliably found on Windows 7 but is inconsistent on Vista, where the older Package Manager is used instead. This was one of the reasons I decided to constrain the specification to Windows 7.

The first step in using DISM, is to find out which features need to be enabled, in order for IIS to work completely. I ran across this MSDN blog entry that lists all the needed features. Next task is to script the DISM commands.

Scripting

Caveats

Iterating over each feature and enabling it with DISM can be very time consuming. The faster approach is to evaluate the enabled state of each feature and turn them all on in one command. PowerShell can be used but the presence of it and its needed modules can be inconsistent on Windows 7. I had to fall back on batch scripting, which is the part that can cause nightmares due to its ugly syntax.

Arrays are need to store the enabled states of each feature and the feature names themselves. In my case, I needed an array of pairs. I was able to fake it using a suggestion on Stack Overflow. Also, special syntax is needed to reference variables inside a loop that are scoped outside the loop.

Last, but not least, remember to register the ASP.NET framework version being used with IIS. This will need to be done once IIS is enabled (reboot is required). Adjust the framework bitness and version below according to the one you are using.

[WindowsFolder]\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -i

The Code

The following batch script will discover which features are disabled, concatenate them into the enable command and then execute. You may need to replace SysNative with system32 or the Windows system folder being used by your target system if this is being ran outside the installer.

setlocal enableextensions enabledelayedexpansion
set features=IIS-ApplicationDevelopment IIS-ASPNET IIS-CommonHttpFeatures IIS-HostableWebCore IIS-HttpCompressionDynamic IIS-HttpCompressionStatic IIS-HttpErrors IIS-HttpLogging IIS-HttpRedirect IIS-HttpTracing IIS-Metabase IIS-NetFxExtensibility IIS-Performance IIS-StaticContent IIS-URLAuthorization IIS-WebServerRole WAS-ConfigurationAPI WAS-NetFxEnvironment WAS-ProcessModel WAS-WindowsActivationService
set dism_params=

set n=0
for %%f in (%features%) do (
 set /a n+=1
 set dism_params=!dism_params! /FeatureName:%%f
 set features[!n!]=%%f
)
set n=0
for /f "tokens=3*" %%i in ('%WINDIR%\SysNative\Dism.exe /Online /Get-FeatureInfo %dism_params% ^| findstr /b /r "^State : "') do (
 set /a n+=1
 set states[!n!]=%%i
)

set dism_params=
for /l %%i in (1,1,%n%) do (
 if "!states[%%i]!" == "Disabled" set dism_params=!dism_params! /FeatureName:!features[%%i]!
)

if "%dism_params%" neq "" %WINDIR%\SysNative\Dism.exe /Online /Enable-Feature %dism_params%

Wednesday, July 25, 2012

Converting thermocouple voltages to temperature.

Background

Unlike most sensors used in data acquisition, thermocouples do not have a linear mapping between their voltage and celcius values. This means that instead of factoring some scalar value against the data, in order to scale it to engineering units (eg. volts to acceleration), an experimentally derived equation must be created to approximate the temperature at specific voltages. This non-linearity is primarily due to the materials being used to discover the temperature difference.

The science and engineering behind thermocouples goes beyond the scope of this post but more information can be found below:

K-Type Thermocouples

One of the most general purpose thermocouple types used in data acquisition are K types since their are relatively inexpensive to make. This will also be the thermocouple that I will focus on.

Luckily, NIST provides a table of discrete values, correlating voltages to celcius and vice-versa. In addition, they also provide the exact polynomial equation to use underneath the given tables, depending on the voltage range the value falls in. Following is the exact functions to do the conversion for K-type thermocouples.

Voltage To Celcius

Given some ordered set of coefficients, D, and a voltages value, v, let the voltage to celcius conversion be defined as a function of v as follows.

D is dependent on the voltage value being evaluated:

  • values greater than 206.44 mV will have D = {-131.8058, 48.30222, -1.646031, 0.05464731, -0.0009650715, 0.000008802193, -0.0000000311081, 0.0, 0.0, 0.0 };
  • values geater than 0.0 V will have D = { 0.0, 25.08355, 0.07860106, -0.2503131, 0.0831527, -0.01228034, 0.0009804036, -0.0000441303, 0.000001057734,-0.00000001052755};
  • and values less than 0.0 V will have D = { 0.0, 25.173462, -1.1662878, -1.0833638, -0.8977354, -0.37342377, -0.086632643, -0.010450598, -0.00051920577, 0.0 }.

Celcius To Voltage

Given some ordered set of coefficients, C, an ordered set of exponentials, A, and a celcius value, x, let the celcius to voltage conversion be defined as a function of x as follows.

C is dependent on the celius value being evaluated:

  • positive and zero values will have C = { -0.017600413686, 0.038921204975, 0.000018558770032, -0.000000099457592874, 0.00000000031840945719, -0.00000000000056072844889, 0.00000000000000056075059059, -3.2020720003E-19, 9.7151147152E-23, -1.2104721275E-26};
  • negative values will have C = { 0.0, 0.039450128025, 0.000023622373598, -0.00000032858906784, -0.0000000049904828777, -0.000000000067509059173, -0.00000000000057410327428, -0.0000000000000031088872894, -1.0451609365E-17, -1.9889266878E-20, -1.6322697486E-23};
A is a fixed ordered set of exponentials; A = {0.1185976, -0.0001183432, 126.9686}.

Performance

Since the polynomial equation's number of terms are bounded by a constant (ie. |D| or |C|) the performance hit will be a constant factor to the number of voltage or celcius values to calculate. Also, temperature is sampled at a very low rate, yielding small data sets that usually have redundant values in them. Further optimization can be made by caching previous computations and reusing their results if the same voltage or celcius value is encountered again.

The Code

private static double[] _thermocoupleCoefficientsTypeKneg = {
 0.0,
 0.039450128025,
 2.3622373598E-05,
 -3.2858906784E-07,
 -4.9904828777E-09,
 -6.7509059173E-11,
 -5.7410327428E-13,
 -3.1088872894E-15,
 -1.0451609365E-17,
 -1.9889266878E-20,
 -1.6322697486E-23
};
private static double[] _thermocoupleCoefficientsTypeKpos = {
 -0.017600413686,
 0.038921204975,
 1.8558770032E-05,
 -9.9457592874E-08,
 3.1840945719E-10,
 -5.6072844889E-13,
 5.6075059059E-16,
 -3.2020720003E-19,
 9.7151147152E-23,
 -1.2104721275E-26
};
private static double[] _thermocoupleExponentialsTypeK = {
 0.1185976,
 -0.0001183432,
 126.9686
};
/// 
/// Type K thermocouple inverse coefficient values for voltage to celcius conversions.
/// 
/// Valid values for -200C - 0C / -5.891mV - 0mV.
private static double[] _thermocoupleInverseCoefficientsTypeK0 = {
 0.0,
 25.173462,
 -1.1662878,
 -1.0833638,
 -0.8977354,
 -0.37342377,
 -0.086632643,
 -0.010450598,
 -0.00051920577,
 0.0
};
/// 
/// Type K thermocouple inverse coefficient values for voltage to celcius conversions.
/// 
/// Valid values for 0C - 500C / 0mV - 20.644mV.
private static double[] _thermocoupleInverseCoefficientsTypeK1 = {
 0.0,
 25.08355,
 0.07860106,
 -0.2503131,
 0.0831527,
 -0.01228034,
 0.0009804036,
 -4.41303E-05,
 1.057734E-06,
 -1.052755E-08
};
/// 
/// Type K thermocouple inverse coefficient values for voltage to celcius conversions.
/// 
/// Valid values for 500C - 1372C / 20.644mV - 54.886mV.
private static double[] _thermocoupleInverseCoefficientsTypeK2 = {
 -131.8058,
 48.30222,
 -1.646031,
 0.05464731,
 -0.0009650715,
 8.802193E-06,
 -3.11081E-08,
 0.0,
 0.0,
 0.0
};


public static double CelciusToVoltageTypeK(double value)
{
 double[] a = _thermocoupleExponentialsTypeK;
 double[] c = null;
 double result = 0.0;
 if ((value >= 0.0)) {
  c = _thermocoupleCoefficientsTypeKpos;
 } else {
  c = _thermocoupleCoefficientsTypeKneg;
 }
 for (int index = 0; index <= c.Length - 1; index++) {
  result += (c[index] * Math.Pow(value, index)) + (a[0] * Math.Exp(a[1] * Math.Pow(value - a[2], 2)));
 }
 return result;
}

public static double VoltageToCelciusTypeK(double value)
{
 double[] d = null;
 double result = 0.0;
 if ((value > 0.020644)) {
  d = _thermocoupleInverseCoefficientsTypeK2;
 } else if ((value >= 0.0)) {
  d = _thermocoupleInverseCoefficientsTypeK1;
 } else {
  d = _thermocoupleInverseCoefficientsTypeK0;
 }
 for (int index = 0; index <= d.Length - 1; index++) {
  result += d[index] * Math.Pow(value, index);
 }
 return result;
}

Monday, April 23, 2012

Bit specific builds in Visual Studio 2010.

Using Conditioned Tags

The Visual Studio 2010 UI does not have options to handle conditional references based on what kind of configuration and platform you are running. For example, you may have a project reference that needs to change depending on whether you are doing a 32 or 64 bit build.

Fortunately, the project file syntax does support conditional references. I was able to find this out on stack overflow.

In our case, we have our DLL's in source control under the project's bin directory. For some reason I kept getting the 64-bit version of the DLL and then found out it was because the Content tags also needed to be conditioned.

Example

Lets say you have a project (MyProject) with two DLL's, 32bit.dll and 64bit.dll, that are referenced by the project and kept under the bin directory for the project:

  • MyProject\bin\x86\32bit.dll
  • MyProject\bin\x64\64bit.dll

You will need to hand edit the MyProject\MyProject.vbproj file and add these entries to it (or change it if they are already in there). The first two entries are for the project references and the last two are for their inclusion as a content item in the project.

   <Reference Include="32bit" Condition="'$(Platform)' == 'x86'">
      <HintPath>bin\x86\32bit.dll</HintPath>
   </Reference>  
   <Reference Include="64bit" Condition="'$(Platform)' == 'x64'">
      <HintPath>bin\x64\64bit.dll</HintPath>
   </Reference>
   ...
   <ItemGroup>    
      <Content Include="bin\x86\32bit.dll" Condition="'$(Platform)' == 'x86'"/>
      <Content Include="bin\x64\64bit.dll" Condition="'$(Platform)' == 'x64'"/>
   </ItemGroup>

Now, whenever the platform is changed to x86, it will only use the x86 conditioned tags and likewise when changed to x64. This can also be applied to other conditions too, check the links below for a complete breakdown of the MSBuild schema and conditions.