Monday, 03 January 2005

When does my Password Expire?

Here is a quick and dirty way to figure out when your account (or anyone else's) will expire on the domain.

Prerequisites:
1.  You must be running this from a computer joined to the domain.
2.  You must be running this with valid domain credentials.

How does it work:

There is no attribute that directly holds when your password expires.  It is a calculation done based on two factors - 1. When you last set your password (pwdLastSet), and 2. What your domain policy for the maximum password age (MaxPwdAge) is.

Here is a simple command line app to demonstrate how this is done:

using System;
using System.DirectoryServices;
using System.Reflection;
class Invoker
{
    [STAThread]
    static void Main(string[] args)
    {
        try
        {
            if (args.Length != 1)
            {
                Console.WriteLine("Usage: {0} username", Environment.GetCommandLineArgs()[0]);
                return;
            }
            PasswordExpires pe = new PasswordExpires();
            Console.WriteLine("Password Policy: {0} days", 0 - pe.PasswordAge.Days);
            TimeSpan t = pe.WhenExpires(args[0]);
            if(t == TimeSpan.MaxValue)
                Console.WriteLine("{0}: Password Never Expires", args[0]);
            else if(t == TimeSpan.MinValue)
                Console.WriteLine("{0}: Password Expired", args[0]);
            else
                Console.WriteLine("Password for {0} expires in {1} days at {2}", args[0], t.Days, DateTime.Now.Add(t));
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.ToString()); //debugging info
        }
    }
}
class PasswordExpires
{
    DirectoryEntry _domain;
    TimeSpan _passwordAge = TimeSpan.MinValue;
    const int UF_DONT_EXPIRE_PASSWD            = 0x10000;
    public PasswordExpires()
    {
        //bind with current credentials
        using (DirectoryEntry root = new DirectoryEntry("LDAP://rootDSE", null, null, AuthenticationTypes.Secure))
        {
            string adsPath = String.Format("LDAP://{0}", root.Properties["defaultNamingContext"][0]);
            _domain = new DirectoryEntry(adsPath, null, null, AuthenticationTypes.Secure);
        }
    }
    public TimeSpan PasswordAge
    {
        get
        {
            if(_passwordAge == TimeSpan.MinValue)
            {
                long ldate = LongFromLargeInteger(_domain.Properties["maxPwdAge"][0]);
                _passwordAge =  TimeSpan.FromTicks(ldate);
            }
            
            return _passwordAge;
        }
    }
    public TimeSpan WhenExpires(string username)
    {
        DirectorySearcher ds = new DirectorySearcher(_domain);
        ds.Filter = String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))", username);
        SearchResult sr = FindOne(ds);
        int flags = (int)sr.Properties["userAccountControl"][0];
        if(Convert.ToBoolean(flags & UF_DONT_EXPIRE_PASSWD))
        {
            return TimeSpan.MaxValue; //password never expires
        }
        //get when they last set their password
        DateTime pwdLastSet = DateTime.FromFileTime((long)sr.Properties["pwdLastSet"][0]);
        
        if(pwdLastSet.Subtract(PasswordAge).CompareTo(DateTime.Now) > 0)
        {
            return pwdLastSet.Subtract(PasswordAge).Subtract(DateTime.Now);
        }
        else
            return TimeSpan.MinValue;  //already expired
    }
    private long LongFromLargeInteger(object largeInteger)
    {
        System.Type type = largeInteger.GetType();
        int highPart = (int)type.InvokeMember("HighPart",BindingFlags.GetProperty, null, largeInteger, null);
        int lowPart  = (int)type.InvokeMember("LowPart",BindingFlags.GetProperty, null, largeInteger, null);
            
        return (long)highPart << 32 | (uint)lowPart;
    }
    private SearchResult FindOne(DirectorySearcher searcher)
    {
        SearchResult sr = null;
        using (SearchResultCollection src = searcher.FindAll())    
        {
            if(src.Count>0)
            {
                sr = src[0];
            }
        }
        return sr;
    }
}

Wednesday, 22 December 2004

Memory Leak using DirectorySearcher .FindOne()?

I remember Joe Kaplan telling me one time that there was a memory leak when using the .FindOne() method of the DirectorySearcher. I also believe Max Vaughn from MS told me the same thing, now that I think about it. I decided to dig a little bit more into this and figure out what was actually wrong. It turns out that this bug will only really affect you when you are doing a lot of searches and not finding any results. That's right, I said not finding any results. A quick look using Reflector and we can see why this is:

            public SearchResult FindOne()
            {
                SearchResultCollection collection1 = this.FindAll(false);
                foreach (SearchResult result1 in collection1)
                {
                    collection1.Dispose();
                    return result1;
                }
                return null;
            }

As you can see, the .Dispose() is only called when at least one SearchResult is found. If no SearchResult is found, then the SearchResultCollection hangs onto its unmanaged resource. What's the fix? It is a pretty simple fix actually - just make sure you always call .Dispose() on your SearchResultCollection by using the .FindAll() method instead:

        public static SearchResult FindOne( DirectorySearcher ds )
        {
            SearchResult sr = null;

            using (SearchResultCollection src = ds.FindAll())
            {
                if (src.Count > 0)
                {
                    sr = src[0];
                }
            }
            return sr;
        }


Note, for those unfamiliar with C# syntax, the 'using' statement will call .Dispose() in a try/finally manner at the last brace.

Friday, 17 December 2004

MCE 2005 Salvaged

I am posting this in the hopes that it will save someone the agony that I have gone through with my Media Center lately.  For the last month, the machine has been entirely unusable.  It would work for only brief moments at a time and then fail with “Loss of Signal” errors.  I have not been able to record a thing for the last month or so - I would come home to find the machine locked hard and needing to be physically reset.

Initially, I blamed the new 2005 version of the software, and I tried every MPEG decoder known to man with no success.  I must have tried 10 different PVR card driver and MPEG decoder combos and to no avail.  The problem?  It turns out that SATA drivers were to blame.  Specifically the IDE versions of the drivers.  I am using an NForce2 board with SATA RAID.  I was trying to figure out the problem and was checking the Event Logs when I saw a large number of disk errors in the logs.  Initially, I had thought this was because MCE was getting locked up and then having to do a hard reset I was causing disk errors.  Actually, it turns out that those errors were some of the symptoms leading up to the lock-ups and “Loss of Signal” errors.

As I dug deeper in the SATA drivers, I found that they were already 2 years old, so naturally I went looking for the latest drivers.  Turns out, I had the latest drivers, they just had not been updated in 2 years (I have a SiL 3114 SATA controller btw).  Digging into Nforce2 forums, I found others reporting similar errors with the controllers.  One clever person found out that if you enable RAID, and use the RAID drivers those errors just seem to go away.  You don't actually have to be using RAID, you just need to use the drivers.  So, that was the eventual solution - I just enabled RAID in the  BIOS and installed the RAID drivers.  I am not using RAID, but the problem is solved.  My Media Center has been chugging away for two days straight and nary a hiccup now.  I have checked the logs again, and no more disk errors.

Why “Loss of Signal” errors?  From what I gather, the SATA IDE drivers are so bad for this controller that the SATA drives have trouble sustaining any sort of data stream.  I suspect that particular error was caused by the drives being unable to keep up with the streaming MPEG as the were written to the drives.

It's nice to finally have a Media Center again.