Active Directory Permitted Logon Times with C# .Net 3.5 using System.DirectoryServices.AccountManagement

Recently I was working on a proof of concept for some active directory manipulation.  Using the System.DirectoryServices.AccountManagement was the easiest and best option for managing accounts (as the name implies).  However, the one thing that I really needed was a way to restrict when a particular user would be able to logon.

What I discovered was that the UserPrincipal class does in fact have a property called PermittedLogonTimes, but the catch is that there is no documentation on how to set the property for specific hours and to make this more confusing it is a byte[].

So after a little bit of playing around in Active Directory, setting the permitted logon times and reading it back out in C# it finally made sense.  The property is basically a byte[21] with a mask to cover the hours the user is allowed to logon.  Each day of the week is broken down into 3 byte values (refer to the table below for the index and the hours).

Sunday Monday Tuesday Wednesday Thursday Friday Saturday
Midnight-8am 1 4 7 10 13 16 19
8am-4pm 2 5 8 11 14 17 20
4pm-Midnight 3 6 9 12 15 18 0

Each hour in each individual block has a specific mask value.  To get a specific time span, all you need to do is add up the values for the hours you want.  Refer to the following table:

Group 1 Group 2 Group 3
1 12am 8am 4pm
2 1am 9am 5pm
4 2am 10am 6pm
8 3am 11am 7pm
16 4am 12pm 8pm
32 5am 1pm 9pm
64 6am 2pm 10pm
128 7am 3pm 11pm

For example, if you would like to allow logon on Monday from 1pm-5pm your byte array would be all zeros except value at index 5 would be 224 and index 6 would be 1.

I’ve written the following classes to aid in calculating the byte masks:

LogonTime – is used to pass the information for logon for a specific day.

PermittedLogonTime – used to calculate the actual values that need to be passed to Active Directory.

http://gist.github.com/568549

Update 5/6/2011 : Thanks to comments and testing by Simone Greci, we came to the conclusion that the masks provided here are for Pacific Standard Time (PST, GMT -8).  I would like to update this code when I have time but keep in mind of this limitation.

Update 10/18/2011: I’ve finally gotten around to rewriting the code.  I rewrote everything from the ground up so it also takes into account the timezone.  By default it should go to your machine’s time zone, but can be passed specific timezones.  I’ve included code that converts the AD byte mask back into a list of the LogonTime objects.  To make it easier to use, I’ve compiled it into a project so you can just import the dll and use it.  The project is up on github (here).

There is a demo console app that compares the output to my old code that worked for PST (-8 GMT).

Usage is fairly straight forward:

To create a new logon time using your machine’s time zone:

var time = new LogonTime(DayOfWeek.Monday, 
new DateTime(2011, 1, 1, 8, 0, 0), 
new DateTime(2011, 1, 1, 10, 0, 0));

To use a specific time zone:

var zone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var time = new LogonTime(DayOfWeek.Monday, 
     new DateTime(2011, 1, 1, 8, 0, 0), 
     new DateTime(2011, 1, 1, 10, 0, 0), zone);

To get the byte mask to submit to AD:

var zone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var time = new LogonTime(DayOfWeek.Monday, 
     new DateTime(2011, 1, 1, 8, 0, 0), 
     new DateTime(2011, 1, 1, 10, 0, 0), zone); 
var times = new List<LogonTime>();
times.add(time);
var mask = PermittedLogonTimes.GetByteMask(times)

To get the list of times from the AD mask:

byte[] mask;// populate from AD
var times = PermittedLogonTimes.GetLogonTimes(mask);

*The resulting times that are parsed have invalid dates, just the hour ranges.

I hope this helps those of you using those permitted logon times. Enjoy!