Reporting on Smart Card Logon in Active Directory

Yesterday, the higher-ups needed a report of all users in Active Directory whose accounts required Smart Cards to log into the Domain (and those that did not). Most things I have needed to retrieve out of Active Directory have been quick to find and usually obvious, but this particular information was a bit more elusive. Any AD guru would certainly know exactly where to look, but alas, I did not. As I looked around Google and on all the properties on the User object in AD, nothing obvious came up – then I stumbled across this KB article: kb305144.

The Smart Card Required for Interactive Logon check box located in the property pane for a User in Active Directory Users and Computers mmc snap-in is stored as a bit field in the UserAccountControl property of the User object in Active Directory. Once I saw it was a bit field and I had all the possible values, the rest is simple.

Here the quick and dirty code I wrote to run the report:

using System.DirectoryServices;

namespace AdSmartCardReport
{
public class AdUtility
{
// This retrieves the root entry point for AD for
// current DOMAIN
internal static string ADsDomainName
{
get
{
DirectoryEntry rootEntry
=
new DirectoryEntry("LDAP://RootDSE");
return "LDAP://" +
(
string)rootEntry.Properties
[
"defaultNamingContext"][0];
}
}

public void RunReport()
{
DirectoryEntry _entry
=
new DirectoryEntry(AdUtility.ADsDomainName);

_entry.AuthenticationType
=
AuthenticationTypes.FastBind
|
AuthenticationTypes.ReadonlyServer;

DirectorySearcher searcher
=
new DirectorySearcher(_entry);

searcher.SearchScope
= SearchScope.Subtree;
searcher.Filter
= "(&(objectCategory=person)" +
"(objectClass=user)(!" +
"useraccountcontrol:1.2.840.113556.1.4.803:=2))";
searcher.PropertiesToLoad.Add(
"UserAccountControl");
searcher.PropertiesToLoad.Add(
"samAccountName");
searcher.PropertiesToLoad.Add(
"DisplayName");
searcher.PropertiesToLoad.Add(
"mail");

try
{
SearchResultCollection results
=
searcher.FindAll();
int counter = 0;

foreach (SearchResult result in results)
{
ResultPropertyCollection collection
=
result.Properties;

string displayName =
(
string)collection["DisplayName"][0];
string email = (string)collection["mail"][0];
string samAccountName =
(
string)collection["samAccountName"][0];

UserAccountControl userAccountControl
=
(UserAccountControl)collection
[
"UserAccountControl"][0];

if ((userAccountControl &
UserAccountControl.SMARTCARD_REQUIRED)
== UserAccountControl.SMARTCARD_REQUIRED)
{
counter
++;
}

// Write out to console in csv format
System.Console.Write("\"");
System.Console.Write(displayName);
System.Console.Write(
"\",\"");
System.Console.Write(email);
System.Console.Write(
"\",\"");
System.Console.Write(samAccountName);
System.Console.Write(
"\",\"");
System.Console.Write(userAccountControl);
System.Console.WriteLine(
"\"");
}

System.Console.WriteLine(
"There are " + counter +
"users required to use a Smart Card to logon " +
"in the domain.")
);
}
finally
{
if (_entry != null)
{
_entry.Close();
_entry.Dispose();
}
}
}
}

// See: http://support.microsoft.com/kb/305144/ for more
// info on UserAccountControl flag in Active Directory
[Flags()]
public enum UserAccountControl
{
SCRIPT
= 0x1,
ACCOUNTDISABLE
= 0x2,
HOMEDIR_REQUIRED
= 0x8,
LOCKOUT
= 0x10,
PASSWD_NOTREQD
= 0x20,
PASSWD_CANT_CHANGE
= 0x40,
ENCRYPTED_TEXT_PWD_ALLOWED
= 0x80,
TEMP_DUPLICATE_ACCOUNT
= 0x100,
NORMAL_ACCOUNT
= 0x200,
INTERDOMAIN_TRUST_ACCOUNT
= 0x800,
WORKSTATION_TRUST_ACCOUNT
= 0x1000,
SERVER_TRUST_ACCOUNT
= 0x2000,
DONT_EXPIRE_PASSWORD
= 0x10000,
MNS_LOGON_ACCOUNT
= 0x20000,
SMARTCARD_REQUIRED
= 0x40000,
TRUSTED_FOR_DELEGATION
= 0x80000,
NOT_DELEGATED
= 0x100000,
USE_DES_KEY_ONLY
= 0x200000
DONT_REQ_PREAUTH
= 0x400000,
PASSWORD_EXPIRED
= 0x800000,
TRUSTED_TO_AUTH_FOR_DELEGATION
= 0x1000000
}
}


I hope others may find this helpful.

UPDATE: You can also filter the search results using the same UserAccountControl enum.

Let's inspect the following expression:

(&(objectCategory=person)(objectClass=user)(useraccountcontrol:1.2.840.113556.1.4.803:=262144))

decodes to:

Filter on AD Records where (objectCategory = Person) && (objectClass=user) && (UserAccountControl & 0x40000)

The number sequence 1.2.840.113556.1.4.803 tells AD to to a bitwise AND operation on the value in useraccountcontrol and 262144 (0x40000 hex). See kb269181 on how to query Active Directory by using a bitwise filter.

Using a bitwise AND (&) on UserAccountControl and 0x40000 will return true (bit 1) if that bit field is set.


If you found this article helpful: kick it on DotNetKicks.com

10 comments: