So far I have been enjoying the beta of Windows 7 and the new version of Windows Media Player 12. I would consider myself a heavy user of Windows Media Player. I use it to as my default music player, sync my music to my Windows mobile device, and to stream home movies to my Xbox 360. If something is not working quite right, I would probably encounter it.
There are a few bugs, with newest version of Media Player, that I hope will be fixed in the final version. I’ve had to rebuild my media library on several occasions and Media Player does not always recognize when I have added new music or videos. One of the features I miss, from version 11, is the ability to force it to look for new content. With version 12, the library seems to refresh with a background task and there is no way to force it. Note to Microsoft: Perhaps a better implementation would be a file watcher on the library directories.
If you want to rebuild you library,
- Shutdown windows media player.
- Stop the media sharing service. Go to Start->Control Panel->All Control Panel Items->Administrative Tools->Services. Right-click on the “Windows Media Player Network Sharing Service” and select Stop.
- Navigate to “c:\Users\”your username”\AppData\Local\Microsoft\Media Player. Now delete the files named CurrentDatabase_***.wmdb and LocalMLS_*.wmdb.
- Start the media sharing service.
- Now start Windows Media Player and watch it build up your library again.
Just like my previous post on dealing with unhandled exceptions in WinForms, I’ll be talking about how to accomplish the same thing in WPF. When your WPF application encounters an unhandled exception, the default .NET exception handler for WPF displays the following dialogs to the user.

