Monday, September 01, 2008

I ran across some interesting (read "unexpected") behavior the other day when writing a .NET WinForms control.  This particular control has some logic that executes if and only if the host OS has visual themes enabled.

In order to check for the existence of enabled themes, I call a method called 'IsVisualStylesEnabled()' which is defined as below:

public static bool IsVisualStylesEnabled() {
   bool isEnabled = false;
   try {
      OperatingSystem os = Environment.OSVersion;
      // decide whether the OS even supports visual styles
      bool isSupported = ( os.Platform == PlatformID.Win32NT )
                         && ( ( ( os.Version.Major == 5 ) && ( os.Version.Minor >= 1 ) )
                              || ( os.Version.Major > 5 ) );
      if ( isSupported ) {
         int majorDllVersion = getComCtlMajorVersion();
         isEnabled = ( majorDllVersion > 5 ) && IsThemeActive() && IsAppThemed();
      }
   }
   catch ( Exception ) {
      // do nothing explicitly
   }
   return isEnabled;
}


private static int getComCtlMajorVersion() {
   DLLVERSIONINFO dllVersion = new DLLVERSIONINFO { cbSize = Marshal.SizeOf(typeof( DLLVERSIONINFO )) };
   DllGetVersion(ref dllVersion);
   return dllVersion.dwMajorVersion;
}

 

[StructLayout(LayoutKind.Sequential)]
private struct DLLVERSIONINFO {
   public int cbSize;
   public int dwMajorVersion;
   public int dwMinorVersion;
   public int dwBuildNumber;
   public int dwPlatformID;
}

 

[DllImport("comctl32.dll", CharSet = CharSet.Auto)]
private static extern int DllGetVersion(ref DLLVERSIONINFO version);


[DllImport("uxtheme.dll", CharSet = CharSet.Auto)]
private static extern bool IsThemeActive();


[DllImport("uxtheme.dll", CharSet = CharSet.Auto)]
private static extern bool IsAppThemed();

This code is pretty standard.

At design-time (i.e., within Visual Studio) my control would render properly.  The problem, however, was evident when I executed the code - it would think that visual styles were disabled and degrade gracefully to the non-themed output.

After a little bit of debugging I discovered that the comctl32.dll version being loaded at design-time was version 6 (the desired version) whereas at runtime I was getting version 5.  The first thing I tried was embedding a manifest into my executable to explicitly load version 6.  No dice - I was still getting version 5.

Ultimately, however, I did discover the source of the issue: I was calling the IsVisualStylesEnabled() too early in the load process of my application.  This was made apparent by placing my control on a form other than the start-up form - everything worked!

When initialized, my control began setting up UI components such as brushes, pens, etc and some of this logic was based on whether the themes were enabled.  Most, if not all, of the setup logic occurs in the InitializeComponent() method which is called from the .ctor() of the control.

In order to guarantee that the proper version of comctl32.dll is loaded, you cannot call the GetDllVersion() method until at least the OnHandleCreated() method of your control.  If you call it earlier, you effectively lock your application (and consequently the host application if your application is a visual component) into using version 5 whether or not the manifest dictates otherwise.

For my purposes, I had to wait until the OnPaintBackground() method to initialize the brushes and other auxiliary objects, but that at least happens after the OnHandleCreated().

Monday, September 01, 2008 10:50:27 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, August 28, 2008

I've been putting together a nice little WinForms Wizard control recently that had a few twist and turns to its development.  In particular, I wanted to make the design-time experience a good one for the end user.  I wanted to support design-time navigation of the wizard pages, drag/drop of control to each child page, reordering of the pages, and much more.  In fact, as soon as I tidy up the code a bit and flesh it out a bit, I'll be posting the component here on my blog for general use.  But enough of that...

The wizard control has a rich design-time experience.  I owe some of the thanks for the functionality to control's ControlDesigner and DesignerActionList.  Among many other things, these classes provides a mechanism for a developer to associate custom verbs and actions with the control at design-time.  These actions are made available to the developer through the smart-tag menu when the control is selected on its design surface.

It is through these custom actions that I've given the programmer the ability to manage pages on the wizard.  Internally, these pages are represented not only as controls on the wizard itself, but also in a collection of pages that the wizard uses to properly navigate the sequence of pages.  It is this collection that gets persisted (serialized) in the form's InitializeComponent() method when saving changes made to the wizard at design-time.

I quickly discovered, however, that all wasn't green grass and blue skies.  I wanted to support reordering the pages through the design-time smart-tags.  While I was able to easily reorder the elements in the page collection and even update the control's display at design-time to reflect the changes, the Visual Studio environment didn't register the change.  Unless I then went ahead and further edited a property through Visual Studio itself (effectively to cause a change in the environment), the reordering of the pages never persisted.  So I had to figure out how to notify Visual Studio that a change had been made.

Fortunately, this isn't difficult, but it's not very intuitive at the same time.

The ControlDesigner's GetService() method provides access to the services available to the control at design time.  Among these is the IComponentChangeService.  It is through this service that you can notify the designer of a change.  To do so, you need a property to update.  I, for one, don't really like the idea of utilizing an existing property for this purpose (though it could be done).

What I did in my solution was create a design-time-only property in the ControlDesigner.  Then, when a change is made that requires the change notification I call a method which 'updates' the property.  The property doesn't really get updated, but at least the code is clearer.

In the control designer:

protected override void PreFilterProperties(IDictionary properties) {
   base.PreFilterProperties(properties);

   Attribute[] attribs = {
                            new BrowsableAttribute(false),
                            new DesignOnlyAttribute(true)
                         };
   PropertyDescriptor prop = TypeDescriptor.CreateProperty(GetType(), "DesignTimeChange", typeof(string), attribs);
   properties.Add("DesignTimeChange", prop);
}

/// <summary>
/// Property created exclusively for the purpose of notifying the
/// designer of changes to properties made through the designer
/// (such as reordering the pages).
/// </summary>
public string DesignTimeChange { get; set; }

Then, also in the Designer, a method that causes a change to be acknowledged:

private void notifyOfChange() {
   PropertyDescriptor prop = TypeDescriptor.GetProperties(this)["DesignTimeChange"];
   getComponentChangeService().OnComponentChanged(this, prop, null, null);
}

That's all there is to it.  When an action occurs in the designer that would not otherwise register a change in the environment, I simply have it call the notifyOfChange() method.

A little more work would be involved if the change were coming from outside the designer.  For instance, if the change was made within the collection or within the control, you'd first have to evaluate whether it's running in design mode, and then retrieve a reference to the ControlDesigner, invoking the method (it'd probably have to be made internal (and it's name appropriately Pascal-cased) rather than private).

Maybe something like this (NOTE: untested, but seemingly approximately accurate):

if ( DesignMode ) {
   IDesignerHost host = ( IDesignerHost )Site.Container;
   ControlDesigner designer = ( ControlDesigner )host.GetDesigner(this);
   designer.NotifyOfChange();
}

Thursday, August 28, 2008 6:47:20 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, August 26, 2008

Earlier this year I posted an article demonstrating how to use the SGEN.exe utility to generate a serialization assembly.  The article identified how to create a custom MSBuild task by editing the project file (e.g., csproj).  I've recently come to discover that the MSBuild task will fail when automating a build of the project through TFS Build.  The error presents itself as follows:

SGEN(0,0): error : File or assembly name 'C:....\bin\CustomLibrary.dll', or one of its dependencies, was not found.

Ultimately, the root of the problem goes back to the SGEN MSBuild task's usage of the $(OutputPath) variable.  Even in TFS Build this is pointing to same location that Visual Studio would point to.  TFS, however, will override the output locations of the compiled applications, but this variable doesn't get updated.  As stated in the MSDN documentation, "OutputPath has been deprecated and OutDir should be used instead whenever possible.".  I was happy to discover that by simply replacing the $(OutputPath) with $(OutDir) in the MSBuild task was all I needed to do; the task will execute properly both from my development machine as well as the build server.

My updated task resembles the following:

<Target Name="GenSerializationAssembly"
        DependsOnTargets="AssignTargetPaths;Compile;ResolveKeySource"
        Inputs="$(MSBuildAllProjects);@(IntermediateAssembly)"
        Outputs="$(OutDir)$(_SGenDllName)">
  <SGen BuildAssemblyName="$(TargetFileName)"
        BuildAssemblyPath="$(OutDir)"
        References="@(ReferencePath)"
        ShouldGenerateSerializer="true"
        UseProxyTypes="false"
        KeyContainer="$(KeyContainerName)"
        KeyFile="$(KeyOriginatorFile)"
        DelaySign="$(DelaySign)"
        ToolPath="$(SGenToolPath)">
    <Output TaskParameter="SerializationAssembly" ItemName="SerializationAssembly" />
  </SGen>
</Target>
<Target Name="AfterBuild" DependsOnTargets="GenSerializationAssembly" />

Tuesday, August 26, 2008 12:06:57 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, August 17, 2008

I've had an issue on two computers (my primary development machine and a virtual pc dev box) which I was finally able to solve today after many hours of frustrated searching and experimenting.  Interestingly, this issue only affected my two machines, but not those of a co-worker.  What was more peculiar was that I have higher privileges in TFS.  The issue revolved around getting the Team Foundation Server Team Explorer 2008 to recognize our automated builds.

Within Visual Studio 2008's Team Explorer pane I am able to browse all work items, documents, reports, etc, but not builds.  In fact, the node shows up with a red 'X' icon and is mislabeled 'Build' rather than the correct 'Builds'.

I attempted to fix it by uninstalling / reinstalling the TFS 2008 Power Tools (July Release), repairing my VS2008 installation, disabling my firewall, disabling my antivirus.  I tried digging into various configuration files and renaming / deleting my cache folder to no avail.

Ultimately, to fix the issue I had to resort to resetting my Visual Studio settings to their factory settings.  To do this, I did the following:

  1. Renamed/Deleted by TFS Cache folder.  On my Vista machine it's found in (C:\Users\[USERNAME]\AppData\Local\Microsoft\Team Foundation\2.0).
  2. Reset Visual Studio 2008 settings by typing devenv /resetuserdata from the command line.
Sunday, August 17, 2008 10:34:53 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Thursday, August 14, 2008

I upgraded my Visual Studio 2008 installation the other day to SP1 and everything went beautifully.  One of the issues that Microsoft fixed centered around the naming of embedded binary resource files.

Traditionally, binary resource files have an extension of .resources.  However, when VS2008 was introduced, it came with a bug that forced you to tack on a second extension: fileName.resources.resources.

Apparently, this is now fixed, but I did have to go back and rename my files accordingly.

Thursday, August 14, 2008 3:27:22 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 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