Tuesday, August 05, 2008

I finally gave in (for better or for worse) and signed up on facebook.com this past weekend.  I've always had an aversion to such social networking sites for one reason or another.  In fact, facebook.com and myspace.com have long been blocked on my firewall at home until just recently.  I may go back to block myspace.com, however; pretty much everything I've seen on it has been annoying, sleasy, trashy, and not worth my time - basically stuff I didn't want on my computer in the first place.

Now I don't know all the specifics about facebook.com, but I've had a great time on it in the past few days reestablishing connections with friends from my youth in New Mexico.  I've made contact with some people that I've not been able to find until now, which is very exciting.

I realize that I'm a late bloomer of a sort with regards to social networking, but such is the way of things I suppose.  Among other things, I've simply not had time for it.  I still don't have time for it, but I may make some for it, we'll see how it goes.

If you on facebook.com and you know me, drop me a line!

Tuesday, August 05, 2008 12:18:27 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Sunday, July 13, 2008

I've recently been thinking about database authentication in an ASP.NET application.  While this concept is most definitely unoriginal - a great many web applications must perform data access of some sort - it is easy to get confused by the many variations of data access.

To summarize in brief, there are two primary methods for authenticating to a database: Windows Authentication (also called Integrated Security), and Sql Authentication.  All SQL Servers support Windows Authentication and it's the natural choice.  Enabling Sql Authentication, on the other hand, requires that the server be configured to support it; this can be accomplished either during installation or after the fact.  What's more, not all organizations will support Sql Authentication.

Despite the aforementioned, perhaps he simplest technique to authenticate a user in the database is to use Sql Authentication.  It requires that 1) a login be created in the SQL Server and mapped to the database and 2) the connection string contain the appropriate User ID and Password parameters.  It is the simplest also because the database can be located locally or remotely and the authentication will succeed.

As soon as you use Windows Authentication in a web application the level of difficulty raises, even if only slightly.  To properly implement Windows Authentication in a web application, the identity of the process must be determined.  For purposes of this article, as it is not directly pertinent and can be quite large in scope, I will refrain from exploring the topic of ASP.NET process identity.  Suffice it to say that on a Windows Server 2003/2008 the default identity for an IIS application pool is NT AUTHORITY\NETWORK SERVICE.  On other platforms this identity will vary.

ASP.NET Application - Anonymous or Integrated Windows Authentication, No Impersonation

If the requesting user is not being impersonated, IIS will access resources using the identity of the application pool (e.g., NT AUTHORITY\NETWORK SERVICE).

ASP.NET Application - Anonymous Authentication, Impersonation Enabled

If your web application is using anonymous access and the user is being impersonated a couple of things can happen.  First, if, in your web.config file, you specify <identity impersonate="true" />, IIS will impersonate the anonymous user specified for your web site (e.g., IUSR_MACHINENAME, IUSR, etc.).  A user may be specified in the web.config file as well via <identity impersonate="true" userName="DOMAIN\User" password="xxx" />.  In this case, the web server will impersonate the user on the server.  NOTE: This user needs, at a minimum, Write access to the \Temporary ASP.NET Files folder.

ASP.NET Application - Integrated Windows Authentication, Impersonation Enabled

This is functionally similar to the previous item except if you use the simple form of the identity element (i.e., <identity impersonate="true" />), the user account that is performing the request is impersonated.  This is quite helpful especially if you need to control access to a server-side resource by ACL.

When connecting to a SQL Server database that is local to the web server, that is, it is physically on the same machine, you can grant access to the identity under which the web application is running (ASPNET, NT AUTHORITY\NETWORK SERVICE, IUSR_MACHINENAME, or the impersonated user for instance).

When the database is physically remote, however, care must be taken to properly flow user credentials to remote server.  That is, in fact, something of a misnomer.  Credentials aren't actually flowing to the remote server, but rather an authentication token.  This token is generated on the computer where the user identity is authenticated.

