Monday, October 31, 2005

Having been turned on to a review by Orson Scott Card (one of my favorite authors - Ender's Game, anyone?) several weeks ago, I decided to go out an check out the movie. Oh my goodness!  It was awesome!  I've rarely had a movie experience quite like that - fantastic!  Even not knowing the backstory one bit I was completely engrossed in the film and quickly fell in love with the characters.

After seeing it we decided to go out and purchase the complete series of Firefly, the FOX television series on which the movie was based.  I was very impressed!  I'm now an addict of the series and really want to see the movie again (though I'll probably wait until it comes out on DVD unless someone wants to go with me [hint, hint]).  It's too bad they didn't keep up a good series, though FOX, it seems, does that: throws out good, quality stuff for trash.  I can't think of the last time I actually watched something on FOX since the X Files or the occasional The Simpsons episode.

Oh well, Firefly is beautiful...what a cool show!

Monday, October 31, 2005 3:14:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

<sarcasm>Well, today's a funny day.</sarcasm>  I was starting to reset an older machine (one that I've had for several years), when I heard the now-all-too-familiar 'click, click, whirrr, click'.  Well, chalk that up to another hard drive gone...I have a stack of 5 hard drives on my desk now that have failed within the past several months.  Se la vi.  Oh well.  I guess I'm beyond feeling now - this crash didn't even affect my disposition today, except that it made me laugh.

Fortunately, I had backed up all valuable information from the drive - so no data lost (this time).

Monday, October 31, 2005 6:15:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Saturday, October 29, 2005

This is, by now, old news, but if you're an MSDN subscriber, you'll definitely want to go get the RTM (Release To Manufacturer) versions of VS.NET 2005 and SQL Server 2005.  If not, but you anticipate running .NET 2.0 applications (which many undoubtedly will), the released 2.0 Framework is now available for download.

This is an exciting time!  I can't wait for the launch event.  I'm heading to San Francisco for the launch on the 7th.  If you're going to be there, drop me a line and let's get together!

Saturday, October 29, 2005 5:53:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Monday, October 24, 2005

Several months back I wrote an article wherein I described creating an IDE.  This IDE was (and still is) pretty cool.  It has the ability to take an ASP.NET .ascx file, parse it for custom controls, render the controls using alternative text, and associate property values with what I refer to as a meta-instance.  In fact, that is one of the cool, geeky elements of this application; the application creates instances of these controls dynamically without having a container for the controls and can map the control text in the textbox back to an actual in-memory instance of a control that isn't defined within the code.

The magic that facilitates this capability is dynamic IL generation via the System.Reflection and System.Reflection.Emit namespaces.  I will not take the time to illustrate how these work or even my motivations in designing the application in this way.  For those explanations, please see the original posts (Part I and Part II).

I had to make some alterations of the code and wanted to post some insights and strategies that I employed to achieve my goals.  Firstly, the IL that I spit in the previous iteration had some pretty simple and brainless methods.  In other words, the property methods were simply getters and setters - they took the value and assigned it to the private field or returned the private field's value, as in the following code.

NOTE:  All of the code in this article has been greatly simplified and altered to protect NDA and IP.  While this code is pretty specific and not very extensible, the actual code is much more extensible and reusable.

// the getter method which is functionally equivalent to the following:
// public int Age {
//    get { return _age; }
// }
//
// variables already initialized:
// tb == TypeBuilder
// pb == PropertyBuilder
// fb == FieldBuilder
//

MethodBuilder getter = tb.DefineMethod("getAge", MethodAttributes.Public, typeof(int), Type.EmptyTypes);
ILGenerator il = getter.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fb);
il.Emit(OpCodes.Ret);
pb.SetGetMethod(getter);

Similarly, my setter method was very vanilla in that all it did was accept a value and blindly assign it to the underlying field:

// the setter method which is funcionally equivalent to the following:
// public int Age {
//    set { _age = value; }
// }
//

MethodBuilder setter = tb.DefineMethod("setAge", MethodAttributes.Public, null, new Type[] { typeof(int) });
ILGenerator il = setter.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, fb);
il.Emit(OpCodes.Ret);
pb.SetSetMethod(setter);

This is all well and good - especially for properties that simply contain their assigned values.  No sweat.  However, what happens in the event that we need to perform validation on the values being assigned?  The proposition gets trickier, because now we need to emit IL that is a tad more complicated and we have to understand how to manipulate the stack in IL and know when to push and when to pop...that, or know how to interpret IL (via ILDASM.exe) and retrofit it into our code - something that is quite easy, but fraught with a learning curve.

Due to the nature of this application, I could make some allowances and take some shortcuts.  Suppose I want the following code:

public int Age{
   set {
      if ( isValidAge(value) )
         _name = value;
      else
         MessageBox.Show("Invalid age: " + value);
   }
}

Now I have to perform validation on the arguments, call a function, concatenate a string, pass it to the message box, etc.  The ante has been raised, but it's not something that can't be overcome.  Notice the isValidName() function.  I don't want to have to emit a validation method in the dynamic assembly; instead, I can create a generic validation method in my source and have my dynamic assembly call back into the host assembly.  This would, of course, require that we know, upon creating the dynamic method, what function to call, what parameters to supply, and how to validate the value.

For this purpose, I propose to create a class in the host assembly that performs this validation in a generic way.  We could then provide the builder with the metadata necessary to call the appropriate validation function, and have it return the appropriate result.  Following the pattern outlined above, we might end up with the following:

public sealed class Validators {
   public static bool IsValidAge(int value) {
      return ( value < 0 ) ? false : true;
   }  
}

Ok, very contrived, but I'm illustrating a point...and a point that we'll rectify here momentarily.

The IL necessary to generate the new setter that calls this function is substantially more involved, but we'll illustrate it with comments:

// get the pointer to the methods.
// in order to call the various methods, we need to first have the appropriate metadata so the
// emitter can effectively map the calls
//

MethodInfo methodValidator = typeof(Validators).GetMethod("IsValidAge", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new Type[] { typeof(int) }, null);
MethodInfo methodStringConcat = typeof(string).GetMethod("Concat", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new Type { typeof(string), typeof(string) }, null);
MethodInfo methodMessageBox = typeof(System.Windows.Forms.MessageBox).GetMethod("Show", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new Type[] { typeof(string) }, null);

// now we need to create the jump points to control program flow.
// Despite the simplicity of the code we're emitting, we have a conditional block
// which requires application branching and flow control.
//
Label labelInvalidProp = il.DefineLabel();
Label labelEndOfMethod = il.DefineLabel();

il.Emit(OpCodes.Ldarg_1);                              // .. push the value parameter
il.EmitCall(OpCodes.Call, methodValidator, null);      // if ( isAgeValid(...) ) {
il.Emit(OpCodes.Brfalse_S, labelInvalidProp);          //
il.Emit(OpCodes.Ldarg_0);                              // .. push the this parameter
il.Emit(OpCodes.Ldarg_1);                              // .. push the value parameter
il.Emit(OpCodes.Stfld, fb);                            //    _field = value;
il.Emit(OpCodes.Br_S, labelEndOfMethod);               // }
il.MarkLabel(labelInvalidProp);                        // else {
il.Emit(OpCodes.Ldstr, "Invalid age: ");               // .. push the first string to concat
il.Emit(OpCodes.Ldarg_1);                              // .. push the value parameter to concat
il.EmitCall(OpCodes.Call, methodStringConcat, null);   // .. push the result of the concatenation
il.EmitCall(OpCodes.Call, methodMessageBox, null);     //    MessageBox.Show(...);
il.Emit(OpCodes.Pop);                                  // .. ignore return value
il.MarkLabel(labelEndOfMethod);                        // }
il.Emit(OpCodes.Ret);

This code is pretty slick, but it isn't without its problems and issues.  These issues are made manifest mostly by the problem domain of the issues that I'm solving.  First of all, the only mechanism that I'm enabling for changing property values is a PropertyGrid control.  If I display my own message box, I steal focus from the control and it makes the application more difficult to use.

Second, this method of data validation is not very 'OO'.  I have to create a validator method for each property that I want to validate that falls outside the scope of the object.  What if I had another object with an Age property and I needed to validate it differently, or against a different range?  Using this method, I'd have to create a separate method for each object, for each property to be validated.  If I did that, I might as well forego the entire dynamic code generation approach and create hard classes with their own properties and validators...but that violates my original design.  I want to keep this cleaner.