There is not much information in these dialogs that would tell the user what went wrong. More importantly there is very little information for you as the developer in these dialogs that would help you find out what went wrong. Without stating everything I said in my previous post all over again, I’ll just say that there is a better option by replacing the default unhandled exception handler with your own.
My same disclaimer applies to this post as well. I grabbed most of the samples your about to see from MSDN (with some tweaks). However it was much harder to find them for WPF, and I had to combine two different sample apps to get you the complete solution for UI thread exception handling and non-UI thread exception handling.
Unlike the WinForm example, where we had to register for two events, there is only one DispatcherUnhandledException event that we have to be concerned about in WPF. You register your handler for this in the App.xaml.
1: <Application
2: x:Class="DispatcherUnhandledExceptionSample.App"
3: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5: StartupUri="MainWindow.xaml"
6: DispatcherUnhandledException="App_DispatcherUnhandledException" />
Next lets look at our code-behind for App.xaml and the handler function App_DispatcherUnhandledException().
1: void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
2: {
3: // Possible things to do:
4: // Write to the system event log
5: // Log to a text file
6: // Take a screen shot
7: // Display a friendly message to the user about what happened and what they should do next.
8:
9: // In this sample I am just dumping the error to a message box for you to see.
10: // THIS IS NOT A BEST PRACTICE.
11: var stringBuilder = new StringBuilder();
12: stringBuilder.AppendFormat("{0}\n", e.Exception.Message);
13: stringBuilder.AppendFormat(
14: "Exception handled on main UI thread {0}.", e.Dispatcher.Thread.ManagedThreadId);
15:
16: // attempt to save data
17: var result = MessageBox.Show(
18: "Application must exit:\n\n" + stringBuilder.ToString() + "\n\nSave before exit?",
19: "app",
20: MessageBoxButton.YesNo,
21: MessageBoxImage.Error);
22: if (result == MessageBoxResult.Yes)
23: {
24: // Save data
25: }
26:
27: // Return exit code
28: this.Shutdown(-1);
29:
30: // Prevent default unhandled exception processing
31: e.Handled = true;
32: }
There is not much required of this handler function other than the last line of code on line 31. We set the handled property to true so that the .NET framework does not display its own handler for the exception. If we were only concerned with exceptions that occurred on the UI thread, that would be all that is to it!
Next let’s look at how we can also handle exceptions
that occur in other worker threads by using a Dispatcher. I’ve created a simple form, similar to the
previous WinForm example, that has two buttons on it – one for generating an exception on the UI thread and another that generates an exception on a worker thread. Here are the important functions on that form:
1: private void StartSecondaryWorkerThreadButton_Click(object sender, RoutedEventArgs e)
2: {
3: // Creates and starts a secondary thread in a single threaded apartment (STA)
4: var thread = new Thread(this.MethodRunningOnSecondaryWorkerThread);
5: thread.SetApartmentState(ApartmentState.STA);
6: thread.IsBackground = true;
7: thread.Start();
8: }
9:
10: private void MethodRunningOnSecondaryWorkerThread()
11: {
12: try
13: {
14: WorkerMethod();
15: }
16: catch (Exception ex)
17: {
18: // Dispatch the exception back to the main UI thread. Then, reraise
19: // the exception on the main UI thread and handle it from the handler
20: // the Application object's DispatcherUnhandledException event.
21: int secondaryWorkerThreadId = Thread.CurrentThread.ManagedThreadId;
22: Application.Current.Dispatcher.Invoke(
23: DispatcherPriority.Send,
24: (DispatcherOperationCallback)(arg =>
25: {
26: // THIS CODE RUNS BACK ON THE MAIN UI THREAD
27: throw ex;
28: }),
29: null);
30:
31: // NOTE - Application execution will only continue from this point
32: // onwards if the exception was handled on the main UI thread.
33: // by Application.DispatcherUnhandledException
34: }
35: }
36:
37: private void WorkerMethod()
38: {
39: // This method would do real processing on the secondary worker thread.
40: // For the purposes of this sample, it throws an index out of range exception
41: string msg = string.Format(
42: "Index out of range exception raised on secondary worker thread {0}.",
43: Dispatcher.CurrentDispatcher.Thread.ManagedThreadId);
44: throw new IndexOutOfRangeException(msg);
45: }
Where all of the trickery happens is in the MethodRunningOnSecondaryWorkerThread() function. Here we have a try catch block with a generic exception handler (code smell – I know, but there is no way around it) that wraps the execution of the WorkerMethod(). After we catch the exception, we invoke a Dispatcher to execute some code on the main UI thread. All we do is re-throw the exception and our handler for the DispatcherUnhandledException event, in App.xaml, will catch the error. That’s it.
You can download the full sample code
here.
Okay here is the scenario for this blog. Your application crashes, and the user gets this cryptic error dialog that they have seen a hundred times with other applications, and they dismiss it without blinking an eye. They call you up or send you an email that said they had a problem with your app. The first thing you ask is “Did you click on the Details button”? The answer is almost always no. It gets worse. If your application had an unhandled exception in a worker thread, then the thread will exit silently, and the user may not even know there is a potential serious problem. You can try to look in the application log to see if you can figure out the problem, but you may not see the error that caused the crash because your application did not handle it. Alas, you have to resort to try and re-create the problem which may or may not pan out. Despite all of your good programming practices of using mocks, and unit tests to figure out every conceivable error scenario, your app somehow encountered an exception you didn’t expect.
Wouldn’t it be nice if the user was presented a nice friendly message telling them you are sorry your application encountered a problem and give them a chance to save any data? In the background you could also be logging the heck out of every detail of the exception and even taking a screen shot at the instant the crash happened. This blog will explain how you can be notified of an unhandled exception, and insert your own handler instead of the .NET framework version that shows that lovely dialog. This blog will describe how to do this in Winforms. My next blog post will describe how to deal with unhandled exceptions in WPF.
First a disclaimer. All of these code snippets are taken directly from MSDN (with slight modification). I hate bloggers that just regurgitate MSDN docs. In this case however, there are so many developers out there that do not know about this feature -- I feel compelled to blog about it to raise awareness.
There are two types of handlers you need to register in your WinForms application. The ThreadException event is for unhandled exceptions in your UI thread, and the UnhandledException event is for non-UI thread exceptions. The later event needs to be registered for every AppDomain that your application creates.
Lets take a look at some code:
1: [STAThread]
2: [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
3: private static void Main()
4: {
5: // Add the event handler for handling UI thread exceptions to the event.
6: Application.ThreadException += UIThreadException;
7:
8: // Set the unhandled exception mode to force all Windows Forms errors to go through
9: // our handler.
10: Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
11:
12: // Add the event handler for handling non-UI thread exceptions to the event.
13: AppDomain.CurrentDomain.UnhandledException += CurrentDomainUnhandledException;
14:
15: Application.Run(new Form1());
16: }
This is the main function of your WinForms app. On line 2 we have an security attribute that gives our application permission to add our event handler to the AppDomain. To add a handler for unhandled exceptions on the UI thread, requires two lines of code. Line 6 adds our handler for unhandled exceptions on the UI thread and the second, on line 10, tells the framework how we want the exception to be routed. In this case, we want to catch the exception. Next, on line 13, we can add our handler for non-UI thread unhandled exceptions. Now that all of the handlers are registered, we can show the main form.
Now lets look at our handler for the UI thread:
1: private static void UIThreadException(object sender, ThreadExceptionEventArgs t)
2: {
3: DialogResult result = DialogResult.Cancel;
4:
5: try
6: {
7: result = MessageBox.Show("UIThreadException caught", "Error", MessageBoxButtons.AbortRetryIgnore);
8: }
9: catch
10: {
11: Application.Exit();
12: }
13:
14: // Exits the program when the user clicks Abort.
15: if (result == DialogResult.Abort)
16: {
17: Application.Exit();
18: }
19: }
When an unhandled exception occurs on the main UI thread, you do have the option, in your handler, to figure out if you want to ignore the error or retry the last operation that failed. I do not recommend this. In my opinion, you would have to know what kind of error occurred in order to know if it is safe to continue running the app. Since this is a handler for errors you are not expecting, it doesn’t seem likely you will know what to expect here. All of your expected exception handling should be done elsewhere in your code.
Next lets look at the handler for the non-UI thread:
1: private static void CurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
2: {
3: try
4: {
5: Exception ex = e.ExceptionObject as Exception;
6: MessageBox.Show("CurrentDomainUnhandledException caught", "Error", MessageBoxButtons.OK);
7: }
8: finally
9: {
10: Application.Exit();
11: }
12: }
When this handler is called, you have no choice but to exit the application after the handler is finished. Just log what information you can, notify the user with a friendly message, and exit.
Some final notes on the handlers. Hopefully you have noticed that both of my handlers are wrapped in try blocks. It is very important that your handler function does not throw any exceptions. Also do not follow my example by showing a message box with all of nitty-gritty details of the exception. The user does not care, and they are not reliable to relay that information back to you. Give the user a friendly message that you are sorry that the application has encountered an error, and give them instructions on what to do next. Give them a link to submit a problem report, or tell them how to gather application logs. Also you may give them an option to save any data (although the data may be corrupt at least it is will not be gone).
You might be asking the question at this point. Why can’t I just put a try catch block around Application.Run()?
1: try
2: {
3: Application.Run(new Form1());
4: }
5: catch (Exception)
6: {
7: // handler code here.
8: }
This will work for most exceptions. However before .NET 2.0, there were non-CLS compliant exceptions that did not inherit from System.Exception. I don’t know if there are any exceptions out there today that do not inherit from System.Exception. Also I do not know what happens if I reference an assembly that was built on the older .NET 1.0 and that assembly throws a non-CLS compliant exception. Also it will not work for exceptions in non-UI threads. I just do not want to take the chance. This solution is bullet proof and will work for every type of exception that is encountered.
You can download the full sample code here.
With Windows 7 arriving around the corner, you may be considering letting go of XP or Vista and upgrade your computer to Microsoft’s latest operating system. From my experience so far, with the Beta version of Windows 7, it is definitely worth the upgrade. Upgrading your OS however can be a scary undertaking. You can use Microsoft’s Windows Easy Transfer application to help you move your user accounts, email, and documents, but you will not know if you have missed something until it is too late. What if you forget to transfer a file that you need, or what if a program you use suddenly does not work anymore? Whether you choose the Windows 7 upgrade option, which keeps your files and programs intact, or start from scratch with a brand new install, this is how you can create a worry-free backup of your old operating system. I am going to walk you though how you can create a virtual machine of your current computer, so that you can run your new and old OS at the same time – making it easy to transfer files between the two.
Step 1: Clean house on the old OS.
This step is optional -- especially if you have plenty of external hard drive space. Nevertheless, it doesn’t hurt to delete your temporary internet files, old restore points, and other data you know for sure you will never need to make the virtual machine of your old operating system as small as possible. You can get started on this task by firing up the Disk Cleanup wizard. An easy way to find the cleanup wizard is to press the windows key
and type “disk cleanup” into windows search (without the quotes). When the wizard fires up, go ahead and choose the option to clean files from all users of your computer. Go ahead and check all of the components and clean up your files. Next, click on the “More Options” tab and clean up your old system restore points.
Now let us take a look at how much space you will need to create your virtual machine. Press the windows “start” button and then click on “computer”. Right-click on your c: drive and select “properties”. Pay attention to the amount of used space. This is approximately how much free space you will need on an external or network drive in order to create your VM. If you do not have enough space, then consider buying a bigger external hard drive or freeing up more space from your old OS.
Step 2: Create a virtual machine of your old OS
- Shut down as many running programs as you can. This prevents your data from being out of sync during the conversion.
- Download and install VMware’s free converter tool (I am using version 4.0) to create your virtual machine.
- When you run the converter, make sure you right-click and “run as an Administrator”.
- Click on “Convert Machine” which launches the conversion wizard.
- When asked about the source type, select “powered-on-machine” and then select the “This local machine” option.
- Now we are ready to select a destination for your virtual machine files. When you are asked for a destination type, select “VMware Workstation or other VMware virtual machine”.
- Next select VMware Player 2.5.x as the product.
- Select a location on your external drive and give the VM a name.
- When you reach the view/edit options section, click edit on the “data to copy” list item. Now select the disk volume marked as c: to convert. Make sure you un-check your external drive. You can modify the size of the disk on the VM to conserve space. Click on the “Target size” column entry to set the VM disk size to be the minimum or enter your preferred size.
- Edit the “Devices” list item and reduce the memory to be half the amount on your computer. This should divide the available memory between your VM and new OS.
- Edit the “Advanced options” list item and select the “Install VMware Tools” option.
- After finishing the wizard, the conversion will take several hours. It is probably best to run this overnight.
Step 3: Manual config
VMplayer is free, so they “hide” certain customizations that their commercial products provide. Luckily you can edit the .vmx file of your VM manually easily. One such customization is to add a shared folder so that you can easily transfer files to/from your VM to your new OS. Open up your .vmx file in a text editor, like notepad, and make sure the following lines are in the file:
isolation.tools.hgfs.disable = "FALSE"
sharedFolder.maxNum = "1"
sharedFolder0.present = "TRUE"
sharedFolder0.enabled = "TRUE"
sharedFolder0.readAccess = "TRUE"
sharedFolder0.writeAccess = "TRUE"
sharedFolder0.hostPath = "C:\Users\username\Documents\vmshare"
sharedFolder0.guestName = "vmshare"
sharedFolder0.expiration = "never"
Change the hostPath to a folder on your host OS that you want to share. The guestName is just the name of the shared drive that will appear on the guest OS.
Step 4: Test it
Before you upgrade your machine to the new OS, you can test the new VM to be sure it runs okay. Install VMWare’s free player (I am using version 2.5.1). Double click on your vmx file and you should see a clone of the machine you are currently running.
To enable the shared folders, click on the “VMware Player” menu item on main window and select “Shared Folders” and click enable.
Step 5: Install the new OS
Now that you are sure that you have a valid working backup of your OS, go ahead and upgrade to your new OS.
Step 6: Install VMplayer on the new OS
Once your new OS is up and running and you are pretty confident that it is working, you can install VMPlayer on your new OS and run your virtual machine. Make sure you enable your shared folder by following the same steps that I mentioned in step 4. Now you can transfer files to and from your old and new OS. Once you are confident that you have all of your files and settings transferred, you can delete your VM.
Note: As of the writing of this blog, there is a problem accessing the internet from your VM if your host OS is Windows 7 Beta. Since I only used my VM to transfer my files to my new OS, I didn’t need the internet. Hopefully this bug will be fixed by either Microsoft or VMware.
Bazaar is a great distributed version control system, but when it comes to diff and merging, command-line tools fall flat. Thankfully there are plugins and external tools available to make it easier. Here is a step by step guide to get them installed on your machine.
When Bazaar encounters a conflict, it does a three-way merge (the common parent, your changes, and the other persons changes). This makes merging easier because you can see what the file looked like before the conflict occurred. The diff/merge tool that I use is KDiff3. It can do three-way merges and two-way diffs.
- Install KDiff3 to a location that does not have a space in the path.
- Add the KDiff3 install directory to your PATH environment variable (this is needed for the extmerge plugin that we will be installing later)
- You may have to reboot your computer for the PATH to take effect.
- If you don't already have one, create a file called bazaar.conf in the %APPDATA%\Bazaar\2.0 directory. On my Vista OS this expands to c:\users\yourusername\AppData\Roaming\Bazaar\2.0. Now add the following alias to the file using your install directory for kdiff3:
[ALIASES]
kdiff = diff --using c:/tools/kdiff3/kdiff3.exe
Note: We have to tell bazaar that we are using an external tool when we perform a diff. This creates an alias so that you do not have to type the following “bzr diff –-using kdiff3.exe somefilename” every time you want to see a diff. All you have to type now is “bzr kdiff somefilename”. You can change the name of the alias (kdiff) to be whatever you want (except for the word diff of course).
- Open a command prompt and navigate to your Bazaar plugin folder. On my machine it is in the following location C:\Program Files\Bazaar\plugins. Now type the following command:
bzr branch lp:bzr-extmerge extmerge
Note: This installs a plugin called External Merge. It allows external tools to be used for merging. KDiff3 is one of the tools it supports.
How to use it.
To see changes you have made to a file. See bzr –help diff for more options for prior revisions and such.
bzr kdiff somefilename.cs
If bazaar tells you that you have a conflict after merging, type these commands. Note: emerge and the extmerge command can be used interchangeably. You can also create an alias if you don’t like the name.
bzr emerge –all (finds all conflicts and displays them one by one)
or
bzr emerge somefilename.cs
When you are finished merging, run the following.
bzr resolve –all (resolves all conflicts in the tree)
or
bzr resolve somefilename.cs
When you are all finished, KDiff3 will leave a file called somefilename.orig which is your original file before merging. To prevent accidentally adding this file to your repository, you can add the *.orig extention to your global ignore file.
This weekend I had the opportunity to participate in the first Ann Arbor Give Camp. I wasn't sure what to expect at first since this was the first event of its kind in the area, but I had a blast! At the end of the week, all of the charities that participated walked away with some great projects and the developers walked away with an amazing experience.
Dan Hibbitts originally convinced me to attend give camp with the lure of working on adding digital signing to a mobile app for Forgotten Harvest. I've been interested in learning more about mobile development, so I decided to give it a try. I worked with Dan at Media Station for a few years and was looking forward to working on the same project with him again. Martin Shoemaker was the other developer on the project. Besides his reputation on UML, Martin also has a lot of mobile experience. Our team was set to crank out this project in a weekend.
On the first day we met with the representative from Forgotten Harvest Donald Washington.
As he explained, the person who wrote the proposal for Give Camp didn't understand completely what Forgotten Harvest's needs were. They already had a mobile app that would capture digital signatures from food donors or recipients of the donated food. What they needed was to get the digital signatures integrated into their database and signed receipts to be automatically emailed or faxed to the recipient. Their current process required many manual steps. Some Give Camp teams were able to start from scratch on their database projects, but Forgotten Harvest had a large pre-existing database in Access 2000 with many custom forms, so we had to integrate with their database. It was a lucky thing that Dan is also an expert on anything having to do with databases, I had some ADO.Net experience to facilitate talking to the Access database, and Martin and I had plenty of desktop windows experience to knock out the necessary automation.
There were over a hundred developers that signed up to volunteer their time developing for local charities. Each charity had a team of anywhere between 3 to 7 developers working on their project. Each room at Washtenaw Community College had two development teams in them. The other team occupying our room (BE-240) was the team developing for NOCIRC of Michigan. Jeff McWherter headed up a team that was working on a web site that allowed any charity (not just NOCIRC) to keep track of and schedule volunteers for upcoming events. He drafted (conned) many developers to help him out. It seemed that every day there was someone new working on the project. I follow many of the people in the room on twitter, so it was great to work with them for a weekend and get to know them better.
Both teams had an incredible amount of work to do in a short amount of time. The chemistry in the room was shall we say very jovial. Jokes (good and bad) were flying all weekend long. It was probably the opposing forces of the intense caffeine we were drinking and our lack of sleep that made the atmosphere in the room so much fun.
In the picture you can see Jeff wearing a florescent head band -- Jeff stayed up for over 40 hours before he took a cat nap. In the back you can also see another developer Marc (can't recall his last name) who downed a stack of 8 monster energy drinks and one red bull. Yikes!
Sometime on Saturday Dan decided to play episodes from "The IT Crowd". A very funny English sitcom on the overhead projector while everyone worked. This was very natural to me since most evenings I enjoy having a movie on in the background while I get some work done on the computer.
The Give Camp was also a good opportunity for developers to learn new skills. The environment of the room provided a great opportunity for people to pair program on projects. On the NOCIRC team there were a couple developers that learned C# and ASP.NET for the first time. On the first day Martin and I paired up and both of us learned little things here and there from each other. On Saturday I wrote a windows service for the first time and learned a few good techniques on how to debug it. Neither Martin or I wanted to pair with Dan since he was stuck working on the Access 2000 database. Sorry Dan.
I would like to thank all of the organizers of the Ann Arbor Give Camp for putting together a great event for the charities and also the developers of room BE-240 for making the experience a lot of fun. If you get a chance, make sure you attend a Give Camp. It is definitely worth it. I can't wait for next year.
Welcome to my blog. I'm going to use this first post to explain a few things. While my life's path is one not many have shared, the end of the journey is pretty clear: I am thoroughly enjoying my job as a .NET developer for SRT Solutions. When I first joined the programming community, I'd casually mention my former career to a coworker and I'd wind up slightly embarrassed as they'd inexplicably burst out laughing. I stopped bringing it up and recognized that, to the casual observer, my two careers seemed so far apart. Even comic writers made fun of the disparity.

Today I realize that my past influences who I am and how I work. If it weren’t for some strange twists of fate and many years of hard work, I would be in a very different place right now . . . so sit back for a minute. I’ll tell you the story of how a guy who always saw himself behind the wheel of a tractor somehow found himself firmly planted in front of a computer monitor.
I grew up on a family farm, and for as long as I could remember, that was the life I wanted to live. I finished high school and enrolled in the Institute of Agricultural Technology program at Michigan State University with a major in fruit & vegetable production (don't laugh -- well, okay). For the next 5 years I raised tomatoes, sugar beets, green beans, corn, wheat, and soy beans. Together with my family I raised around 700 acres -- 300 of which was tomatoes (not the kind you put on a sandwich, but the kind you make ketchup and salsa out of). I was always interested in computers. When I was a teenager, I used to sneak onto my brother's DOS computer and play Castle Adventure. When I farmed, I used Lotus 123 to calculate payroll taxes for my 30 seasonal employees and other accounting tasks. Computers were becoming a great tool for farmers.
Unfortunately, mother nature wasn’t so cooperative: two of my first five years happened to see the top two droughts in the past 30 years. In other words, I worked hard and didn’t make any money. Political decisions didn’t help my situation, either. NAFTA all of a sudden made it cheaper for Hunt & Wesson (the only local tomato plant) to haul tomato paste in from Chile than to process tomatoes locally. It was a hard hit for my family. After 30 years of raising tomatoes, we were one of many farms trying to sell a $200,000 tomato harvester and other specialized equipment and get out without losing everything.
I was able to sit down with my dad and brother and work out a deal that allowed them to keep their farms running. Without tomatoes, the number of acres we raised could support two families, but not three. As the youngest contributor and with the least invested, I was no longer a farmer. Newly engaged and hoping I’d someday be able to pay off my remaining debt and start over, I essentially turned in my keys to the John Deere and frantically began trying to figure out what the heck I could do if I wasn’t waking up every day and walking out to the barn.
I found my answer a few months later.
You see, farmers spend a lot of time hauling stuff around in big trucks, so I did have a commercial driver’s license that landed me a job that first winter as a truck driver. It didn't take me long to figure out that the life of a truck driver was not for me. As fate would have it, the trucking company I worked for had just invested in a computerized dispatching system. Nobody knew how to run the thing, though, and since I knew how to turn a computer on and was good with people, they determined I was just the guy they were looking for to run their dispatch operation. I was working with computers all day, but it still wasn't a job I could be happy with long-term. On the advice of wise old truck driver who told me I didn't belong there, I started to look for other options. I spent time that winter watching the software engineer who came in to customize the dispatch system. He’d charge around $300 an hour (I now know he was really ripping them off), and I decided that as long as I was determined to completely start over, I wanted that guy’s job.
Growing (creating) something is very familiar to a farmer. I decided a degree in computer science would let me create things on the computer, but it was a long road. As I planned my wedding, I tried to convince colleges that a history of courses titled “Diesel Engines” and “Weed Management” didn’t mean I wasn’t capable of tackling Calculus IV. I tucked away my Spartan sweatshirt and graciously donned my maize and blue and spent the next four years at the University of Michigan. I continued to pay off farming loans as I accumulated student loans. My first little girl was born three weeks before my final exams. That month, I finished my B.S. in computer science and accepted a job developing computer games. I was in heaven. Life was good.
In an ironic twist, I learned the software engineering profession can also suffer droughts. I entered the .com field just before it crashed. I watched one employer go bankrupt, and at another job I experienced an eerie déjà vu as I spent weeks in Korea retraining my cheaper replacement. Chilean tomato paste, anyone? It’s different now, though, because I have a unique skill set: the fortitude of a farmer and the diverse code-solving skills of a man who wakes up each day excited by every new thing that comes along in this profession. I love what I do; I credit my past for making me the developer I am.
So that’s me. The guy who had a string of bad luck that took him away from something he loved and somehow led him to something he is just as excited about. "Retrain all the farmers as computer programmers!" Count me in. Sometimes you do what you gotta do. And if you’re lucky, sometimes you really like where you end up.