A user token generated on the web server will be able to flow to the remote machine without any extra work.  This token is created in the following scenarios:

  1. Using Basic authentication - the user is actually logged-in on the server.
  2. Using no impersonation - the website is locally authenticated as ASPNET or NT AUTHORITY\NETWORK SERVICE.
  3. The impersonation identity is manually set in the web.config's <identity /> element.

NTLM will permit the token a 'single hop' to the remote server.  Provided the identity in question has access to the database, the connection will be successfully established, and the requested information returned.

If, on the other hand, the web server is impersonating the requesting user, the solution is not so cut-and-dry.  The user's token is created on the client computer and makes a 'single hop' to the web server.  When making a request to a remote server a 'double hop' must be performed.  NTLM will prohibit the token from being passed to the server and you will encounter an error resembling "Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'".

This error may be confusing and disheartening to a developer who has not seen it before.  This is often the result of having tested the website on a development machine (which is usually local) - "It works on my machine."  Well, it works because the token is created locally and only needs a 'single hop' to get to the database server.

There are a few solutions that may help to address the oft-times confusing and frustrating 'double hop' conundrum.

  1. Setup Constrained Delegation
    Less far-sweeping than full delegation, Kerberos constrained delegation allows tokens to flow against a limited set of services.  This option is only available on Windows Server 2003+.  To properly implement constrained delegation, you must set up a Service Principal Name (SPN), identifying the service and the machine trusted for delegating the tokens.  While simple in principle, I've found this solution to be tempermental and very sensitive.  You may seemingly have everything set up correctly and still not get it to work.
  2. Fall back (a.k.a., revert) to the base IIS process's identity
    Having fought the 'double hop' issue more times than I care to admit, and for more hours than are healthy, this idea came to me last week while discussing the 'problem' with a friend and associate at Microsoft.  While this idea is not revolutionary nor original, it is definitely useful.  This technique allows your website to retain its ability to identify the calling user while deferring to the process's identity (e.g., NT AUTHORITY\NETWORK SERVICE) to access remote databases.

