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 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 |
| 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 few are covered in detail below, because they 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 that reads 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 |
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 field | LDAP attribute | Notes |
|---|---|---|
| Job Title | title | |
| Department | department | |
| Company | company | |
| Manager | manager | Distinguished name |
| Direct reports | directReports | Computed, 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.DistinguishedNamedirectReports 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 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
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 memberOfThird, 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
| Attribute | Contains | Notes |
|---|---|---|
| distinguishedName | Full DN | Changes if the object moves |
| objectGUID | Unique identifier | Stays the same for the life of the object |
| objectSid | Security identifier | Changes on domain migration |
| sAMAccountName | Pre-Windows 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 | Added 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
| Attribute | Contains |
|---|---|
| Primary SMTP address as displayed | |
| proxyAddresses | All addresses, multi-valued |
| mailNickname | Exchange alias |
| targetAddress | Forwarding address for mail-enabled users |
| msExchHideFromAddressLists | Hidden 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.comThe 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
| Value | Flag | Meaning |
|---|---|---|
| 2 | ACCOUNTDISABLE | Account is disabled |
| 16 | LOCKOUT | Account is locked out (see note below) |
| 32 | PASSWD_NOTREQD | No password required |
| 64 | PASSWD_CANT_CHANGE | User can’t change password (see note below) |
| 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, can’t be delegated |
| 2097152 | USE_DES_KEY_ONLY | DES encryption only |
| 4194304 | DONT_REQUIRE_PREAUTH | Kerberos 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:
| 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, 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, userAccountControlIn 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 $trueSetting 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 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
| 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 the current time (write-only) |
| 0 for lastLogon | Never 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.
| Attribute | Replicated | Accuracy | Where it lives |
|---|---|---|---|
| lastLogon | No | Exact | Each DC separately |
| lastLogonTimestamp | Yes | Up to 14 days stale | Replicated to all DCs |
| LastLogonDate | n/a | Same as lastLogonTimestamp | PowerShell 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 -DescendinglastLogonTimestamp
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 LastLogonDateTo 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
| 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 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 $pdcObject metadata
| Attribute | Contains |
|---|---|
| whenCreated | Object creation time |
| whenChanged | Last modification time |
| objectClass | Object type hierarchy |
| objectCategory | Simplified type, used for indexing |
| uSNCreated | Update sequence number at creation |
| uSNChanged | Update 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, ValueTo 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 NameMicrosoft’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
-bandor the bitwise LDAP matching rule. - Relying on the LOCKOUT bit. Use the
LockedOutproperty 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
- Request every attribute you need with
-Properties, rather than assuming Get-ADUser returns it. - Identify whether each attribute is single-valued, multi-valued, a back-link or computed before you script against it.
- Use objectGUID, not the DN or sAMAccountName, as a stable identifier.
- Set manager with a distinguished name, and change group membership on the group, not the user.
- Use the in-chain matching rule when you need nested group membership.
- Test userAccountControl flags with
-bandor the bitwise matching rule, and change them with the dedicated cmdlets. - Check FILETIME values for 0 and the maximum value before converting them.
- Treat both 0 and 9223372036854775807 as “never” for accountExpires.
- Use LastLogonDate for stale-account reports, and lastLogon across all DCs for precise activity.
- Query the PDC Emulator for badPwdCount and lockout details.
- Set c, co and countryCode together.
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.

