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:
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:
Posted on 10:09 AM by j. montgomery, CISSP, GNET, GSEC and filed under
.net,
activedirectory,
ad,
ldap,
programming,
reporting,
smartcard
| 10 Comments »
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:

Secure Coding in .NET: Developing Defensible Applications