Therefore, I've come up with a solution that is substantially more flexible, leverages the functionality of the PropertyGrid control, is ultimately easier with respect to the validator methods, and the IL is cleaner.  All in all, it sure seems like a win-win.  In order to implement it properly, I have to go back to how the properties are created in the first place.  After reading Posts I and II, you'll undoubtedly recall that I have an interface (IToolPropertyDefn) that provides a mechanism for referencing properties of the control/object.  Well, in order to provide control-specific validation, I've extended the interface to support a new property called ValidationData.  This class contains information regarding the method to call and the parameters needed.

internal sealed class ValidationData {
   private MethodInfo   _validatorMethod;
   private Type[]       _parameters;
   private object[]     _values;
  
   private ValidationData(string methodName, Type[] parameters, object[] values) {
      _validatorMethod = typeof(Validators).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, parameters, null);
      _parameters = parameters;
      _values = values;
   }
  
  
   internal static ValidationData GetIntRangeValidator(int min, int max) {
      return new ValidationData("ValidateIntRange", new Type[] { typeof(int), typeof(int), typeof(int) }, new object[] { min, max });
   }
  
   // methods and properties to return private fields
   // ...Type[] GetParameters();
   // ...object[] GetValues();
   // ...MethodInfo ValidatorMethod;
}


public sealed class Validators {
   public static void ValidateIntRange(int value, int min, int max) {
      if ( value < min || value > max )
      throw new ArgumentException(string.Format("Invalid value.  Value must be between {0} and {1}.", min, max));
   }
}

I am, of course, simplifying my code substantially for a variety of reasons mentioned above, but am still leaving the gist of it so that it's understandable.  The idea is the ValidationData constructor takes a method name, parameters, and the set of values to be passed to the validator.  The constructor then resolves to the actual method (found in a different class) to perform the validation.  At this point, we must change the generated IL code to call this new validation method.

Note also that the validator method no longer returns a boolean value but instead throws an exception if the value fails validation.  This takes advantage of the functionality built into the PropertyGrid that if an exception is thrown from the property setter the PropertyGrid will catch it and display an 'Invalid property value' error message with the description of the exception in the 'Details'.  Additionally, the focus will remain on the PropertyGrid which is a good thing.

The updated IL generator then looks more reminiscent of the following where defn is the IToolPropertyDefn implementation (note: greatly simplified for clarity):

MethodInfo setter = tb.DefineMethod("set" + defn.Name, MethodAttributes.Public, null, new Type[] { defn.DataType });
ILGenerator il = setter.GetILGenerator();
if ( null != defn.ValidateData ) {
   il.Emit(OpCodes.Ldarg_1);                              // .. push the value parameter
                                                          // .. push the other parameters
   object[] values = defn.ValidateData.GetValues();
   Type[] parameters = defn.ValidateData.GetParameters();
   if ( null != values ) {
      for ( int i = 0; i < values.Length; i++ ) {
         // evaluate the type of parameters and push them onto the stack appropriately
         // for simplicity, show just the 'int' variety for illustration purposes:

         il.Emit(OpCodes.Ldc_I4, (int)values[i]);
      }
   }
   il.EmitCall(OpCodes.Call, defn.ValidateData.ValidatorMethod, null);
}
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, fb);
il.Emit(OpCodes.Ret);
pb.SetSetMethod(setter);

Again, the code has been greatly simplified, but it makes for a much more extensible model.

Monday, October 24, 2005 2:56:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Saturday, October 22, 2005

Last night was a blast!  About a month and a half ago, two former co-workers and I put the plans in motion to have a PPI reunion of sorts wherein we would invite the various trainers and employees of PPI (Productivity Point International) who worked together 6 years ago when the Utah branch was closed down.  We had so much fun together and had created such strong bonds that we thought it would be a great event.

Well, six of us got together last night and spent about 3 1/2 hrs catching up and having a ball.  All told, those present included me (Aaron Zupancic), Dale Byrd, Joyce Raymundo, Wendy Bloomquist, Marja Larsen, and Kelland Coleman.  What a fun time!  We're planning on getting back together again in March, hopefully this time with a greater turnout.

