LDAP Attributes

Active Directory stores every object as a set of LDAP attributes. A user account isn’t simply “John Smith”. It’s 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, reporting, scripting, provisioning and troubleshooting all become far easier.

This is a reference page. It covers the attributes behind each tab in Active Directory Users and Computers (ADUC), userAccountControl and its bit flags, the timestamp attributes and how to convert them, the difference between lastLogon, lastLogonTimestamp and LastLogonDate, and the attributes that behave differently from how people expect.

Attributes aren’t 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 and some hold many. Some are computed by the domain controller rather than stored, and 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 few are covered in detail below, because they 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 that reads 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

The info attribute 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

The manager attribute holds a distinguished name, not a display name, such as CN=Jane Doe,OU=Managers,OU=Users,DC=example,DC=com. So Set-ADUser jsmith -Manager "Jane Doe" doesn’t work. Pass the manager’s DN instead:

powershell

$manager = Get-ADUser -Identity jdoe
Set-ADUser -Identity jsmith -Manager $manager.DistinguishedName

directReports is a back-link, so you don’t set it. Active Directory computes it from everyone whose manager attribute points at that user, and trying to write to it fails.

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

Three things trip people up here. First, memberOf is a back-link, computed from the member attribute on each group, so you add a user to a group by modifying the group, not the user.

Second, memberOf doesn’t include the primary group. By default every user’s primary group is Domain Users (primaryGroupID 513), and that membership doesn’t appear in memberOf. So this won’t list Domain Users, even though the user is a member:

powershell

Get-ADUser jsmith -Properties memberOf | Select-Object -ExpandProperty memberOf

Third, memberOf isn’t transitive. 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 in-chain matching rule:

powershell

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 identifierStays the same for the life of the object
objectSidSecurity identifierChanges on domain migration
sAMAccountNamePre-Windows 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 textAdded by the Exchange schema

If you need a stable identifier for a user across renames and moves, use objectGUID. The DN changes when an object moves 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 the GAL

proxyAddresses

This attribute is multi-valued, and the case of the prefix matters:

text

SMTP:john.smith@example.com
smtp:j.smith@example.com
smtp:jsmith@old-domain.com

The uppercase SMTP: marks the primary address, and lowercase smtp: entries are secondary aliases. There can be only one primary. Duplicate proxyAddresses across two objects are a frequent reason an object fails to synchronise to Microsoft Entra ID.

userAccountControl

This is a single integer holding a set of bit flags. It’s queried constantly, and it’s the attribute most often misunderstood. Microsoft documents the full list in Use the UserAccountControl flags to manipulate user account properties.

Common flag values

ValueFlagMeaning
2ACCOUNTDISABLEAccount is disabled
16LOCKOUTAccount is locked out (see note below)
32PASSWD_NOTREQDNo password required
64PASSWD_CANT_CHANGEUser can’t change password (see note below)
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, can’t be delegated
2097152USE_DES_KEY_ONLYDES encryption only
4194304DONT_REQUIRE_PREAUTHKerberos pre-authentication not required

Not every flag is one you’ll meet in practice. Some relate to features that are effectively obsolete, and in day-to-day administration the ones worth knowing are 2, 512, 65536 and 262144.

Two flags need a caution. LOCKOUT (16) isn’t reliably maintained in userAccountControl, because domain controllers report lockout state through the computed msDS-User-Account-Control-Computed attribute instead. In PowerShell, use the LockedOut property rather than testing bit 16. PASSWD_CANT_CHANGE (64) is an oddity in the other direction: despite appearing in the list, you can’t set it by modifying userAccountControl. 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, because a user with password-never-expires and smart-card-required has a value you won’t have anticipated. In PowerShell, use -band:

powershell

Get-ADUser -Filter 'userAccountControl -band 65536' -Properties userAccountControl |
    Select-Object Name, userAccountControl

In an LDAP filter, use the bitwise AND matching rule, LDAP_MATCHING_RULE_BIT_AND. This one finds disabled accounts:

text

(userAccountControl:1.2.840.113556.1.4.803:=2)

And this one finds enabled accounts whose password never expires:

text

