Active Directory stores every object as a set of attributes.
A user account is not simply “John Smith”. It is a collection of attribute values such as givenName, sn, sAMAccountName, userPrincipalName, mail, memberOf and dozens more.
Once you understand which attribute maps to which field, tasks such as reporting, scripting, provisioning and troubleshooting become far easier.
This is a reference page. It covers:
- The attributes behind each tab in Active Directory Users and Computers
userAccountControland its bit flags- The timestamp attributes and how to convert them
- The difference between
lastLogon,lastLogonTimestampandlastLogonDate - Attributes that behave differently from how people expect
Attributes are not columns
It helps to think of an LDAP attribute as a named value on an object rather than a column in a table.
Some attributes hold a single value. Some hold many. Some are computed by the domain controller rather than stored. Some are stored in a format that looks nothing like what ADUC displays.
That distinction matters, because a script that treats memberOf like a simple list, or pwdLastSet like a date, will produce wrong results.
General tab
| ADUC field | LDAP attribute | Example |
|---|---|---|
| First name | givenName | John |
| Initials | initials | A |
| Last name | sn | Smith |
| Display name | displayName | John Smith |
| Description | description | Finance team |
| Office | physicalDeliveryOfficeName | Brisbane |
| Telephone number | telephoneNumber | +61 7 1234 5678 |
mail | john.smith@example.com | |
| Web page | wWWHomePage | https://example.com |
Note that Office is physicalDeliveryOfficeName, not telephoneNumber. The two are often confused because they sit next to each other in the interface.
Account tab
| ADUC field | LDAP attribute | Notes |
|---|---|---|
| User logon name | userPrincipalName | john.smith@example.com |
| User logon name (pre-Windows 2000) | sAMAccountName | jsmith |
| Logon hours | logonHours | Binary |
| Log on to | userWorkstations | Comma-separated list |
| Account expires | accountExpires | FILETIME |
| Account options | userAccountControl | Bit flags |
| User must change password at next logon | pwdLastSet | Set to 0 |
The last two are covered in detail below, because both behave differently from the way the interface presents them.
Address tab
| ADUC field | LDAP attribute |
|---|---|
| Street | streetAddress |
| P.O. Box | postOfficeBox |
| City | l |
| State/province | st |
| Zip/Postal Code | postalCode |
| Country/region | c, co, countryCode |
The three country attributes
This one catches people out.
Selecting a country in ADUC writes to three separate attributes:
| Attribute | Contains | Example |
|---|---|---|
c | Two-letter ISO 3166 code | AU |
co | Country name | Australia |
countryCode | Numeric ISO code | 36 |
A script that sets only co produces a user whose country looks right in ADUC but whose c attribute is empty. Anything downstream reading c (including some Microsoft 365 provisioning) will see no country at all.
Set all three, or none.
Telephones tab
| ADUC field | LDAP attribute |
|---|---|
| Home | homePhone |
| Pager | pager |
| Mobile | mobile |
| Fax | facsimileTelephoneNumber |
| IP phone | ipPhone |
| Notes | info |
info is worth knowing about. It’s a free-text field that many organisations repurpose, and it’s often where useful undocumented information ends up.
Organization tab
| ADUC field | LDAP attribute | Notes |
|---|---|---|
| Job Title | title | |
| Department | department | |
| Company | company | |
| Manager | manager | Distinguished name |
| Direct reports | directReports | Computed, not stored |
manager and directReports
manager holds a distinguished name, not a display name:
CN=Jane Doe,OU=Managers,OU=Users,DC=example,DC=com
So this doesn’t work:
Set-ADUser jsmith -Manager "Jane Doe"
This does:
$manager = Get-ADUser -Identity jdoe
Set-ADUser -Identity jsmith -Manager $manager.DistinguishedName
directReports is back-linked. You don’t set it. Active Directory computes it from everyone whose manager attribute points at that user. Trying to write to it will fail.
Profile tab
| ADUC field | LDAP attribute |
|---|---|
| Profile path | profilePath |
| Logon script | scriptPath |
| Home folder – Local path | homeDirectory |
| Home folder – Connect | homeDrive |
Member Of tab
| ADUC field | LDAP attribute | Notes |
|---|---|---|
| Member of | memberOf | Back-linked |
| Primary group | primaryGroupID | RID, default 513 |
memberOf doesn’t contain everything
Two things trip people up here.
memberOf is back-linked. It’s computed from the member attribute on each group. You add a user to a group by modifying the group, not the user.
memberOf does not include the primary group. By default every user’s primary group is Domain Users (primaryGroupID 513), and that membership does not appear in memberOf.
So this:
Get-ADUser jsmith -Properties memberOf | Select-Object -ExpandProperty memberOf
will not list Domain Users, even though the user is a member.
memberOf is not transitive by default. If a user is in Group A, and Group A is in Group B, memberOf shows only Group A. For the full nested picture, use the matching rule in chain:
Get-ADGroup -LDAPFilter "(member:1.2.840.113556.1.4.1941:=CN=John Smith,OU=Users,DC=example,DC=com)"
That OID is LDAP_MATCHING_RULE_IN_CHAIN, and it walks nested membership.
Identity attributes
| Attribute | Contains | Notes |
|---|---|---|
distinguishedName | Full DN | Changes if the object moves |
objectGUID | Unique identifier | Never changes |
objectSid | Security identifier | Changes on domain migration |
sAMAccountName | Pre-2000 logon name | Must be unique in the domain |
userPrincipalName | UPN | Must be unique in the forest |
employeeID | Free text | Often used for HR integration |
employeeNumber | Free text | |
extensionAttribute1 to 15 | Free text | Exchange schema |
If you need a stable identifier for a user across renames and moves, use objectGUID. The DN changes when an object is moved between OUs, and sAMAccountName changes when a user is renamed.
Exchange and mail attributes
| Attribute | Contains |
|---|---|
mail | Primary SMTP address as displayed |
proxyAddresses | All addresses, multi-valued |
mailNickname | Exchange alias |
targetAddress | Forwarding address for mail-enabled users |
msExchHideFromAddressLists | Hidden from GAL |
proxyAddresses
This is multi-valued and case-sensitive in a specific way:
SMTP:john.smith@example.com
smtp:j.smith@example.com
smtp:jsmith@old-domain.com
Uppercase SMTP: marks the primary address. Lowercase smtp: entries are secondary aliases. There can be only one primary.
Duplicate proxyAddresses across two objects is one of the most common reasons an object fails to synchronise to Microsoft Entra ID.
userAccountControl
This is a single integer holding a set of bit flags. It’s the most-queried attribute in Active Directory and the one most often misunderstood.
Common flag values
| Value | Flag | Meaning |
|---|---|---|
| 2 | ACCOUNTDISABLE | Account is disabled |
| 16 | LOCKOUT | Account is locked out |
| 32 | PASSWD_NOTREQD | No password required |
| 64 | PASSWD_CANT_CHANGE | User cannot change password |
| 512 | NORMAL_ACCOUNT | Standard user account |
| 2048 | INTERDOMAIN_TRUST_ACCOUNT | Trust account |
| 4096 | WORKSTATION_TRUST_ACCOUNT | Computer account |
| 8192 | SERVER_TRUST_ACCOUNT | Domain controller account |
| 65536 | DONT_EXPIRE_PASSWORD | Password never expires |
| 262144 | SMARTCARD_REQUIRED | Smart card required for logon |
| 524288 | TRUSTED_FOR_DELEGATION | Trusted for delegation |
| 1048576 | NOT_DELEGATED | Sensitive, cannot be delegated |
| 2097152 | USE_DES_KEY_ONLY | DES encryption only |
| 4194304 | DONT_REQUIRE_PREAUTH | Kerberos pre-auth not required |
Not every flag in the full list is one you’ll meet in practice. Some are computed by the domain controller rather than set by an administrator, and some relate to features that are effectively obsolete. In day-to-day administration the ones worth knowing are 2, 512, 65536 and 262144.
PASSWD_CANT_CHANGE (64) is a particular oddity: despite appearing in the flag list, it cannot be set by modifying userAccountControl directly. It’s implemented as an ACE on the object.
Combined values
Because the flags are additive, the stored value is a sum:
| Value | Meaning |
|---|---|
| 512 | Normal account, enabled |
| 514 | Normal account, disabled (512 + 2) |
| 546 | Disabled, password not required (512 + 2 + 32) |
| 66048 | Enabled, password never expires (512 + 65536) |
| 66050 | Disabled, password never expires (512 + 2 + 65536) |
| 262656 | Enabled, smart card required (512 + 262144) |
| 4096 | Computer account |
| 532480 | Domain controller (8192 + 524288) |
Querying bit flags properly
Don’t test for equality. A user with password-never-expires and smartcard-required has a value you won’t have anticipated.
In PowerShell, use -band:
Get-ADUser -Filter 'userAccountControl -band 65536' -Properties userAccountControl |
Select-Object Name, userAccountControl
In an LDAP filter, use the bitwise AND matching rule:
(userAccountControl:1.2.840.113556.1.4.803:=2)
That OID is LDAP_MATCHING_RULE_BIT_AND. The example above finds disabled accounts.
To find enabled accounts with password never expires:
(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
Use the cmdlets where you can
For most changes, PowerShell’s dedicated cmdlets are safer than editing the integer:
Disable-ADAccount -Identity jsmith
Enable-ADAccount -Identity jsmith
Set-ADUser -Identity jsmith -PasswordNeverExpires $true
Set-ADAccountControl -Identity jsmith -PasswordNeverExpires $true
Setting userAccountControl directly means calculating the whole value yourself, and getting it wrong can disable accounts or remove password requirements.
Timestamp attributes and FILETIME
Several attributes store time as a Windows FILETIME: the number of 100-nanosecond intervals since 1 January 1601 (UTC).
| Attribute | Contains |
|---|---|
pwdLastSet | When the password was last set |
accountExpires | When the account expires |
lastLogon | Last logon, not replicated |
lastLogonTimestamp | Last logon, replicated |
badPasswordTime | Last failed logon attempt |
lockoutTime | When the account was locked |
A raw value looks like this:
133412345678901234
Convert it:
[datetime]::FromFileTime(133412345678901234)
Or for an attribute read directly:
Get-ADUser jsmith -Properties pwdLastSet |
Select-Object Name, @{N='PasswordLastSet';E={[datetime]::FromFileTime($_.pwdLastSet)}}
Special values
| Value | Meaning |
|---|---|
0 for accountExpires | Never expires |
9223372036854775807 for accountExpires | Never expires |
0 for pwdLastSet | User must change password at next logon |
-1 for pwdLastSet | Set to current time (write-only) |
0 for lastLogon | Never logged on to this DC |
Both 0 and 9223372036854775807 mean “never” for accountExpires, which is why a script checking only for 0 will miss accounts set to never expire through ADUC.
[datetime]::FromFileTime(0) returns 1 January 1601, not an error. Check for zero before converting.
pwdLastSet is not a normal attribute
You cannot set an arbitrary date. The only values you can write are:
0– forces password change at next logon-1– sets it to the current time
Setting it to 0 and then back to -1 is the scripted equivalent of ticking and unticking “User must change password at next logon”.
lastLogon vs lastLogonTimestamp vs lastLogonDate
This causes more incorrect reporting than any other attribute in Active Directory.
| Attribute | Replicated | Accuracy | Where it lives |
|---|---|---|---|
lastLogon | No | Exact | Each DC separately |
lastLogonTimestamp | Yes | Up to 14 days stale | Replicated |
lastLogonDate | n/a | Same as above | PowerShell only |
lastLogon
Updated on the domain controller that authenticated the user, and not replicated. Every DC holds a different value.
To get an accurate answer you must query every domain controller and take the highest value:
$user = 'jsmith'
Get-ADDomainController -Filter * | ForEach-Object {
$dc = $_.HostName
$u = Get-ADUser $user -Properties lastLogon -Server $dc
[PSCustomObject]@{
DC = $dc
LastLogon = if ($u.lastLogon) { [datetime]::FromFileTime($u.lastLogon) } else { $null }
}
} | Sort-Object LastLogon -Descending
lastLogonTimestamp
Replicated, but deliberately imprecise. It’s only updated if the new logon is more than msDS-LogonTimeSyncInterval days newer than the stored value. That defaults to 14 days, minus a random offset of up to 5 days.
So a user who logged in this morning may show a lastLogonTimestamp from two weeks ago. That’s expected behaviour, not a fault.
lastLogonDate
This is not an Active Directory attribute. It’s a constructed property that the PowerShell ActiveDirectory module presents, holding lastLogonTimestamp converted to a DateTime.
Useful for readability, but it inherits the same 14-day imprecision.
Which to use
For finding genuinely stale accounts, lastLogonTimestamp or lastLogonDate is the right choice. The imprecision doesn’t matter when your threshold is 90 or 180 days:
$cutoff = (Get-Date).AddDays(-90)
Get-ADUser -Filter {LastLogonDate -lt $cutoff -and Enabled -eq $true} `
-Properties LastLogonDate |
Select-Object Name, SamAccountName, LastLogonDate |
Sort-Object LastLogonDate
For “did this user log in today?”, you need lastLogon queried across all domain controllers.
Reporting a user as inactive for three weeks based on lastLogonTimestamp when they logged in an hour ago is the classic version of this mistake.
Password and lockout attributes
| Attribute | Contains | Notes |
|---|---|---|
pwdLastSet | Password set time | FILETIME |
badPwdCount | Failed attempts | Not replicated |
badPasswordTime | Last failed attempt | Not replicated |
lockoutTime | Lockout time | 0 means not locked |
msDS-UserPasswordExpiryTimeComputed | Password expiry | Computed |
msDS-UserPasswordExpiryTimeComputed is a constructed attribute, calculated from pwdLastSet and the applicable password policy. It isn’t stored, so you must request it explicitly:
Get-ADUser jsmith -Properties msDS-UserPasswordExpiryTimeComputed |
Select-Object Name, @{
N = 'PasswordExpires'
E = { [datetime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed') }
}
For an account with password-never-expires set, this returns the maximum FILETIME value, which converts to a date in the year 30828. That’s correct, not a bug.
badPwdCount and badPasswordTime are not replicated either. For account lockout investigation, query the PDC emulator, which does receive lockout information from other DCs:
$pdc = (Get-ADDomain).PDCEmulator
Get-ADUser jsmith -Properties badPwdCount, lockoutTime, LockedOut -Server $pdc
Object metadata
| Attribute | Contains |
|---|---|
whenCreated | Object creation time |
whenChanged | Last modification time |
objectClass | Object type hierarchy |
objectCategory | Simplified type for indexing |
uSNCreated | Update sequence number at creation |
uSNChanged | Update sequence number at last change |
whenCreated and whenChanged use Generalized Time rather than FILETIME:
20260923140530.0Z
PowerShell converts these automatically, so they appear as normal DateTime values.
objectCategory is indexed and objectClass generally isn’t, so LDAP filters using objectCategory are usually faster:
(&(objectCategory=person)(objectClass=user))
Getting the full attribute list
To see everything on a single object:
Get-ADUser jsmith -Properties * | Format-List
To see only populated attributes:
Get-ADUser jsmith -Properties * |
Get-Member -MemberType Property |
Where-Object { $null -ne (Get-ADUser jsmith -Properties *).($_.Name) } |
Select-Object Name
To see what the schema defines for user objects:
$schema = [DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()
$schema.FindClass('user').MandatoryProperties | Select-Object Name
$schema.FindClass('user').OptionalProperties | Select-Object Name
Common mistakes
Assuming Get-ADUser returns everything. By default it returns a small default set. Anything else needs -Properties.
Treating memberOf as complete. It excludes the primary group and doesn’t show nested membership.
Trying to write directReports or memberOf. Both are back-linked and computed.
Setting manager to a display name. It needs a distinguished name.
Converting FILETIME without checking for zero. [datetime]::FromFileTime(0) returns 1601.
Using lastLogonTimestamp for precise activity. Up to 14 days stale by design.
Testing userAccountControl for equality. Use -band or the bitwise LDAP matching rule.
Setting co without c and countryCode. Produces an inconsistent record.
Assuming badPwdCount is accurate on any DC. It isn’t replicated. Query the PDC emulator.
Microsoft references
- All Active Directory schema attributes – the full attribute reference including syntax, indexing and replication behaviour
- Use the userAccountControl flags to manipulate user account properties – the complete flag list and values
- Get-ADUser – parameters, filters and the default property set

From my early days on the helpdesk through roles as a service desk manager, systems administrator, and network engineer, I’ve spent more than 25 years in the IT world. As I transition into cyber security, my goal is to make tech a little less confusing by sharing what I’ve learned and helping others wherever I can.

