Archive

Monthly Archives: January 2010

I recently had to do some simple encryption and decryption for password storage.  I wanted to document this so a blog entry seemed in order.  So without any yammering here’s the simple class I created.

public static class EncryptionHelper
    {
        private const string CryptographyKey = "CryptKey";
 
        // The Initialization Vector for the DES encryption routine
        private static readonly byte[] iv =
            new byte[] { 220, 13, 41, 29, 1, 63, 73, 9 };
 
        /// <summary>
        /// Encrypts provided string parameter
        /// </summary>
        public static string Encrypt(string s)
        {
            if (string.IsNullOrEmpty(s)) return string.Empty;
 
            byte[] buffer = Encoding.ASCII.GetBytes(s);
            var des = new TripleDESCryptoServiceProvider();
            var md5 = new MD5CryptoServiceProvider();
 
            des.Key = md5.ComputeHash(Encoding.ASCII.GetBytes(CryptographyKey));
            des.IV = iv;
 
            string result = Convert.ToBase64String(
                des.CreateEncryptor().TransformFinalBlock(
                    buffer, 0, buffer.Length));
 
            return result;
        }
 
        /// <summary>
        /// Decrypts provided string parameter
        /// </summary>
        public static string Decrypt(string s)
        {
            if (string.IsNullOrEmpty(s)) return string.Empty;
 
            byte[] buffer = Convert.FromBase64String(s);
            var des = new TripleDESCryptoServiceProvider();
            var md5 = new MD5CryptoServiceProvider();
 
            des.Key = md5.ComputeHash(Encoding.ASCII.GetBytes(CryptographyKey));
            des.IV = iv;
 
            string result = Encoding.ASCII.GetString(
                des.CreateDecryptor().TransformFinalBlock(
                    buffer, 0, buffer.Length));
 
            return result;
        }
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

So simple enough.  Hope that is helpful to anyone interested in a super easy encrypt & decrypt example.  : )

Where is the business intelligence?

This blog entry may instigate just a bit.  It will also be a little long for a blog entry. You've been warned.  I suppose though, if you know me & the work I do, that is not really something new.  I see something wrong, broken, or otherwise and I am likely to point it out and describe it in detail.

As I roll into 2010 coding, implementing, and rocking with Webtrends, I have noticed something lacking in the analytics industry.  I will add the clause that obviously Webtrends has people thinking about these things and actively working on this topic, but what I want to point out is a general issue.  Where is the other data, where is the existing data?

It seems, even though some company's kind of get to a certain point in connecting data points, not many really do.  The biggest reason is that most companies are just a few steps away from actually being able to do so.  The other even larger reason is, many do not realize what data should or should not be connected.

When someone starts pulling CRM (Customer Relationship Manager/Management), Analytics, POS (Point of Sale), ERP (Enterprise Resource Planning) data, and other sources into a single reporting repository we finally have real business intelligence.  Otherwise so many entities stumble through the land mines of data confusion.  I see this so much it really drives me crazy sometimes.

So how can a company or entity identify and connect these points of data?  It often starts with a ridiculously simple step.  At risk of oversimplifying things, let me just state the first step in getting out of the data confusion land mines is to first figure out your data.  Ask these things:

  • What data does the business have?
  • What data is currently used and available?

Do NOT ask what data you want, do NOT ask what may not be.  What you want to know first, and so many companies make this mistake, is to know what you know.  Do not, at the early stage of business intelligence information gathering start asking too many hypotheticals.  I promise the risk of failure increases exponentially for every hypothetical data point added.

Once you have identified what data is available, start figuring out how the data is related.  Once you understand the data you can then, and only then, make the huge leap to determining what data you want and how to get it to where you want.

Let me draw this out in a real world example.  Beware; I am using my creative mind now!

What we have so far, for Awe Widgets Incorporated, is several data points.

  • Point of Sale/POS Systems in 300+ stores.
  • Web Analytics (by Webtrends of course) tracking all sorts of great data points on the Awe Widgets Incorporated Website.
  • Internal Accounting Software (Almost ERP, not really)
  • In-house Built Customer Lists for Sales.

So there we go, four key pieces of tracking.  So how would they work together?  With a little further analysis (my key creative side now analyzes Awe Widgets Incorporated internal structure) and we find a few connections.

Correlation, POS to Webtrends Analytics

The POS System has a tracking identifier for customers which we can use to sync up with logged in users tracked via Webtrends Analytics.  This data can be used to derive who is and is not in stores purchasing.  In addition trending could follow the user flow to derive some actionable decisions on how to encourage online or store front shopping.  Just these two data points being connected add a lot of value.

Correlation, Internal Account Software ties to POS

