Cannot read property ‘replace’ of undefined

If you get the following while setting up a new React/Babel/Webpack project you forgot to install all of the presets.


> webpack-dev-server --content-base client --inline --hot

/.../node_modules/webpack/lib/NormalModuleFactory.js:72
var elements = request.replace(/^-?!+/, "").replace(/!!+/g, "!").split("!");
^

TypeError: Cannot read property 'replace' of undefined
at /Users/.../node_modules/webpack/lib/NormalModuleFactory.js:72:26
at /Users/.../node_modules/webpack/lib/NormalModuleFactory.js:28:4
at /Users/.../node_modules/webpack/lib/NormalModuleFactory.js:159:3
at NormalModuleFactory.applyPluginsAsyncWaterfall (/Users/.../node_modules/tapable/lib/Tapable.js:75:69)
at NormalModuleFactory.create (/Users/.../node_modules/webpack/lib/NormalModuleFactory.js:144:8)
at /Users/.../node_modules/webpack/lib/Compilation.js:214:11
at /Users/.../node_modules/async/lib/async.js:181:20
at Object.async.forEachOf.async.eachOf (/Users/.../node_modules/async/lib/async.js:233:13)
at Object.async.forEach.async.each (/Users/.../node_modules/async/lib/async.js:209:22)
at Compilation.addModuleDependencies (/Users/.../node_modules/webpack/lib/Compilation.js:185:8)

Fix by installing the presets with yarn/npm:

yarn add babel-preset-es2015 --dev
yarn add babel-preset-react --dev

Setup OpenVPN on Centos

Occasionally when I’m out I’d like to be able to remote into my machine back at home. In the past I have opened up a random port, moved RDP to it, and called it good. I don’t really trust that level of security and I feel dirty when I get home. I’d like another layer there and I know OpenVPN is a proven technology. So I’m going to spend some time this weekend setting up OpenVPN on a CentOS machine that I have laying around. I’m hopeful that will allow me to VPN into my home network and then access my Windows machine over RDP. Let’s go!

While my CentOS box is updating I’m surfing the web for some instructions. I use this machine as a build, test, and general development server. The following command tells me I’m running CentOS release 6.6 (Final).

$cat /etc/issue

A quick yum search tells me that there is no ‘openvpn’ in the base repo’s so I’m going to enable EPEL. I’m pretty sure I had it setup once already, but I recently had to rebuild the server after a hard drive crash. Oh, that reminds me, I’m logged in as root because I haven’t setup extra accounts yet. I’ll need to fix that before vacation.

$yum search openvpn
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
* base: mirrors.easynews.com
* centosplus: repos.lax.quadranet.com
* extras: mirror.san.fastserv.com
* updates: mirror.us.oneandone.net
Warning: No matches found for: openvpn

I’m skimming sites for OpenVPN config info. This one mentions the EPEL and setting up keys with ‘easy-rsa’. What the heck, can’t I setup keys with some funky openssl command? I’ll keep digging… here’s ‘easy-rsa’ again and it looks like they’re building rpm’s by hand. No thanks.

Ok, yum update finished so it’s time to install EPEL. Here’s the command I used to install this extra repo. I also follow it up by disabling it by default. Whenever I need it or want to run an update I tack on --enablerepo=. That keeps me from installing or updating something from the EPEL on accident. Don’t ask me where I got the command from, hopefully it still works.

$rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm

Oh, it’s already installed and ‘/etc/yum.repos.d/epel.repo’ says it’s disabled by default.

cat /etc/yum.repos.d/epel.repo
[epel]
name=Extra Packages for Enterprise Linux 6 - $basearch
#baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch
mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-6&arch=$basearch
failovermethod=priority
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
...

Now yum reports that it found openvpn. Here goes nothing.

$yum --enablerepo=epel search openvpn
$yum --enablerepo=epel install openvpn

The easy part is over and the fun begins. Now what? chkconfig --list tells me it is installed and not running yet. The post mentioned above tells me that there are sample config files and I see them. There’s nothing in ls /etc/openvpn so I guess that’s what I need to do.

$'ls /usr/share/doc/openvpn-2.3.2/sample/sample-config-files/'
client.conf office.up roadwarrior-server.conf tls-office.conf
firewall.sh openvpn-shutdown.sh server.conf xinetd-client-config
home.up openvpn-startup.sh static-home.conf xinetd-server-config
loopback-client README static-office.conf
loopback-server roadwarrior-client.conf tls-home.conf

