Friday, July 22, 2011

RemoteApp and Passwords

We are in the process of preparing a legacy Windows application for deployment as a Windows Terminal Services RemoteApp over the internet. One of the issues we ran into was the lack of password expiration detection and password change support in the RemoteApp infrastructure. Currently, RemoteApp is positioned primarily for usage within a single Windows domain/organisation boundary. As a workaround for the lack of password management support in RemoteApp, we decided to fill this gap as much as possible ourselves.

We used the following ADSI (System.DirectoryServices in .NET) call to reliably detect password expiration date, regardless whether the user account was a local or domain account and regardless of which security policy applies to the given user (see IAdsUser interface reference):

static DateTime PasswordExpiryTime(string domainOrMachine, string userName)
{
  using (var directoryEntry = new System.DirectoryServices.DirectoryEntry("WinNT://" + domainOrMachineName + '/' + userName + ",user"))
  {
    try
    {
      return (DateTime)directoryEntry.InvokeGet("PasswordExpirationDate");
    }
    catch (TargetInvocationException e)
    {
      throw e.InnerException;
    }
  }
}

Initially we also used ADSI to perform the password change:

static void ChangePassword(string domainOrMachine, string userName, string oldPassword, string newPassword)
{
  using (var directoryEntry = new System.DirectoryServices.DirectoryEntry("WinNT://" + domainOrMachineName + '/' + userName + ",user"))
  {
    try
    {
      directoryEntry.Invoke("ChangePassword", oldPassword, newPassword);
    }
    catch (TargetInvocationException e)
    {
      throw e.InnerException;
    }
  }
}

However, after a password change via ADSI we often encountered problems when connecting to for example SQL Server databases using integrated security. It seems that certain cached credentials didn't get refreshed. Ultimately, the resolution for this problem was to directly call the Windows NetUserChangePassword API:

static void ChangePassword(string domainOrMachine, string userName, string oldPassword, string newPassword)
{
  uint returnValue = NetUserChangePassword(domainOrMachine, userName, oldPassword, newPassword);
  if (returnValue != 0) throw new Win32Exception();
}