To make this (#2) work we need the ability to 'undo' the impersonation that ASP.NET performs and then reimpersonate when we're finished.  Unfortunately, this functionality is not native to the .NET Framework as far as I'm aware - it is, however, accessible via the RevertToSelf() Window API function.  In order to encapsulate the logic of reverting to the base process's identity and restoring impersonation, I've created a simple disposable class which is presented below:

/// <summary>
/// Represents a disposable class that, for the lifetime of the object, runs using the
/// underlying identity of the process.  This class is useful within an ASP.NET application
/// that is impersonating the caller, but needs to access network or directory resources
/// that would otherwise be prohibited without setting up constrained delegation in Active
/// Directory.
/// </summary>

public sealed class RevertImpersonator : IDisposable {
  public RevertImpersonator() {
     // acquire the identity of the current user (the user being impersonated)
     _userIdentity = WindowsIdentity.GetCurrent();

     // revert to the underlying process' identity
     // for ASP.NET applications that impersonate, this will be the identity of the IIS process
     // (e.g., the identity of the application pool which, by default, is NETWORK SERVICE).
     // NOTE: the NETWORK SERVICE account will access network resources as the MACHINE$ account, local resources as NT AUTHORITY\NETWORK SERVICE.

     RevertedIdentity = ( 0 != RevertToSelf() );
  }

  private readonly WindowsIdentity _userIdentity;

  
  [DllImport("advapi32.dll")]
  private static extern int RevertToSelf();

  ~RevertImpersonator() {
     restore();
  }


  /// <summary>
  /// Returns whether the user's identity was successfully reverted on initialization.
  /// </summary>
  public bool RevertedIdentity { get; private set; }


  public void Dispose() {
     GC.SuppressFinalize(this);
     restore();
  }


  private void restore() {
     // re-impersonate the user during cleanup
     if ( RevertedIdentity )
        _userIdentity.Impersonate();
  }
}

Effectively, this class allows you to encapsulate database calls thus:

using ( new RevertImpersonator() ) {
   // perform data access here
}

An important note is warranted.  As previously mentioned, using this class will revert the identity on the thread to the process's identity.  You can set your IIS Application Pool to use a domain account rather than the default NT AUTHORITY\NETWORK SERVICE.  Doing so will require that the domain user have access to the database in question.  If, however, you decide to use the default, you must be aware of a few items.  First, a local database will be accessed with the NT AUTHORITY\NETWORK SERVICE account as expected.  A remote database will be accessed with the MACHINE$ account - this is how the NT AUTHORITY\NETWORK SERVICE account is authenticated remotely.

Sunday, July 13, 2008 9:02:49 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, July 01, 2008

While we use TFS (Team Foundation Server) for all of our current development projects, we do maintain a SourceGear Vault server for most if not all of our legacy projects.  I've had nothing but a great experience with Vault over the past 3 or 4 years that I've used it.

Recently, however, I moved the server and SQL databases over to a new Windows Server 2008 x64 system with SQL Server 2005 SP2.  Everything seems to transition smoothly.  This particular SCC system does not get a lot of activity due to the nature of what data it maintains, but about a month after the upgrade one user suddenly couldn't access the system with authentication errors.  Likewise, I was unable to access several pages on the administrative Vault website.  The error reported was 'Object reference not set to an instance of an object.'

The next thing I did was open the Sql Profiler and watch the activity as it pertained to the Vault database (sgvault).  I noticed that it was attempting to execute a stored procedure by the name of spgettreestructure with the repository id and the transaction id.  Attempting to run the statement directly, I was presented with a SQL Server error:

Msg 0, Level 11, State 0, Line 0

A severe error occurred on the current command.  The results, if any, should be discarded.

Msg 0, Level 20, State 0, Line 0

A severe error occurred on the current command.  The results, if any, should be discarded.

This had me a little worried, but I opened up a ticket with SourceGear support.  After a few emails back and forth, a GoToMeeting, I was referred to Pinalkumar's blog page.  It turns out this was a bug in SQL Server that was fixed in the Cumulative update package 6 for SQL Server 2005 SP2.  Applying the patch (http://support.microsoft.com/default.aspx/kb/946608/LN/) fixed the issue :)

Tuesday, July 01, 2008 9:53:43 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, June 27, 2008

I was putting together a small application today that presents data in a TextBox.  Now while this isn't too uncommon :), the text gets added to the TextBox a line at a time via the TextBox's .AppendText() method.  I needed to do this repeatedly and in succession.  As it turns out, an unwelcome side effect of AppendText is that it scrolls the TextBox as the text is appended.  This resulted in the TextBox getting cleared and then 'wiping' down as the text was added.  Not only was this visually unappealing, but I wanted the insertion point to stay at the top of the contents.

To my knowledge, which is quite flawed and limited, there's not a built-in mechanism in .NET that provides this functionality.  Sure, I could override the TextBox control and manage the WndProc method and/or use SetStyle to make the control user drawn.  But I didn't want to do that.  Well, accomplishing this is quite easy.

There are a few ways we can do it:

  1. Use the LockWindowUpdate() API function.
  2. Use SendMessage() API function with the WM_SETREDRAW message.

LockWindowUpdate() is slick and arguably easier to use, but it's intended purpose isn't to suppress the redrawing of controls in this manner.  Plus, you can only have one window locked at a time.  So I threw that one out.

I wrapped the logic to lock and unlock the redrawing of the control in an IDisposable object so I wouldn't have to worry about remembering to clean up after myself.  My class is as follows:

public class LockVisualUpdate : IDisposable {
   public LockVisualUpdate(IWin32Window control) {
      _hWnd = control.Handle;
      SendMessage(control.Handle, WM_SETREDRAW, 0, 0);
   }


   private readonly IntPtr _hWnd;
   private const int WM_SETREDRAW = 0x000B;


   [DllImport("user32.dll")]
   private static extern bool SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);


   [DllImport("user32.dll")]
   private static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);


   public void Dispose() {
      SendMessage(_hWnd, WM_SETREDRAW, 1, 0);
      InvalidateRect(_hWnd, IntPtr.Zero, false);
   }
}