Thanks all for coming!

Saturday, October 22, 2005 7:31:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, October 20, 2005

How exciting!  Today I received an email from Microsoft indicating that I have been nominated for an MVP award!  We'll see what happens come January!

Thursday, October 20, 2005 6:07:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback

Call be masochistic, but I kinda like nuts and bolts programming. I enjoy finding ways of optimizing the code that runs (or should run) completely behind the scenes that provides the infrastructure and support to the “higher-level” objects - whether they be business object, UI, etc.  How many out there use data readers, like the SqlDataReader, OracleDataReader?  Like almost everything, they have their place and are frequently (mis)used.  I fear that many developers (myself included) have written code similar to the following in a generic 'data access' class:

internal sealed class DataAccess {
   internal static SqlDataReader QueryReader(SqlCommand cm) {
      // ...include the code to retrieve data reader and return it
   }
}

internal sealed class EntryPoint{
   public static void Main() {
      using ( SqlDataReader dr = DataAccess.QueryReader(cm) ) {
         // ...iterate over the reader, utilizing the data
      }
   }
}

This, I believe, is a fairly common pattern that developers employ.  Is there anything wrong with it?  Well, I think there is.

First of all, there are no constraints on how the data reader is used and consumed.  In other words, the consumer class (in this case the EntryPoint class) could hold on to the SqlDataReader indefinitely and thereby maintain a connection to the database indefinitely (the data readers are tied to a connection to the database).

Second, your code that walks the reader's contents is tightly coupled with the flow of the procedure.  You have to include your code to iterate the reader inline (or at least delegate the reader to a worker method) to extract the data and it's not as reusable in that fashion.

So what's a better solution?  What do I propose?  Well, I prefer a slightly different approach to how a data reader gets passed back to a consumer that puts tighter constraints on the lifetime of the reader (though it can still be abused by malicious code) and helps compartmentalize how a reader is used.

I suggest using a delegate (a safe function pointer class) as a vehicle to return the data reader out.  Let's take a look at a simple example and then discuss how to augment the functionality to make it even better, and then we'll step it up a notch for .NET 2.0 and we'll leverage some functionality therein.  On to the simple example.  Disclaimer: Please bear in mind that there are other methodologies and objects that provide an ability to get data from a database in a disconnected manner (from DataSets, XmlSerialized objects, etc).  I am merely illustrating how I prefer to work with readers in a semi-practical example.

Simple Example:

Here we create a delegate that has as its parameter a data reader.  The consumer sets up the function to be called via the delegate and then invokes the data access class.  The data access class then takes a command object, executes it, and then calls the callback function (via the delegate), passing the reader.  Once the callback function finishes, resources are cleaned up and the data access method exits.

internal delegate void DataReaderCallback(IDataReader dr);

internal sealed class DataAccess {
   private static SqlConnection getConnection() {
      SqlConnection cn = new SqlConnection(“Data Source=(local); Initial Catalog=AdventureWorks; Integrated Security=SSPI“);
      cn.Open();
      return cn;
   }

   internal static void QueryReader(SqlCommand cm, DataReaderCallback callback) {
      SqlConnection cn = getConnection();
      SqlDataReader dr = null;
      try {
         cm.Connection = cn;
         dr = cm.ExecuteReader(CommandBehavior.CloseConnection);
         if ( null != callback ) callback(dr);
      }
      finally {
         if ( null != dr ) ((IDisposable)dr).Dispose();
         if ( null != cn ) cn.Dispose();
         cm.Connection = null;
      }
   }
}

internal sealed class Worker {
   private ArrayList _list;

   internal void DoWork() {
      SqlCommand cm = new SqlCommand(“SELECT Name FROM Production.Product WHERE Name LIKE '[A-C]%' ORDER BY Name“);
      DataAccess.QueryReader(cm, new DataReaderCallback(this.callbackFn));
      for ( int i = 0; i < _list.Count; i++ )
         Console.WriteLine(“{0}: {1}“, i, _list[i]);
   }

   internal void callbackFn(IDataReader dr) {
      _list = new ArrayList();
      while ( dr.Read() )
         _list.Add(dr.GetString(0));
   }
}