Another data point tie in with the aforementioned POS & Webtrends data is the Internal Accounting Software (IAS).  The IAS holds information related to each sale, and other correlated information about how sales are going for the quarter, year, and other performance indicators.

Correlation, In-house Customer Lists for Sales

The sales department, in aggressive technical fashion has built a number of customer lists in Excel & Access.  The Access Application has a partially updated data store with a server based Excel file holding the updated piece of data about each of the sales person's current sales.  I know, I hear it now, every developer that is familiar with this scenario screaming, "OMG, you have your data in Excel AND Access, and it is supposed to have integrity, and be aaaaaaaaaaaaaaaaggggggggggggggggghhhhhhhhhhh noooooo!"  But you know, this @#$% happens.  : )  When things are like this, solutions get creative.

Tying Together the Pieces

Alright, this is when the awesome nerd bits start to happen.  But I have covered enough for this entry.  In the following entries on this topic I will step through this first data finding mission and start discussions on how to connect these sources and get that data mart, warehouse, or other middle tier piece into action.   I will continue on and lead into how the data can finally start telling a real story.  Because in the end, the real story is, somebody needs actionable data to act upon.  Does it really matter where it is?

Check out Part II of this series

On Tuesday the 12th, from 6-8:30pm, make sure to get yourself to downtown Portland to the Webtrends Office.  Not only will you get to enjoy the awesome views from the 16th floor you’ll be treated to awesome information from the upcoming speaker.  Who is the speaker?

Mike Downey is a Director of Platform Evangelism at Microsoft.  Mike was previously the Principal Evangelist for the Platform Business at Adobe Systems.  His primary focus was on Flash, Flex, and AIR.  Needless to say, the ensuing presentation and conversation should be quit interesting.

If you’re interested, and you ought to be, check out the calagator event.

Last piece of advice, don?t drive, park at one of the transit centers and take the MAX downtown.  Life is exponentially simpler that way, and parking is then free and MAX only costs $2 bucks.

In this entry I want to cover a helper I wrote and some other bits to simplify creating charts for the scorecard application.

One of the things I wanted to do was get rid of the excessive amount of code needed in the view to generate various charts.  Since at some point I wanted to be able to dynamically create charts based on user input, I figured what better time than now to start chiseling out a helper class for displaying the charts.

Take the pie chart I created in the last entry.  The code in the view looked like this.