Really simple to use.  To consume it, I simply have to do the following:

using ( new LockVisualUpdate(textBox1) ) {
   textBox1.AppendText(...);
   // ...repeated as often as necessary to populate the control

   // when finished, position the insertion point to the top of the control
   textBox1.SelectionStart = 0;
   textBox1.ScrollToCaret();
}

.NET | C#
Friday, June 27, 2008 1:28:14 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, June 23, 2008

A few days ago I realized that my blog wasn't working properly.  This came with more than a bit of frustration because I had upgraded ito to DasBlog on May 25th (almost 1 month ago exactly!).  What clued me off was the fact that I wasn't getting any comments to prior posts.  While the vast majority of comments came from my download control and my rating control (neither of which has yet been ported over), I would get the occasional comment through the blog directly.  But since the upgrade nothing.  Well, that's not true, I got two comments on my transition post but I didn't get either one because I didn't set up my mail setting correctly.  That has since been fixed.

As it turns out, the code that I originally used to port the blog site over was flawed, but I didn't realize it until it was too late.  Pretty much all of my google links where my blog was the top page or in the top few have all but disappeared :(.  I have, however, updated the code (which I present below) in the event there is any other poor soul out there needing to migrate from .Text to DasBlog.

Comments should now work on the blog.

I hope this works better for everyone moving forward:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using newtelligence.DasBlog.Runtime;

namespace ConvDotTextToDasBlog {
   internal class Program {
      private class EntryData {
         public EntryData(string id, string title) {
            Id = id;
            Title = title;
         }

         public readonly string Id, Title;
      }

      private static readonly Dictionary<int, EntryData> _postDict = new Dictionary<int, EntryData>();

 
     private static void Main() {
         string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "output");
         Directory.CreateDirectory(path);

         IBlogDataService dataService = BlogDataServiceFactory.GetService(path, null);
         string connStr = @"Initial Catalog=Blog; Data Source=(local)\SQLEXPRESS; Integrated Security=True";