This solution is pretty slick but it has, in my eyes, a drawback: there must be shared state between the callback function and the method that invokes the reader.  Namely, in this example, the ArrayList that gets built in the callback function.  Let's make a simple enhancement to our delegate and see where that takes us:

Enhanced Example:

This example has a delegate that not only passes the data reader, but returns an object.  In effect, the data access method will call back on the delegate which will build up its state and return it.  The return value is then forwarded on to the consumer as the return value from the QueryReader method as a populated object.

internal delegate object DataReaderCallback(IDataReader dr);

internal sealed class DataAccess {
   private static SqlConnection getConnection() {
      SqlConnection cn = new SqlConnection(“Data Source=(local); Initial Catalog=AdventureWorks; Integrated Security=SSPI“);
      cn.Open();
      return cn;
   }

   internal static void QueryReader(SqlCommand cm, DataReaderCallback callback) {
      SqlConnection cn = getConnection();
      SqlDataReader dr = null;
      try {
         cm.Connection = cn;
         dr = cm.ExecuteReader(CommandBehavior.CloseConnection);
         return ( null != callback ) ? callback(dr) : null;
      }
      finally {
         if ( null != dr ) ((IDisposable)dr).Dispose();
         if ( null != cn ) cn.Dispose();
         cm.Connection = null;
      }
   }
}

internal sealed class Worker {
   internal void DoWork() {
      SqlCommand cm = new SqlCommand(“SELECT Name FROM Production.Product WHERE Name LIKE '[A-C]%' ORDER BY Name“);
      ArrayList list = DataAccess.QueryReader(cm, new DataReaderCallback(this.callbackFn)) as ArrayList;
      for ( int i = 0; i < list.Count; i++ )
         Console.WriteLine(“{0}: {1}“, i, list[i]);
   }

   internal object callbackFn(IDataReader dr) {
      ArrayList list = new ArrayList();
      while ( dr.Read() )
         list.Add(dr.GetString(0));
      return list;
   }
}

Note, the major change here is that there is no build up of shared state and once the QueryReader returns, the caller has a fully disconnected set of data with which to work.  This is all well and good, and in fact, I very much like this approach.  It gives the caller the ability to write lean, tight code and still interact with the data reader in the fashion the caller wants.  Very cool.  However, as you may quickly realize, there's no real constraint set on the type of object returned from the callback function - it's an object.  Therefore, the caller has to know the type of object to be returned (in this case, an ArrayList).

With the looming advent of .NET 2.0, there's a very slick way to ensure type compatibility and strongly typed and verified method signatures: generics.  Let's explore a technique that mimics the example above, but utilizes generics to accomplish the goal.

.NET 2.0 Generic Example:

internal delegate T DataReaderCallback<T>(IDataReader dr);

internal static class DataAccess {
   // same getConnection() method as above

   internal static T QueryReader<T>(SqlCommand cm, DataReaderCallback<T> callback) {
      SqlConnection cn = getConnection();
      SqlDataReader dr = null;
      try {
         cm.Connection = cn;
         dr = cm.ExecuteReader(CommandBehavior.CloseConnection);
         return ( null != callback ) ? callback(dr) : default(T);
      }
      finally {
         if ( null != dr ) dr.Dispose();
         if ( null != cn ) cn.Dispose();
         cm.Connection = null;
      }
   }
}

internal sealed class Worker {
   internal void DoWork() {
      SqlCommand cm = new SqlCommand(“SELECT Name FROM Production.Product WHERE Name LIKE '[A-C]%' ORDER BY Name“);
      ArrayList list = DataAccess.QueryReader(cm, new DataReaderCallback<ArrayList>(this.callbackFn));
      for ( int i = 0; i < list.Count; i++ )
         Console.WriteLine(“{0}: {1}“, i, list[i]);
   }

   internal ArrayList callbackFn(IDataReader dr) {
      ArrayList list = new ArrayList();
      while ( dr.Read() )
         list.Add(dr.GetString(0));
      return list;
   }
}

Notice that pretty much all of the code is identical except for a few things:

  • the delegate signature is generic; you must specify the type return type upon creation.
  • the QueryReader signature is also generic, but the type only needs to be specified on the DataReaderCallback parameter instance.
  • we can longer return null from the QueryReader method, but instead return default(T), because the method is generic.
  • you no longer need to cast a DataReader to IDisposable to call Dispose. Ok, that was unrelated, but I like the enhancement.

All in all, I really like this approach.  It makes consuming data readers much more specific, isolated, wrapped, and contained, and empowers the data access layer in ensuring that its resources are properly cleared.

Happy coding!

Thursday, October 20, 2005 6:00:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Thursday, October 13, 2005

Today's Utah .NET User Group Meeting was quite a success.  We had about 50 people show up for Fabio Cavalcante's presentation on LINQ, DLINQ, and XLINQ and Robert Love's presentation on Extending ASP.NET.  This month's meeting was sponsored by The MindCenter, a local training and education organization.  We had some great giveaways too, including a 25 CAL copy of Windows Server 2003!

What started out to be an extremely stressful and jam-packed day ended on a very long-overdue high note.

Thanks guys for such a good job!

Thursday, October 13, 2005 5:02:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

This may be old news, but I just discovered it and thought it was hilarious.  On browsing Sysinternals.com, I stumbled upon the BlueScreen Screen Saver.  This would be an excellent screen saver to install on your boss's computer! :)  It is most convincing!

Thursday, October 13, 2005 3:37:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

In light of my recent (and quite repetitive) fiascos with respect to domain controllers, I've decided to make a change.  And despite of the fact that I absolutely love what an AD domain offers, I've decided to forego the whole notion of having a domain.  The primary motivation behind the switch is the fact that with a domain things are so closely interrelated and coupled and a loss of the DC has more impact across all other computers on the network.

If I were to set up the domain, then I'd have to end up having to reset the entire domain in two weeks when my drive crashes again (ok, slight exaggeration...three weeks).  Technically, of course, I could simply restore my System State backup, but frankly I just can't bring myself to trust a backup made from a drive replete with bad sectors.  It will be a lot easier to simply reset a single machine rather than a domain and manage all of the domain memberships.

Ok, now to the real reason I'm writing this post in the first place:

Another change that I'm implementing is that Exchange will not be part of reconfiguration.  Don't get me wrong, Exchange is awesome and I love it, but it's too difficult for me to rebuild a domain and restore all my mailboxes and Exchange settings, and get everything else up and running - I typically lose a day and lots of hair.  OWA has been an absolute treat.  I'm looking at replacing my email server, therefore, with something smaller, easier to deploy, and Microsoft-technologies based (.NET-based is a plus).

To help assuage the pain of not having my DC and Exchange running, I've installed a trial version of EmailArchitect Email Server 5.9 on my laptop and routed all SMTP traffic to it in the interim until my server is back up and online.  I'm really digging it.  It has a nice web-based front end as well as full SMTP/POP3/IMAP4 support - Outlook setup was a breeze.

Does anyone have any recommendations for an Email Server they prefer to run?  I'm looking for a cost effective, multi-domain, multi-mailbox-supporting email system that is stable, works well, and all that jazz.  Suggestions are very welcome!

Thursday, October 13, 2005 1:54:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Wednesday, October 12, 2005

Can someone please tell me (teach me) how to not lose a domain controller hard drive?  I am practically in tears in frustration because yet again my domain controller disk went bad, sour, kaput.  I rebooted after running into a few issues (namely not being able to run Regedit.exe (or RegEdt32.exe) and attempting to perform a System State backup but erroring out because it couldn't write to the event log) and was greeting with the all too pleasant to behold phrase:

Non-System disk or disk error
replace and strike any key when ready

What is this? the fourth time this year? fifth? I've lost count.  I've used different disks, different computers, different outlets...I ALWAYS get bad sectors and/or corrupted data and/or crashed drives on my domain controller computers.  I am fed up with it.  I am yet again faced with rebuilding my domain - mostly because I don't have (anymore) any trust in my System State backups - who knows? maybe I backed up corrupted data.  I sure don't want to restore it.  On the bright side, my domain controller is just one computer and there is only one other member server...but lots'o'workstations.

I must be doing something wrong.

I should've been a farmer.

Wednesday, October 12, 2005 11:07:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  |  Trackback