(&(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:

powershell

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 133412345678901234. Convert it with

[datetime]::FromFileTime(133412345678901234)

, or for an attribute read directly:

powershell

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 the current time (write-only)
0 for lastLogonNever logged on to this DC

Both 0 and 9223372036854775807 mean “never” for accountExpires, and which one an account holds depends on how it was last set. A new account normally starts at 9223372036854775807, while resetting an expiry back to Never in ADUC writes 0. So a script that checks only for 0 will miss accounts that have never had an expiry set. Check for both.

Also note that

[datetime]::FromFileTime(0)

returns 1 January 1601 rather than an error, so check for zero before converting.

pwdLastSet isn’t a normal attribute

You can’t set pwdLastSet to an arbitrary date. The only values you can write are 0, which forces a password change at next logon, and -1, which sets it to the current time. Setting it to 0 and then 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 area of Active Directory.

AttributeReplicatedAccuracyWhere it lives
lastLogonNoExactEach DC separately
lastLogonTimestampYesUp to 14 days staleReplicated to all DCs
LastLogonDaten/aSame as lastLogonTimestampPowerShell only

lastLogon

This is updated on the DC that authenticated the user, and isn’t replicated, so every DC holds a different value. To get an accurate answer, query every DC and take the highest value:

powershell

$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

This is replicated, but deliberately imprecise. It’s only updated when the new logon is more than msDS-LogonTimeSyncInterval days newer than the stored value. That interval 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 isn’t an Active Directory attribute at all. It’s a property the PowerShell ActiveDirectory module constructs, holding lastLogonTimestamp converted to a DateTime. It’s easier to read, but it inherits the same imprecision.

Which to use

For finding genuinely stale accounts, lastLogonTimestamp or LastLogonDate is the right choice, because the imprecision doesn’t matter when your threshold is 90 or 180 days:

powershell

$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

To answer “did this user log in today?”, you need lastLogon queried across all DCs. 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 have to request it explicitly. It also has two special values that the conversion has to handle. For an account with password-never-expires set, it returns the maximum FILETIME value, which is beyond what .NET’s DateTime can represent, so FromFileTime throws an error rather than returning a date. For a user who must change their password at next logon, it returns 0. Handle both:

powershell

Get-ADUser jsmith -Properties msDS-UserPasswordExpiryTimeComputed |
    Select-Object Name, @{
        N = 'PasswordExpires'
        E = {
            $v = $_.'msDS-UserPasswordExpiryTimeComputed'
            if ($v -eq 0) { 'Must change at next logon' }
            elseif ($v -eq [long]::MaxValue) { 'Never' }
            else { [datetime]::FromFileTime($v) }
        }
    }

badPwdCount and badPasswordTime aren’t replicated either. For lockout investigations, query the PDC Emulator, which receives failed password attempts forwarded from other DCs:

powershell

$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, used for indexing
uSNCreatedUpdate sequence number at creation
uSNChangedUpdate sequence number at last change

whenCreated and whenChanged use the LDAP Generalized Time syntax rather than FILETIME, such as 20260923140530.0Z. PowerShell converts these automatically, so they appear as normal DateTime values.

For LDAP filters, objectCategory is the conventional and efficient way to select a type of object. objectClass is indexed on modern DCs, but it’s multi-valued, so the usual pattern combines the two:

text

(&(objectCategory=person)(objectClass=user))

Getting the full attribute list

To see everything on a single object, run Get-ADUser jsmith -Properties * | Format-List. To see only the populated attributes, fetch the user once and filter its properties:

powershell

$u = Get-ADUser jsmith -Properties *
$u.PSObject.Properties |
    Where-Object { $null -ne $_.Value -and "$($_.Value)" -ne '' } |
    Select-Object Name, Value

To see what the schema defines for user objects:

powershell

$schema = [DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()
$schema.FindClass('user').MandatoryProperties | Select-Object Name
$schema.FindClass('user').OptionalProperties   | Select-Object Name

Microsoft’s All Active Directory schema attributes reference covers the syntax, indexing and replication behaviour of every attribute.

Common mistakes with LDAP attributes

These are the mistakes that come up most often when scripting against these attributes:

  • Assuming Get-ADUser returns everything. By default it returns a small default set, and anything else needs -Properties, as the Get-ADUser documentation explains.
  • Treating memberOf as complete. It excludes the primary group and doesn’t show nested membership.
  • Writing to directReports or memberOf. Both are back-links, computed by AD.
  • Setting manager to a display name. It needs a distinguished name.
  • Converting a FILETIME without checking for special values. Zero returns 1601, and the maximum value throws an error.
  • Using lastLogonTimestamp for precise activity. It can be up to 14 days stale by design.
  • Testing userAccountControl for equality. Use -band or the bitwise LDAP matching rule.
  • Relying on the LOCKOUT bit. Use the LockedOut property instead.
  • Setting co without c and countryCode. That produces an inconsistent record.
  • Trusting badPwdCount on any DC. It isn’t replicated, so query the PDC Emulator.

LDAP attribute scripting checklist

  1. Request every attribute you need with -Properties, rather than assuming Get-ADUser returns it.
  2. Identify whether each attribute is single-valued, multi-valued, a back-link or computed before you script against it.
  3. Use objectGUID, not the DN or sAMAccountName, as a stable identifier.
  4. Set manager with a distinguished name, and change group membership on the group, not the user.
  5. Use the in-chain matching rule when you need nested group membership.
  6. Test userAccountControl flags with -band or the bitwise matching rule, and change them with the dedicated cmdlets.
  7. Check FILETIME values for 0 and the maximum value before converting them.
  8. Treat both 0 and 9223372036854775807 as “never” for accountExpires.
  9. Use LastLogonDate for stale-account reports, and lastLogon across all DCs for precise activity.
  10. Query the PDC Emulator for badPwdCount and lockout details.
  11. Set c, co and countryCode together.

Microsoft references

Leave a Reply

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