LDAP Attributes

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
  • userAccountControl and its bit flags
  • The timestamp attributes and how to convert them
  • The difference between lastLogon, lastLogonTimestamp and lastLogonDate
  • 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 fieldLDAP attributeExample
First namegivenNameJohn
InitialsinitialsA
Last namesnSmith
Display namedisplayNameJohn Smith
DescriptiondescriptionFinance team
OfficephysicalDeliveryOfficeNameBrisbane
Telephone numbertelephoneNumber+61 7 1234 5678
E-mailmailjohn.smith@example.com
Web pagewWWHomePagehttps://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 fieldLDAP attributeNotes
User logon nameuserPrincipalNamejohn.smith@example.com
User logon name (pre-Windows 2000)sAMAccountNamejsmith
Logon hourslogonHoursBinary
Log on touserWorkstationsComma-separated list
Account expiresaccountExpiresFILETIME
Account optionsuserAccountControlBit flags
User must change password at next logonpwdLastSetSet to 0

The last two are covered in detail below, because both behave differently from the way the interface presents them.

Address tab

ADUC fieldLDAP attribute
StreetstreetAddress
P.O. BoxpostOfficeBox
Cityl
State/provincest
Zip/Postal CodepostalCode
Country/regionc, co, countryCode

The three country attributes

This one catches people out.

Selecting a country in ADUC writes to three separate attributes:

AttributeContainsExample
cTwo-letter ISO 3166 codeAU
coCountry nameAustralia
countryCodeNumeric ISO code36

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 fieldLDAP attribute
HomehomePhone
Pagerpager
Mobilemobile
FaxfacsimileTelephoneNumber
IP phoneipPhone
Notesinfo

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 fieldLDAP attributeNotes
Job Titletitle
Departmentdepartment
Companycompany
ManagermanagerDistinguished name
Direct reportsdirectReportsComputed, 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 fieldLDAP attribute
Profile pathprofilePath
Logon scriptscriptPath
Home folder – Local pathhomeDirectory
Home folder – ConnecthomeDrive

Member Of tab

ADUC fieldLDAP attributeNotes
Member ofmemberOfBack-linked
Primary groupprimaryGroupIDRID, 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

AttributeContainsNotes
distinguishedNameFull DNChanges if the object moves
objectGUIDUnique identifierNever changes
objectSidSecurity identifierChanges on domain migration
sAMAccountNamePre-2000 logon nameMust be unique in the domain
userPrincipalNameUPNMust be unique in the forest
employeeIDFree textOften used for HR integration
employeeNumberFree text
extensionAttribute1 to 15Free textExchange 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

AttributeContains
mailPrimary SMTP address as displayed
proxyAddressesAll addresses, multi-valued
mailNicknameExchange alias
targetAddressForwarding address for mail-enabled users
msExchHideFromAddressListsHidden 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

ValueFlagMeaning
2ACCOUNTDISABLEAccount is disabled
16LOCKOUTAccount is locked out
32PASSWD_NOTREQDNo password required
64PASSWD_CANT_CHANGEUser cannot change password
512NORMAL_ACCOUNTStandard user account
2048INTERDOMAIN_TRUST_ACCOUNTTrust account
4096WORKSTATION_TRUST_ACCOUNTComputer account
8192SERVER_TRUST_ACCOUNTDomain controller account
65536DONT_EXPIRE_PASSWORDPassword never expires
262144SMARTCARD_REQUIREDSmart card required for logon
524288TRUSTED_FOR_DELEGATIONTrusted for delegation
1048576NOT_DELEGATEDSensitive, cannot be delegated
2097152USE_DES_KEY_ONLYDES encryption only
4194304DONT_REQUIRE_PREAUTHKerberos 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:

ValueMeaning
512Normal account, enabled
514Normal account, disabled (512 + 2)
546Disabled, password not required (512 + 2 + 32)
66048Enabled, password never expires (512 + 65536)
66050Disabled, password never expires (512 + 2 + 65536)
262656Enabled, smart card required (512 + 262144)
4096Computer account
532480Domain 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).

AttributeContains
pwdLastSetWhen the password was last set
accountExpiresWhen the account expires
lastLogonLast logon, not replicated
lastLogonTimestampLast logon, replicated
badPasswordTimeLast failed logon attempt
lockoutTimeWhen 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

ValueMeaning
0 for accountExpiresNever expires
9223372036854775807 for accountExpiresNever expires
0 for pwdLastSetUser must change password at next logon
-1 for pwdLastSetSet to current time (write-only)
0 for lastLogonNever 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.

AttributeReplicatedAccuracyWhere it lives
lastLogonNoExactEach DC separately
lastLogonTimestampYesUp to 14 days staleReplicated
lastLogonDaten/aSame as abovePowerShell 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

AttributeContainsNotes
pwdLastSetPassword set timeFILETIME
badPwdCountFailed attemptsNot replicated
badPasswordTimeLast failed attemptNot replicated
lockoutTimeLockout time0 means not locked
msDS-UserPasswordExpiryTimeComputedPassword expiryComputed

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

AttributeContains
whenCreatedObject creation time
whenChangedLast modification time
objectClassObject type hierarchy
objectCategorySimplified type for indexing
uSNCreatedUpdate sequence number at creation
uSNChangedUpdate 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

Leave a Reply

Your email address will not be published. Required fields are marked *