         using ( SqlConnection conn = new SqlConnection(connStr) ) {
            conn.Open();
            using ( SqlCommand cmPosts = new SqlCommand("SELECT COUNT(ID) FROM blog_Content WHERE PostType=1; SELECT * FROM blog_Content WHERE PostType=1", conn) )
            using ( SqlDataReader drPosts = cmPosts.ExecuteReader() ) {
               drPosts.Read();
               int totalCount = drPosts.GetInt32(0);
               drPosts.NextResult();

               int currIndex = 0;
               while ( drPosts.Read() ) {
                  int postId = drPosts.GetInt32(0);
                  string postTitle = drPosts.IsDBNull(1) ? string.Empty : drPosts.GetString(1);
                  DateTime dtCreated = drPosts.GetDateTime(2);
                  DateTime dtModified = drPosts.GetDateTime(10);
                  string postText = drPosts.GetString(12);
                  string postAuthor = drPosts.GetString(5);

                  // catalog the id, assigning it a guid
                  string newPostId = Guid.NewGuid().ToString().ToLowerInvariant();
                  string newPostTitle = ( postTitle.Length > 0 ? postTitle : postText.Substring(0, Math.Min(20, postText.Length)) );
                  _postDict.Add(postId, new EntryData(newPostId, newPostTitle));

                  Console.WriteLine("Processing Post #{0} ({1} of {2})", postId, ++currIndex, totalCount);
                  Entry entry = new Entry();
                  entry.CreatedUtc = dtCreated;
                  entry.ModifiedUtc = dtModified;
                  entry.Title = newPostTitle;
                  entry.Content = postText;
                  entry.EntryId = newPostId;
                  entry.Categories = getPostCategories(postId, connStr);
                  entry.Author = postAuthor;
                  dataService.SaveEntry(entry);
               }
            }

            using ( SqlCommand cmComments = new SqlCommand("SELECT COUNT(ID) FROM blog_Content WHERE PostType=3; SELECT * FROM blog_Content WHERE PostType=3", conn) )
            using ( SqlDataReader drComments = cmComments.ExecuteReader() ) {
               drComments.Read();
               int totalCount = drComments.GetInt32(0);
               drComments.NextResult();

               int currIndex = 0;
               while ( drComments.Read() ) {
                  int commentId = drComments.GetInt32(0);
                  int refPostId = drComments.GetInt32(13);
                  DateTime dtCreated = drComments.GetDateTime(2);
                  string commentAuthorName = drComments.IsDBNull(5) ? string.Empty : drComments.GetString(5);
                  string commentAuthorIp = drComments.IsDBNull(7) ? string.Empty : drComments.GetString(7);
                  string commentAuthorUrl = drComments.IsDBNull(11) ? string.Empty : drComments.GetString(11);
                  string commentText = drComments.GetString(12);

                  EntryData refEntry;
                  if ( !_postDict.TryGetValue(refPostId, out refEntry) )
                     Console.WriteLine("Error processing comment #{0} ({1} of {2}); post {3} was not resolved.", commentId, ++currIndex, totalCount, refPostId);
                  else {
                     Console.WriteLine("Processing Comment #{0} ({1} of {2})", commentId, ++currIndex, totalCount);
                     Comment comment = new Comment();
                     comment.EntryId = Guid.NewGuid().ToString().ToLowerInvariant();
                     comment.CreatedUtc = dtCreated;
                     comment.ModifiedUtc = dtCreated;
                     comment.TargetEntryId = refEntry.Id;
                     comment.TargetTitle = refEntry.Title;
                     comment.Author = commentAuthorName;
                     comment.AuthorHomepage = commentAuthorUrl;
                     comment.AuthorIPAddress = commentAuthorIp;
                     comment.Content = commentText;
                     dataService.AddComment(comment);
                  }
               }
            }
         }
      }

      private static string getPostCategories(int postId, string connStr) {
         const string sql = "SELECT cat.Title FROM blog_Links AS links INNER JOIN blog_LinkCategories AS cat ON links.CategoryID = cat.CategoryID WHERE links.PostID = @PostID";

         List<string> categories = new List<string>();
         using ( SqlConnection cn = new SqlConnection(connStr) )
         using ( SqlCommand cm = new SqlCommand(sql, cn) ) {
            cn.Open();
            cm.Parameters.Add("@PostID", SqlDbType.Int).Value = postId;
            using ( SqlDataReader dr = cm.ExecuteReader(CommandBehavior.CloseConnection) ) {
               while ( dr.Read() ) {
                  string category = dr.GetString(0);
                  categories.Add(category);
               }
            }
         }
         return string.Join(";", categories.ToArray());
      }
   }
}

.NET | DasBlog | Journal
Monday, June 23, 2008 9:46:47 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, June 14, 2008

I was experiencing a most perplexing issue yesterday and the day prior that I *finally* resolved late last night.  In essence, I have a Windows Service that I wrote that hosts an adapted Cassini server which, in turn, hosts an ASP.NET website product running on the client's computer.  The issue was the classic case of 'it works on my machine'.

I could install my service, configure it, and run an ASP.NET website on my computer.  It was a beautiful and exciting thing to witness.  When I went to execute it on another computer, however, the service would start up perfectly but when I made the first request to the website the service would crash (immediately stop) and log an error to the system's Application event log.  The error was a FileNotFoundException.

After troubleshooting the obvious stuff for a bit (folder permissions (the Service runs as NETWORK SERVICE), executables in the proper places, comparing systems, etc), I resorted to more drastic means. :)  Actually, this is technique I frequently employ when troubleshooting issues loading assemblies.

You can troubleshoot Fusion binding issues by simply setting up some registry keys on the machine in question.  Fusion, in short, is the name given by Microsoft for their assembly loading and binding technology in .NET.  Because I don't have the .NET SDK or Visual Studio on the target machine in question, I had to set it up manually.  I added the following registry keys:

HKLM\SOFTWARE\Microsoft\Fusion\LogPath (string) with value pointing to my desired output folder (C:\(TEMP)\FusionLog).
HKLM\SOFTWARE\Microsoft\Fusion\LogFailures (DWORD) with a value of 1.

I restarted the service and made another web request to witness the failure for the umpteenth time.  Sure enough I got some output in my FusionLog folder:

*** Assembly Binder Log Entry  (6/13/2008 @ 9:18:40 PM) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
Running under executable  C:\Program Files\Devstone\WebHostSvc\WebHostSvc.exe
--- A detailed error log follows.

=== Pre-bind state information ===
LOG: User = NT AUTHORITY\NETWORK SERVICE
LOG: DisplayName = WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx
 (Fully-specified)
LOG: Appbase = file:///C:/Documents and Settings/All Users/Application Data/Devstone/Web/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = f9861834
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\Documents and Settings\All Users\Application Data\Devstone\Web\web.config
LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
LOG: Post-policy reference: WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx
LOG: GAC Lookup was unsuccessful.
ERR: No codebases found to download from.
ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).

The thing that immeidately stood out was the fact that the Initial PrivatePath, Dynamic Base, and Cache Base were all NULL.  On my development machine these all referenced the /Web/bin folder.  Why wasn't it looking in the \bin folder?

At this point I began debugging the service, actually stepping into the .NET Framework code itself to see if I could figure out the issue.  Delving into the HttpRuntime.cs file I could see that the \bin folder gets appended to the app domain's private paths in the InitFusion() method, just as I had expected.  However, somehow the code wasn't getting that far.  Unfortunately, I couldn't step through some of the code leading up to this point so it was difficult to see exactly why it wasn't getting here.  Actually, in retrospect, I should have looked at the code a little longer - the answer was right in front of me.

I noticed from the fusion log that it was performing a GAC Lookup for the assembly.  Now the assembly isn't GAC-registered on my development machine but I was at my wit's end with the issue.  So I GAC'd it:

gacutil /i WebHostSvc.exe

Immediately upon the next request I got the error I was looking for in the browser:

The current identity (NT AUTHORITY\NETWORK SERVICE) does not have write access to 'C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files'.

Aha!  The one thing I didn't check but should have before.  Granting NETWORK SERVICE rights to that folder fixed the problem straight away.

I then removed it from the GAC and removed the Temporary ASP.NET Files that were created, restarted the service, and ran it again to verify that it was running properly.

At this point I went back to the source code and sure enough I saw the line I was looking for.  When initializing, it invokes a method called SetUpCodegenDirectory (which maps to the Temporary ASP.NET Files folder).  This method ensures that the caller has write access to it.

So the moral of the story: make sure that the user account that you're using for ASP.NET has write access to the Temporary ASP.NET Files folder and you'll be a much happier developer.

.NET | Web
Saturday, June 14, 2008 9:42:17 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Thursday, June 12, 2008

I have some pretty old virtual machines that I use extensively for development and testing.  Recently I upgraded to Virtual PC 2007 SP1 and wanted to upgrade their Virtual Machine Additions, but when I attempted to uninstall the older software I was greeted with the following dialog box:

Well, I immediately clicked OK, knowing full well that a source of "1" was invalid just to see what would happen.  Sure enough, it would not uninstall.  Browsing for the source prompted for the "Virtual Machine Additions.msi" file which I don't have - at least not the version it's looking for.

I had no recollection as to which version of Virtual PC was used to create this particular .vhd file, but I knew it wasn't recent.  To fix it, I had to locate the correct version of the VMAdditions.iso, mount it, and then uninstall.  This is how you might go about fixing it:

  1. Locate Virtual PC 2004 SP1 and download it (I didn't have it anymore).
  2. It's a .zip file, so extract it to a folder (i.e. c:\vpc2004).
  3. Don't install it, we don't want to mess up our existing installation of 2007+.  Instead, perform an administrative install of the .msi file which will prompt you for a network location (a local path is fine) to extract the contents (I chose c:\vpc2004\extract).
    msiexec /a "Microsoft Virtual PC 2004 MSDN.msi"
  4. Then you can navigate to the appropriate location and retrieve the VMAdditions.iso file.
    C:\vpc2004\extract\Program Files\Microsoft Virtual PC\Virtual Machine Additions
  5. Then, within your VPC, capture the VMAdditions.iso file, cancelling any installation process that may start via Auto-Play.
  6. Uninstall the previous software.

Not too painful, but definitely an annoyance.  Now, I've since archived off the .iso and .vfd files from the 2004 SP1, 2007, and 2007 SP1 versions of Virtual PC just in case I have to do this again in the future.

Thursday, June 12, 2008 2:06:31 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

If you attempt to browse to a website on your local machine identified by a host header that differs from the NetBIOS name of the computer or if you reference your server via its fully qualified domain name (i.e., server.domain.local) you may be presented with a login dialog.  This login dialog will not accept your credentials and after three attempts you'll be presented with an error message: HTTP 401.1 - Unauthorized: Logon Failed.  This can be quite disconcerting and confusing.

I've lost many a sleepless hour trying to troubleshoot it in the past.

Well, it happened again today and for the life of me I couldn't find a reference to how to fix it on my blog.  I was certain I had blogged about it before.

The fix is to add a registry setting to bypass a security check introduced by Windows XP SP2 and Windows Server 2003 SP1.

  1. Open your registry editor (regedit.exe)
  2. Locate HKLM\SYSTEM\CurrentControlSet\Control\Lsa.
  3. Create a new DWORD value named DisableLoopbackCheck and assign it a value of 1.

Though the KB article where I got this information says you need to reboot, I've not needed to.

Thursday, June 12, 2008 9:18:22 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, June 09, 2008

Almost immediately following yesterday's post I got to thinking about supporting static classes / methods.  I see static classes all the time where the class represents a set of related-but-operationally-distinct methods, often utilities.  The post yesterday really had two aspects about it: 1) the retrieval / extraction and the hosting of an assembly embedded as a resource within another assembly and 2) the creation of a proxy type by wrapping the logic surrounding using Reflection to dig into the guts of the referenced object in a base class.

The ante is raised (only slightly) when dealing with static objects.  Static objects have no instance methods and therefore have no inheritance support and no constructors (the static .ctor is really a type initializer and it's quite the same thing).  Our simple example yesterday defines an abstract base class (ProxyClassBase) that encapsulates the logic for creating the proxy instance and invoking the methods within it.

If we were to mimic the structure of the reference object (the object from the extracted assembly) in our proxy class we would be unable to inherit ProxyClassBase because our proxy would be static.  Allow me to flesh out an example illustrating this:

Example 01: Static Proxy

For this to work, I'd have to create a new class.  Let's name it StaticProxyClass.  This class would, in large measure, be quite similar to the ProxyClassBase base type we defined yesterday with a few distinctions:  1) we wouldn't need an Instance (all methods are static) and 2) we can't have a protected .ctor (our proxy would be static and would be unable to inherit the class).

Our type might resemble the following:

namespace HostAsm {

   internal class StaticProxyClass {
      internal StaticProxyClass(string assemblyName, string typeName) {
         _type = AssemblyLoader.GetType(assemblyName, typeName);
      }

      private readonly Type _type;

      internal void InvokePublicVoidMethod(string methodName, object[] parameters) {
         Type[] types = getTypes(parameters);
         MethodInfo method = _type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public, null, CallingConventions.Standard, types, null);
         method.Invoke(null, parameters);
      }

      private static Type[] getTypes(object[] parameters) {
         Type[] result;
         if ( null == parameters || 0 == parameters.Length )
            result = Type.EmptyTypes;
         else {
            result = new Type[parameters.Length];
            for ( int i = 0; i < parameters.Length; i++ ) {
               result[i] = parameters[i].GetType();
            }
         }
         return result;
      }
   }
  
}