I’m going to copy server.conf over to /etc/openvpn, but wait what’s the init.d file say for config?

$cat /etc/init.d/openvpn

I see nothing about a config file. Oh wait, never mind. It loops through the /etc/openvpn directory to find all .conf files. That seems a little sketchy but what do I know about init.d files? Guess I’ll copy it over and edit. I don’t know why I still type vim instead of vi.

$cp /usr/share/doc/openvpn-2.3.2/sample/sample-config-files/server.conf /etc/openvpn/
$vim /etc/openvpn/server.conf

Steve at GRC tells me that some ISP’s block 1194 so I’m going to dump it on a random port. WhyY U NO FINISH TUTORIAL?? not, I was going to do it anyway. By the way Steve, why haven’t you finished your OpenVPN tutorial yet? Yes yes, I know proxpn sponsors Security Now.

local 192.168.1.10
port **********

Research time. What’s dev tap/tun and dev-node? Why do all of these guides have one setup a private key first? That’s gotta be the easiest part of the whole thing. Ok, dev tun it is. Server fault tells me that mobile doesn’t support dev tap. Thanks Siegfried Löffler, I believe you even though you only have one internet point (that’s more points than me anyway).

Oh fine, time to create a key. Looks like ‘easy-rsa’ is supplied by the openvpn people. Do I have to use it? Whatever, guess I’ll go for it. Oh, it’s not installed by default.

$yum --enablerepo=epel install easy-rsa

Crud, where did it install? This says to make a directory and copy the files over. They’re in that spot so I’m going to believe what the magic internet tells me.

$mkdir -p /etc/openvpn/easy-rsa/keys
$cp -rf /usr/share/easy-rsa/2.0/* /etc/openvpn/easy-rsa/
$vim /etc/openvpn/easy-rsa/vars

Well look at that, that’s exactly what ‘vars’ tells me to do anyway. Change this, it’s obviously wrong.

# These are the default values for fields
# which will be placed in the certificate.
# Don't leave any of these fields blank.
export KEY_COUNTRY="US"
export KEY_PROVINCE="CA"
export KEY_CITY="SanFrancisco"
export KEY_ORG="Fort-Funston"
export KEY_EMAIL="me@myhost.mydomain"
export KEY_OU="MyOrganizationalUnit"

Still following for the key creation. Looks like they got it right to me. Why so many different extensions for (config, conf, cnf)? Wow, this config file is full of a bunch of stuff I don’t care about.

$cp /etc/openvpn/easy-rsa/openssl-1.0.0.cnf /etc/openvpn/easy-rsa/openssl.cnf

Magic internet site tells me to run the following. Super user tells me that source “executes the content of the file passed as argument, in the current shell.” Thanks nagul, maybe I should have researched that before blindly running the command. Weird, how the heck does ./clean-all do anything? I don’t see it in the directory… oh, duh it’s a directory.

$cd /etc/openvpn/easy-rsa/
$source ./vars
$./clean-all
$./build-ca
$./build-key-server server
$./build-key client
$./build-dh

Answer all of the nice scripts questions now please. You know what’s funny? I have done this exact process while creating a self signed cert for my server. Maybe I could do without easy-rsa? Oh well, this certainly is a lot less work. Ugh, this is taking forever.

Finally, now I have a self signed cert that I don’t have to touch for 3650 days. I’m sure I’ll forget how I generated it in that time. Ignoring that copy the files like says. After this is all over I’ll need to remember to copy the client keys to my Mac.

$cd /etc/openvpn/easy-rsa/keys/
$cp dh2048.pem ca.crt server.crt server.key /etc/openvpn/

Back to the server config file, edit and change the lines. Everyone’s using Google’s DNS servers so I may as well also. I do like OpenDNS but I’m not going to argue with three other sites that I’m following.

$vim /etc/openvpn/server.conf
dh dh2048.pem
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
user nobody
group nobody

Am I done now? It seems like there should be more to the whole process. Guess I’ll punch a hole in my router and start the service. Maybe I can connect via my Mac internally. I’m assuming I know it works if I connect and get a 10.8.x.x address since I think I saw that in a config somewhere.

$vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
$sysctl -p

Time to fire it up!

$service openvpn start
$chkconfig openvpn on

Looks good, ‘ifconfig’ gives the same output as shown here. Now don’t I need to get some sort of key to my client? I would hope this is all secured with key files. Yep, I’ll need to copy the following to my Mac.

/etc/openvpn/easy-rsa/keys/ca.crt
/etc/openvpn/easy-rsa/keys/client.cert
/etc/openvpn/easy-rsa/keys/client.key

To do that I’m going to archive them and then copy the archive over to my Mac. However you get the files to your machine is up to you. I always forget how scp works so the command is below but you’ll need to tune it for your setup.

$cd /etc/openvpn/easy-rsa/keys/
$tar -czvf client.tar.gz ca.crt client.crt client.key
$cp client.tar.gz /home/
$scp root@192.168.1.10:/home/client.tar.gz ~/Documents/client.tar.gz

I’m using Viscosity on my Mac to do the client side. It’s $9, can handle multiple profiles, and I already have it installed.

Nuts, looks like the Mac isn’t connecting. I know I can reach the server … oh poop, I choose the same port that SSH is running on. It couldn’t have even started. Interesting, checking the logs I see the following. I think I may need to correct that but I don’t really look forward to changing out my network configuration.

Nov 1 18:58:09 dev openvpn[12583]: NOTE: your local LAN uses the extremely common subnet address 192.168.0.x or 192.168.1.x. Be aware that this might create routing conflicts if you connect to the VPN server from public locations such as internet cafes that use the same subnet.

I see little else in the logs and ‘service openvpn restart’ didn’t error while shutting down the service. Just in case I’m going to move the port and open a hole in the firewall. Oh, hah, SSH is using tcp over my port and openssh is setup to use udp. I think I just need to poke the udp hole in my firewall for that port. I’ll try that instead.

MONEY! Yes! Now to poke a hole in my external firewall. I’ll port forward the same udp port to my server at 192.168.1.10.

Now I’m not sure how to test this other than from some remote location. It all appears to be working correctly. I know I can reach my server and connect to it but I have no idea if my traffic can hop from my server to my workstation. I enabled Remote Desktop and poked a hole through Windows firewall for port 3389. I also added my user as a Remote Desktop User because I don’t run as admin.

Network Topology - Produced with Dia Diagram Editor
Network Topology – Produced with Dia Diagram Editor

Oh, the internet tells me I can setup a subnet to test. That makes sense but I’m not sure that I can do that with my router. Maybe I can put my wireless router on a different subnet? Nope, not supported. I have a spare router around here somewhere. Oh, wait my cable modem has a router attached. I can jack into that and attempt to VPN into my second internal network. I’m running two because I don’t really trust this COX router.

The subnet that my OpenVPN server lives on is 192.168.1.X and the subnet that my Mac and internal router are now on is 192.168.0.X. Traffic cannot flow from 192.168.0 to 192.168.1 without the VPN working. I’m excited, fire it up! HAHAHA, IT CONNECTED!

Bad news, I cannot connect to my internal Windows box while connected to OpenVPN. I am connected though as I see tun0 with an IP of 10.8.0.6. Traffic is absolutely flowing but I think it’s stuck at the server. I also see ‘Nov 1 20:11:46 dev openvpn[12663]: 192.168.0.11’ in the logs which is the IP I’m connecting from. Time to research.

Oh, neat. I ran iptables -F now my pings are coming through. Perhaps it was working and my firewall rules were just dropping ping packets. Nope, something is still blocking. I can now ping my Windows machine but RDP doesn’t seem to be working. I’m going to install Wireshark on my Windows machine to see if packets are coming in.

What??? The packets are hitting the Windows machine. I can see them in Wireshark “Transmission Control Protocol, Src Port: 59778 (59778), Dst Port: 3389 (3389), Seq: 0, Len: 0”. Do I not have Windows Firewall setup correctly? Windows Firewall shows TCP/UDP 3389 is allowed in. I did change the port that RDP is listening to a little while ago. Maybe that service needs a reboot. That was it, after restarting all “Remote Destop *” services RDP is letting me in now.

I would rather not run without firewall rules so I’ll do the opposite of ‘iptables -F’ now. Part of the trouble is my firewall rules are really agressive. Only certain ports are allowed in and out. I’ll drop all of the outbound rules because OpenVPN will be opening random connections when connecting to my LAN.

Got it! Thanks to Bebop here I was able to get the correct settings for iptables. The major changes I made are below.

iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 10.8.0.0/24 -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

There are a few cleanup tasks that I want to do next. First, I want some sort of password added to my key. Second, I need some sort of dynamic dns setup because my public IP will change eventually. Finally, I would love to have Google Authenticator setup to handle two factor auth. It’s getting late so I’m going to save those tasks for another day.

More than a “Hello World” in VBA

When I started at Bates Group, LLC one of my first assignments was to debug an Excel VBA macro.  Knowing nothing about the language I fought my way through the bug and fixed the macro.  After that I quickly decided to learn more about the language.   Since “Hello World” only gets me so far, I decided to do something a little tougher.  What better way to do that than to think back to my college assignments?

Back then one of the assignments I had was to write a random walk function.  Imagine standing next to a lamppost on the street.  From the lamppost you can take a step in one of four directions; North, South, East, or West.  You take a step in a random direction and then look at where you are.  From your new location you take another step in a random direction and you keep taking these random steps for a while.  Finally you stop and look up, how far away from the lamppost are you?

The following function does that only much faster than you or I could.  It takes 20,000 steps total and colors them along the way.  Every 2,000 steps it will change colors leaving a cool trail as it goes along.

Public Sub TakeAWalk()
    Workbooks("themikecom_vba_helloworld.xls").Activate
    ActiveWorkbook.Worksheets("Board").Select

    ' Where on the sheet should we start?
    ActiveSheet.Range("EE150").Select

    ' How many steps per turn should we take?
    STEPS_PER_TURN = 2000

    ' How many turns should we take?
    TURNS = 10

    For j = 3 To (TURNS + 3)
    For i = 0 To STEPS_PER_TURN

        ' Should we step east or west?
        randomX = Int(4 * Rnd)

        ' Should we step north or south?
        randomY = Int(4 * Rnd)

        ' Move west-east
        Select Case randomX
            Case 2 ' Move one step west
                If ActiveCell.Column < 1 Then ' Do not overstep the west border
                    ActiveCell.Offset(0, -1).Select
                End If

            ' Case 1 - Stay in the same spot

            Case 0 ' Move one step east
                If ActiveCell.Column <= 255 Then ' Do not overstep the east border                     ActiveCell.Offset(0, 1).Select                 End If         End Select         ' Move north-south         Select Case randomY             Case 2 ' Move one step north                 If ActiveCell.Row > 1 Then ' Do not overstep the north border
                    ActiveCell.Offset(-1, 0).Select
                End If

            ' Case 1 - Stay in the same spot

            Case 0 ' Move one step south
                If ActiveCell.Row <= 65535 Then ' Do not overstep the south border
                    ActiveCell.Offset(1, 0).Select
                End If
        End Select

        ' Leave a trail
        ActiveCell.Interior.ColorIndex = j

    Next i
    Next j
End Sub

Macro Flower ShotWith that done I wanted to add another function to learn how to create a menu.  I came up with the square flower.  This function will generate a square of random size with each section of the square filled with a different color.  This function taught me some tricks about looping in VBA, some ways are a lot faster than others.

Public Sub Flower()
    Workbooks("themikecom_vba_helloworld.xls").Activate
    ActiveWorkbook.Worksheets("Board").Select

    Dim start As Range
    Dim Length As Integer
    Dim Width As Integer
    Dim Color As Integer

    ' The starting point of the flower
    Set start = ActiveCell

    ' The maximum size of the flower
    size = Int(57 * Rnd)

    ' Ignore boundry errors for now
    On Error Resume Next

    For z = 0 To size
        ' Generate a random color for this row
        Color = Int((56 - 1 + 1) * Rnd + 1)

        ' Left side
        Range(start.Offset(0, 0), start.Offset(Length, 0)).Interior.ColorIndex = Color

        ' Bottom side
        Range(start.Offset(Length, 0), start.Offset(Length, Length)).Interior.ColorIndex = Color

        ' Upper side
        Range(start.Offset(0, 0), start.Offset(0, Width)).Interior.ColorIndex = Color

        ' Right side
        Range(start.Offset(0, Width), start.Offset(Width, Width)).Interior.ColorIndex = Color

        Set start = start.Offset(-1, -1)
        Length = Length + 2
        Width = Width + 2
    Next z

    On Error GoTo 0
End Sub

So what did I learn after all of this?  Mostly that I have a strong dislike for VBA.  It works well for small projects with small data sets.  However those small projects quickly expand into real programs which need to be maintained.  You are better off doing it right the first time instead of maintaining a large clunky macro.Excel Random Walk

Download the complete macro here. You will need to enable macros in your security settings to get them to work.  Once enabled, select “Random Walk” from the “theMike.com – Hello World VBA” menu.  This will start a random walk which will finish after a couple of seconds.  The “Square Flower” menu item will create a square flower under your cursor.

Creating an Audiobook for the iPod

iPodiPods treat audiobooks differently than music files. For one you can leave an audiobook listen to something else, and later pick up where you left off. An iPod won’t do this when you are listening to music file. Furthermore an audiobook can have chapter markings making it easier to find a chapter in a longer book.

iTunes makes it easy to create a music file for the iPod. You can insert a CD and iTunes will import it at the push of a button. Creating a proper audiobook however, is difficult with iTunes. You could import a whole CD as a single file but what if your audiobook covers multiple CDs?

I’ve had this problem for a while now. My initial solution involved importing each track and numbering them in a way that they would line up in a playlist. This works, but it’s annoying. I can play the book but if I stop I have to remember which chapter I left off with. If I don’t go back to the book for a couple of days I forget where I am and have to start over. A couple of weeks ago I finally had enough and went searching for a better solution.

The solution to my problem came from a program called Chapter and Verse by lodensoftware.com.  The program will take a list of AAC (.m4a) files and convert them into one single audiobook (.m4b).  This makes creating an audiobook from a CD very easy.  Simply import the CD using iTunes in the AAC format and then use Chapter and Verse to make the conversion.

Chapter and Verse - Main screen

Chapter and Verse is not perfect however.  If files are not in the AAC format it will have to convert them into this format before creating the audiobook.  If you use a free audiobook solution such as librivox.org this means it’ll have to convert each file before combining them into one.  Not too big of a deal, just plan a little extra time when creating an audiobook from a set of mp3s.

Quick Audiobook Tutorial

Creating an audiobook is very easy in Chapter and Verse.  First download and install the application.  You will need to make sure iTunes is installed as well because it is used by Chapter and Verse to convert files.

Once Chapter and Verse is installed start the application and it will load to an empty project screen.  Each project contains a set of AAC audio files which will end up being the chapters in this simple tutorial.

Begin by clicking the “Add Files” button to add the audio files you want to be a part of the audiobook.  The open file dialog only shows MP4 files by default so be sure to change the filter if you are adding say .mp3 files.

If you added a file that is not in the AAC format Chapter and Verse will attempt to convert the file using iTunes.  In that case click “Yes – Convert” on the conversion screen.  iTunes will open for the conversion, do not close it until it’s done.  When Chapter and Verse is done with the conversion your files will be added to the “Input Files” tab.

Click on the “Chapters” tab to change each chapter’s name.  On this tab you can rename each chapter in the audiobook.  For my audiobooks I generally set each chapter name to be the same as that of the file.  There are several options here that you may wish to explore.  Near the bottom of the tab there is an “Input Files” dropdown where you can choose prefixes for the chapter names.  I usually to choose the <Filename> option and leave the rest of the settings at their defaults.

The “Metadata” tab allows you to change the file information for the audiobook.  I usually set the title because that is displayed in the track listing on the device.

Finally make sure “Autobuild” is on and then click the “Build Audiobook” button.  Chapter and Verse will ask you where to save the file and then will create the book.

So to sum it all up

  1. Click Add Files to add your audio tracks, Chapter and Verse will convert them if necessary.
  2. Edit the chapter titles on the “Chapters” tab.
  3. Edit the final file information on the “Metadata” tab.
  4. Build the audiobook

 

Ahh programming for a living…  There’s nothing quite like working on someone else’s problem for hours on end trying to get the dumb thing to do what you want.

Alright I’ll admit it; there are some days when I would rather be at home working on one of my own projects.  Those days I find it very hard to focus because one thought runs through my head.  “My own projects are fun and exciting and this is soooo BORING, I want to go home!”

But since my fun projects don’t put food on the table I have to snap out of it and get my head back into what actually does.  I find on those days it’s best to medicate the problem a little with my trusty iPod.  With my iPod I can do something for myself while still working and lately I have been on an audiobook and podcast kick.

iPods treat audiobooks and podcasts differently than they do music files.  For one you can leave a podcast, listen to something else, and later pick up where you left off.  An iPod won’t do this when you are listening to music.  Furthermore an audiobook can have chapter markings making it easier to find a chapter in a longer book.

iTunes makes it easy to create a music file for the iPod.  You can insert a CD and iTunes will import it at the push of a button.  Creating a proper audiobook however, is impossible with iTunes.  Oh sure you could import a whole CD as a single file but what if your audiobook covers multiple CDs?

I’ve had this problem for a while now.  My initial solution involved importing each track and numbering them in a way that they would line up in a playlist.  This works, but it’s annoying.  I can play the book but if I stop I have to remember which chapter I left off with.  If I don’t go back to the book for a couple of days I forget where I am and have to start over.  A couple of weeks ago I finally had enough and went searching for a better solution.

The solution to my problem came from a program called Chapter and Verse by lodensoftware.com.  The program will take a list of AAC (.m4a) files and convert them into one single audiobook (.m4b).  This makes creating an audiobook from a CD very easy.  Simply import the CD using iTunes in the AAC format and then use Chapter and Verse to make the conversion.

Chapter and Verse is not perfect however.  If files are not in the AAC format it will have to convert them into this format before creating the audiobook.  If you use a free audiobook solution such as librivox.org this means it’ll have to convert each file before combining them into one.  Not too big of a deal, just plan a little extra time when creating an audiobook from a set of mp3s.

Starting a Windows Forms .NET Application

This week at work I have been redesigning the flow of our internal application. When Sally gets into work the first thing she does is launch this internal application. The application opens to a login form which disappears when she enters her credentials. A secondary screen then shows up asking which set of data she would like to work with. She chooses what she’s been assigned to work on and waits for the data to load in a third screen.

The code required to launch such an application is very straight forward. The login form needs to be displayed and when it is dismissed the secondary form can be shown. The third screen can be launched off of the second and will stay alive as long as the second is open.

Program.cs

///
/// Entry point for the test application
///
static class Program
{
    ///
    /// The main entry point for the application.
    ///
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        // Ask the user to login
        Login.LoginScreen login = new Login.LoginScreen();
        Application.Run(login);

        if (login.DialogResult == DialogResult.OK)
        {
            // Run the main application
            Application.Run(new NewWindowApp());
        }
    }
}

Take a look at lines 17 and 22 in the code sample from Program.cs.  The calls to Application.Run(Form) start a message loop on the current thread enabling the form to receive Windows messages.  Application.Run(Form) essentially says to Windows, “Here take this form and show it to the user.”  The form that is supplied can launch other child forms but when it dies so does the whole application.

My goal this week has been to make Sally’s job a little easier by removing the secondary screen and building its functionality into the third screen.  Now normally this would be as simple as replacing line ten with Application.Run(new ThirdForm());.  However that will not work in this case because the third form has a very useful “New Window” button.

Oh, it will work just fine for a while. Sally can click the button leaving her with two views of the application. However the message loop is only hooked up to the first form and if it is closed both forms will die off.

The solution to this problem is in an overload to the Application.Run method that takes an ApplicationContext property. In fact the documentation at MSDN shows a partial solution to my problem.

Instead of supplying Windows with a Form to display to the user I now supply a custom ApplicationContext object.  This custom context can manage the new window call and keep the application alive no mater which form is closed.

Context.cs

/// 
/// Does the work of firing off new application windows.
/// 
internal class Context : ApplicationContext
{
    // The number of windows currently open
    private int mWindowCount;

    /// 
    /// Initializes a new instance of the  class.
    /// 
    public Context()
    {
        this.NewWindow();
    }

    /// 
    /// Creates and shows a new window.
    /// 
    public void NewWindow()
    {
        NewWindow window = new NewWindow(this);

        window.FormClosed += new FormClosedEventHandler(window_FormClosed);

        ++this.mWindowCount;

        window.Show();
    }

    // Close out the application when all windows have been exited
    private void window_FormClosed(object sender, FormClosedEventArgs e)
    {
        NewWindow window = sender as NewWindow;            
        if (window != null)
        {
            window.FormClosed -= this.window_FormClosed;
        }

        --this.mWindowCount;

        if (this.mWindowCount <= 0)
        {
            this.ExitThread();
        }
    }
}

It's important to note that this object knows how many windows there are.  Also note that as each window is created by NewWindow() a listener is added to the FormClosed event.  These two elements allow the context to know when the last form is closed and therefore when to finally exit.

Full source code for the project.