using (Chart chartPie = new Chart())
{
    double[] yValues = (double[])ViewData["TopCountryCounts"];
    string[] xValues = (string[])ViewData["TopCountries"];
 
    Title t = new Title("Pie Chart Representation",
        Docking.Top,
        new System.Drawing.Font("Verdana, Helvetica, Sans-Serif", 14, System.Drawing.FontStyle.Bold),
        System.Drawing.Color.FromArgb(26, 59, 105));
    chartPie.Titles.Add(t);
    chartPie.ChartAreas.Add("Default");
 
    // create a couple of series
    chartPie.Series.Add("Default");
    chartPie.Series["Default"].Points.DataBindXY(xValues, yValues);
 
    // Set Doughnut chart type
    chartPie.Series["Default"].ChartType = SeriesChartType.Pie;
 
    // Set labels style
    chartPie.Series["Default"]["PieLabelStyle"] = "Inside";
    // Set Doughnut radius percentage
    chartPie.Series["Default"]["DoughnutRadius"] = "40";
    // Explode data point with label "USA"
    chartPie.Series["Default"].Points[3]["Exploded"] = "true";
 
    chartPie.Width = 400;
    chartPie.Height = 300;
    chartPie.Page = this;
    HtmlTextWriter writer = new HtmlTextWriter(Page.Response.Output);
    chartPie.RenderControl(writer);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

At 31 lines of code, I was not really stoked about this.  A lot of this, such as creating the title, the chart area, and having a series was something that would be needed by most charts.  With that I went to work writing a set of tests to test the helper class.

public const string title = "Chart Title";
public double[] YValues = {3};
public string[] XValues = {"X Value"};
 
[TestMethod]
public void InstantiateChartHelper()
{
    ChartHelper chartHelper = new ChartHelper("title", YValues, XValues, SeriesChartType.Pie);
    Assert.IsNotNull(chartHelper);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This first test and default values, that I assumed I would use for the other tests, got me a nice skeleton class & constructor.  I used ReSharper to flesh it out a little and then went to writing the other tests.  Each contributing a bit more functionality to the overall class.  I have excluded the remaining tests from this blog entry, but they will be available when I provide the solution for download (that means keep reading and stay tuned).  The class however, that ended up with is below.

public class ChartHelper
{
    public ChartHelper(string chartTitle, IEnumerable<double> seriesValues, IEnumerable<string> seriesKeys, SeriesChartType chartType)
    {
        // Setup defaults.
        System.Drawing.Font font = new System.Drawing.Font("Verdana, Helvetica, Sans-Serif", 14, System.Drawing.FontStyle.Bold);
        System.Drawing.Color color = System.Drawing.Color.FromArgb(26, 59, 105);
 
        // Title
        Title title = new Title(chartTitle, Docking.Top, font, color);
       
        // Chart Area
        ChartArea chartArea = new ChartArea("DefaultChartArea");
        chartArea.Area3DStyle.Enable3D = true;
        
        // Series
        Series series = new Series("DefaultSeries");
        series.Points.DataBindXY(seriesKeys, seriesValues);
        series.ChartType = chartType;
 
        ResultingChart = new Chart();
        ResultingChart.Titles.Add(title);
        ResultingChart.ChartAreas.Add(chartArea);
        ResultingChart.Series.Add(series);
        
    }
 
    public Chart ResultingChart { get; set; }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

As you can see, a nice simple class.  With this class I was then able to use to reduce my lines of code to 9 lines (including the multi-line instantiation, which I suppose could be one line, leaving me with 6).

Scorecard.Views.ChartHelper chartHelper = new Scorecard.Views.ChartHelper("Pie Chart Representation",
   (double[])ViewData["TopCountryCounts"],
   (string[])ViewData["TopCountries"],
   SeriesChartType.Pie);
Chart chartPieTwo = chartHelper.ResultingChart;
 
chartPieTwo.Page = this;
HtmlTextWriter writer1 = new HtmlTextWriter(Page.Response.Output);
chartPieTwo.RenderControl(writer1);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This now generates the following pie chart.

There are a few more defaults I want to set though, so I went ahead and added a logic section for pie charts as shown below, with the switch view to provide a bit of factory style magic.

ResultingChart = new Chart();
 
// Setup defaults.
System.Drawing.Font font = new System.Drawing.Font("Verdana, Helvetica, Sans-Serif", 14, System.Drawing.FontStyle.Bold);
System.Drawing.Color color = System.Drawing.Color.FromArgb(26, 59, 105);
 
// Title
Title title = new Title(chartTitle, Docking.Top, font, color);
ResultingChart.Titles.Add(title);
 
// Chart Area
ChartArea chartArea = new ChartArea("DefaultChartArea") {Area3DStyle = {Enable3D = true}};
ResultingChart.ChartAreas.Add(chartArea);
 
// Series
Series series = new Series("DefaultSeries");
series.Points.DataBindXY(seriesKeys, seriesValues);
series.ChartType = chartType;
ResultingChart.Series.Add(series);
 
// Legend
Legend legend = new Legend("DefaultLegend");
ResultingChart.Legends.Add(legend);
 
switch (chartType)
{
    case SeriesChartType.Bar:
        break;
    case SeriesChartType.Column:
        break;
    case SeriesChartType.Pie:
        series["PieLabelStyle"] = "Inside";
        ResultingChart.Legends[0].Docking = Docking.Bottom;
        ResultingChart.Legends[0].Enabled = true;
        break;
    case SeriesChartType.Funnel:
        break;
    case SeriesChartType.Line:
        break;
    default:
        throw new ArgumentOutOfRangeException("chartType");
}
 
ResultingChart.Width = 400;
ResultingChart.Height = 300;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Notice I also reorganized where some things where instantiated, such as the Chart Object, and organized the order according to each thing I needed to have for the chart.  Once I did this I ran and rendered the chart, which I was happy with for now.

At this point I went back in and added some more charts to my scorecard view.  While I did this I noticed one last thing I ought to refactor.

<table>
<tr>
<td>
    <%
        Scorecard.Views.ChartHelper chartHelper = new Scorecard.Views.ChartHelper("Top Countries",
           (double[])ViewData["TopCountryCounts"],
           (string[])ViewData["TopCountries"],
           SeriesChartType.Pie);
        Chart chartPieTwo = chartHelper.ResultingChart;
 
        // Explode data point with label "USA"
        chartPieTwo.Series["DefaultSeries"].Points[3]["Exploded"] = "true";
 
        chartPieTwo.Page = this;
        HtmlTextWriter writer = new HtmlTextWriter(Page.Response.Output);
        chartPieTwo.RenderControl(writer);
    %>
</td>
<td>
    <%
        chartHelper = new Scorecard.Views.ChartHelper("View Cart Trend",
           (double[])ViewData["LineValues"],
           (string[])ViewData["TopEngines"],
           SeriesChartType.Line);
 
        Chart lineChart = chartHelper.ResultingChart;
 
        lineChart.Page = this;
        lineChart.RenderControl(writer);
    %>
</td>
</tr>
<tr>
<td>
    <%
        chartHelper = new Scorecard.Views.ChartHelper("Yesterday's Page Views",
         (double[])ViewData["ColumnStats"],
         (string[])ViewData["ColumnStatHeaders"],
         SeriesChartType.Column);
 
        Chart columnChart = chartHelper.ResultingChart;
 
        columnChart.Page = this;
        columnChart.RenderControl(writer);
    %>
</td>
<td>
    <%
        double[] theValues = (double[]) ViewData["ColumnStats"];
        double[] newValues = new double[]{0,0,0,0};
        int count = 0;
        foreach(double d in theValues)
        {
            newValues[count] += d*DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month) +
                                DateTime.Now.Month + DateTime.Now.Millisecond;
            
            count++;
        }
 
        chartHelper = new Scorecard.Views.ChartHelper("Current Month Page Views",
    newValues,
    (string[])ViewData["ColumnStatHeaders"],
    SeriesChartType.Bar);
 
        Chart barChart = chartHelper.ResultingChart;
 
        barChart.Page = this;
        barChart.RenderControl(writer);
    %>
</td>
</tr>
</table>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I was assigning the page and rendering the control at every single section within my table.  I didn’t need to do that, so I went back and added a render method to the ChartHelper Class.

public void RenderChart(Page page)
{
    ResultingChart.Page = page;
    HtmlTextWriter writer = new HtmlTextWriter(page.Response.Output);
    ResultingChart.RenderControl(writer);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

With a small change to the view I can now render each chart with one line of code.

chartHelper.RenderChart(this);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Now that puts everything in a better situation.  Until the next part.  Happy coding.

Square

I was fumbling through my RSS feeds reading a few entries and found this one.  One of the quotes quoted in this article sums up a massive shift in business in the US.

"I can buy an iPod touch] for $200, get the app and I'm in business. I don't need a contract with AT&T or anything. I'm in business.  - Jack Dorsey in The Economist "

Think about that for a minute.  This brings a massive amount of consumer power back to the individual.  This brings back something that had been gone for a while now.  There are of course, hurdles to be leapt, bureaucracy to avoid, and other unseen glitches in this shift but the possibilities are awe inspiring for sure.

A number of my fellow developers & other assorted friends have mentioned Square.  I have not used it yet, nor have they, but we each see unique possibilities.  I for one, just like the straight forward, clean, and easy interaction the service has with customers.  That alone is refreshing.

Another friend has pointed out the empowering nature for smaller businesses.  This is really part of a shift that has been occurring, but now brings home the concept decisively.  Anybody can get hold of a iPhone, Droid, or otherwise and utilize something like Square.  Someone could run a transaction in their living room if they're selling their couch, or their TV, and run it against someone else's credit card.  That is an awesome idea.

Web Analytics, Please Toss the Waterfall

Over on the Web Analytics Demystified Blog, John Lovett inspires a Manifesto for Web Analytics.  Reading the Manifesto draws attention to another idea that keeps popping into my mind about the larger analytics realm into which web analytics is really becoming.  Web analytics is type specific to the web, but in reality it reaches well beyond that now.  If not in function in ideal, and bridging that ideal into a central analytics view for a marketing department, executive staff, or whoever needs the information available for decisions to be made.  Each of John's points of the manifesto are well laid out.

Several of the points; Listen to your constituents, roll up your sleeves, assimilate to the culture, and others point out something that many software developers may simply think of as good individual Agile Practices.

#7 however really hits home with me.  Actually solve the problem, John writes.  Too often, too many groups simply band aid things.  Of course, there are always times when band aids will do just fine.  But a simple test is to ask, will you throw away this solution?  If you do not intend to throw away the solution in the near future, then following #7, is fundamental to ongoing success.  I will just add, be proactive, solve the problem.

#8 I read with some disdain.  Waterfall, being a software developer, is a vulgar word in so many ways.  The Waterfall methodology has sunk more projects, wasted more millions, blown schedules, and all in all been a poor practice in software development.  HOWEVER, John is not talking about the Waterfall Methodology.  But boy did it trigger a negative response from me at first.  I have helped out John here and removed the vulgarity and put in place a more software developer friendly word.

Strategy Credo #8: Establish a central mind-map strategy. By this I mean strategy should flow from the headwaters of the organization and align with the corporate goals set forth by the executive team. Once your measurement team is clear and united on the goals, then identify objectives as the next tier in your central mind-map that supports the corporate goals (these are your business promises).

etc, etc, etc…  you get the idea at this point.  :)   Note:  John, I'm just giving you gruf, please don't take any offense.  ;)

That is it from me today.  Hope all have a great first work day of 2010.  Cheers!

Follow

Get every new post delivered to your Inbox.

Join 3,712 other followers