Note that I've included a method (getTypes) that I didn't include in yesterday's example but may well belong in the ProxyClassBase type as well.  This method will assist in resolving overloaded methods (methods with the same name but with differing parameters).

Our static proxy class would effectively wrap an instance of this type and delegate calls to the reference object through it.

namespace HostAsm {

   internal static class CustomStaticClass {
      static CustomStaticClass() {
         _proxy = new StaticProxyClass("EmbeddedAsm.dll", "EmbeddedAsm.CustomStaticClass");
      }

      private static readonly StaticProxyClass _proxy;

      internal static void StaticMethod(string data) {
         _proxy.InvokePublicVoidMethod("StaticMethod", new object[] { data });
      }
   }

}

This is all well and good and it works perfectly well.  There's something missing, however, and a few things bug me about it.

First of all, it bugs me most that we'd end up having to duplicate (in large part) the functionality in one class (ProxyClassBase) in another (StaticProxyClass).  Secondly, we're omitting one major part of the discussion: objects that have both instance methods and static methods.  To me, this is the deal breaker.

To support this and to leverage our ProxyClassBase type even for static methods, I'd do away with the StaticProxyClass and add the methods therein to the ProxyClassBase.  This will necessitate that our proxy type for static types be non-static, but we can pseudo-simulate that by making its .ctor private.  For these, we will have to encapsulate the actual proxy within our proxy type.  In fact, we'd have to take that one more level by creating delegate methods on our proxy class that direct the calls to the base type.  I'd rather do this than muck with the scope of the base class's methods to keep my intentions pure with the base class.

Example 02: Enhancing the ProxyClassBase

The ProxyClassBase will get the functionality we just added to StaticProxyClass as well as a flag indicating whether the wrapped type is a static type.

using System;
using System.Reflection;

namespace HostAsm {

   internal abstract class ProxyClassBase {
      protected ProxyClassBase(string assemblyName, string typeName, bool isStatic) {
         ObjType = AssemblyLoader.GetType(assemblyName, typeName);
         Instance = isStatic ? null : Activator.CreateInstance(ObjType);
      }

      protected Type ObjType { get; private set; }
      protected object Instance { get; private set; }
      protected bool IsStatic { get; private set; }

      protected T InvokePublicMethod<T>(string methodName) {
         MethodInfo method = ObjType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Standard, Type.EmptyTypes, null);
         return ( T )method.Invoke(Instance, null);
      }

      protected void InvokePublicStaticVoidMethod(string methodName, object[] parameters) {
         Type[] types = getTypes(parameters);
         MethodInfo method = ObjType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, types, null);
         method.Invoke(null, parameters);
      }

      private Type[] getTypes(object[] parameters) {
         Type[] result;
         if ( null == parameters || 0 == parameters.Length )
            result = Type.EmptyTypes;
         else {
            result = new Type[parameters.Length];
            for ( int i = 0; i < parameters.Length; i++ ) {
               result[i] = parameters[i].GetType();
            }
         }
         return result;
      }
   }

}

Then, to finish the puzzle, our proxy class that wraps the static type gets a face lift:

using System;

namespace HostAsm {

   internal class CustomStaticClass : ProxyClassBase {
      private CustomStaticClass()
         : base("EmbeddedAsm.dll", "EmbeddedAsm.CustomStaticClass", true) {
      }

      private static readonly CustomClass2 _proxy = new CustomClass2();

      internal static void StaticMethod(string data) {
         _proxy.invokePublicStaticVoidMethod("StaticMethod", new object[] { data });
      }

      private void invokePublicStaticVoidMethod(string methodName, object[] parameters) {
         base.InvokePublicStaticVoidMethod(methodName, parameters);
      }
   }

}

I've not given this approach a great deal of scrutiny.  At first glance I can see it being somewhat fragile.  If the base class changes you have to proliferate changes to the inheriting types.  Then again, I don't see this base class like this changing much if at all except for new additions.

Monday, June 09, 2008 8:20:56 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback