🏢Active DirectoryAccounts

AD Account Expiration Review: Decide Before Extending Users

Review AD account expiration dates, confirm owners and dependencies, and record authorized end dates before extending users.

Younes AZABARBy Younes AZABAR10 min read
AD Account Expiration Review: Decide Before Extending Users

AD Account Expiration Review: Decide Before the Deadline

An AD account expiration review turns upcoming access end dates into decisions that someone owns. An expiring account may belong to a contractor whose engagement is ending, an employee with temporary access, or a user identity used by an application. The immediate question is whether the planned end date is still correct. Extending every account that appears on a report defeats the purpose of assigning an end date.

A review needs the expiration instant, the accountable owner, the continuing business need and the dependencies that must be retired or transferred. It should cover all relevant user populations. Privileged membership affects the urgency and the rights to remove, but it is not a prerequisite for reviewing an upcoming deadline.

An account approaching expiration is not evidence of compromise. Account expiration is also separate from password expiration: these are different account properties and different operational problems. This guide proposes a review workflow; it does not report results from a live domain or prescribe a universal renewal policy.

How accountExpires Represents an End Date

The AD attribute accountExpires stores Windows FILETIME: intervals of 100 nanoseconds counted from January 1, 1601 UTC. Microsoft defines two values for an account that never expires: 0 and 9223372036854775807, also written 0x7FFFFFFFFFFFFFFF. Neither value is a future deadline to put on an expiration calendar. Microsoft Learn: Account-Expires attribute

Keep the stored instant distinct from a business instruction such as "access ends Friday." Ask whether the owner means the start of that day, the end of a shift, or a particular UTC instant. Record the agreed timezone and exact time before making a change. A date without that context can end access earlier than the owner intended.

There is an acknowledged overlap with our article on disabled, expired and locked accounts remaining in privileged groups. That article covers accounts already in an invalid state while privileged membership persists. This review acts before expiration, across all user populations, to assign the decision and coordinate the access changes. If the account is already expired, put it in the existing offboarding and membership review rather than treating it as an upcoming deadline.

The Kerberos ticket lifetime and connection containment guide explains why account state, issued tickets and authenticated connections require separate evidence. Use that distinction when planning an access end date. An ordinary expiration review is not an incident finding and does not justify assuming ticket theft.

Detection: Build an Expiration Inventory

1. Set the scope and use an explicit horizon

Start with the domain, directory server, search base and reference time. Record the review horizon as a local operational choice. The examples below require the ActiveDirectory PowerShell module and an authorized directory context. They are illustrative commands adapted from documentation, not commands executed against an AD environment. Replace every example server, DN and identity with verified values.

Microsoft documents Search-ADAccount -AccountExpiring with either -TimeSpan for a period from the current time or -DateTime for a deadline. -UsersOnly narrows the object population to users. The following example chooses a thirty-day reporting horizon; this choice is not a Microsoft requirement. Microsoft Learn: Search-ADAccount

$server = 'dc01.example.com'
$searchBase = 'OU=People,DC=example,DC=com'
Search-ADAccount -AccountExpiring -UsersOnly -TimeSpan '30.00:00:00' -Server $server -SearchBase $searchBase |
    Select-Object DistinguishedName, Name, Enabled, AccountExpirationDate

For a scheduled review, an absolute deadline can be easier to reproduce. This separate example constructs a UTC DateTime rather than relying on a locale-dependent date string:

$deadlineUtc = [datetime]::new(2026, 11, 3, 18, 0, 0, [DateTimeKind]::Utc)
Search-ADAccount -AccountExpiring -UsersOnly -DateTime $deadlineUtc -Server $server -SearchBase $searchBase |
    Select-Object DistinguishedName, Name, Enabled, AccountExpirationDate

Use one horizon form per query. Preserve the query time as well as its deadline: a relative query run later has a different window. These cmdlet searches are investigation inputs, not a claimed reproduction of EtcSec's strict boundary comparisons. Inspect the returned dates and enabled state before assigning action.

2. Read the raw attribute and normalize dates

Retrieve accountExpires explicitly with Get-ADUser -Properties. Microsoft documents additional LDAP attributes through this parameter, along with -SearchBase and -Server for selecting the directory context. Microsoft Learn: Get-ADUser

This illustrative inventory keeps the raw value and reports conversion status. It deliberately distinguishes an unavailable or invalid value from a documented never-expiring sentinel:

Get-ADUser -Filter * -Server $server -SearchBase $searchBase -Properties accountExpires,manager,description |
    ForEach-Object {
        $raw = $_.accountExpires
        $expiryUtc = $null
        $state = 'Missing or invalid'
        if ($null -ne $raw -and [string]$raw -ne '') {
            try {
                $value = [long]$raw
                if ($value -eq 0 -or $value -eq [long]::MaxValue) {
                    $state = 'Never expires'
                } else {
                    $expiryUtc = [datetime]::FromFileTimeUtc($value)
                    $state = 'Dated'
                }
            } catch {
                $state = 'Missing or invalid'
            }
        }
        [pscustomobject]@{
            DistinguishedName = $_.DistinguishedName
            Enabled = $_.Enabled
            RawAccountExpires = $raw
            ExpirationUtc = $expiryUtc
            DateState = $state
            Manager = $_.Manager
            Description = $_.Description
        }
    }

FromFileTimeUtc converts a file time to a UTC DateTime and rejects out-of-range input. The two never-expiring sentinels are handled before conversion. This example's error labels are proposed review labels, not output from the collector parser. Microsoft Learn: DateTime.FromFileTimeUtc

The directory's manager and description fields are leads for finding an owner, not proof that the named person authorized access. Confirm the accountable owner through the current contract, access request or service record. Leave unresolved ownership visible instead of using an empty field as permission to renew.

3. Turn dates into an actionable register

For each in-scope identity, preserve its stable identifier, raw attribute, normalized expiration, enabled state, review timestamp and directory source. Add the business end date, owner, dependencies, decision and change reference. Keep already expired accounts, never-expiring accounts and unreadable dates distinguishable from the upcoming-deadline queue.

Review itemEvidence to obtainDecision it supports
Expiration instantRaw attribute plus UTC and agreed business timezoneConfirm the intended deadline
Business ownerCurrent contract or access requestConfirm who may authorize continuation
Application dependencyService owner's inventory of jobs and integrationsTransfer or retire use before access ends
Access scopeCurrent rights and relevant membershipsAssign explicit removal tasks
ExceptionApproved reason, bounded new date and review ownerRecord a targeted extension

An application dependency should become a task for its service owner. It should not silently convert a time-limited identity into a permanent one. Decide how work will continue, who owns the replacement or transition, and what evidence closes that task.

Remediation: Confirm the End Date or Authorize a Bounded Extension

1. Decide before editing the account

Confirm the reference date and population first, then send each deadline to its owner. Ask whether the original end date remains valid and whether the user still needs the same access. Record the answer with its approval source. A missing response is an unresolved review, not an authorization to extend.

When access should end as planned, coordinate rights removal, membership review and application handover with the responsible teams. When work continues, require a new bounded end date and an authorized reason. Separate these two outcomes in the register so that a bulk renewal cannot be mistaken for completed remediation.

2. Apply only the approved date

Microsoft documents Set-ADAccountExpiration for setting an exact expiration time. Its -DateTime parameter assumes local time unless otherwise specified; omitting the time means midnight local time. Use the exact approved instant and record it explicitly. Microsoft Learn: Set-ADAccountExpiration

The following illustrative targeted extension uses a placeholder identity and UTC deadline. Run the preview first in an authorized environment. Apply the separate write only after checking that the identity and date match the recorded approval. These commands have not been executed here.

$identity = 'CN=Example User,OU=People,DC=example,DC=com'
$approvedExpiryUtc = [datetime]::new(2026, 12, 1, 18, 0, 0, [DateTimeKind]::Utc)
Set-ADAccountExpiration -Identity $identity -DateTime $approvedExpiryUtc -Server $server -WhatIf
Set-ADAccountExpiration -Identity $identity -DateTime $approvedExpiryUtc -Server $server

Do not feed the entire expiration report into this write. A query result establishes a date condition; it supplies no approval to continue access. Likewise, clearing expiration is a separate policy decision, not a routine fix for an upcoming-expiration finding.

3. Re-read and retain the decision

Read the same account from the chosen server after the change. Preserve the old and new raw values, normalized dates, server, timestamp, approval and change reference:

Get-ADUser -Identity $identity -Server $server -Properties accountExpires,AccountExpirationDate |
    Select-Object DistinguishedName, Enabled, accountExpires, AccountExpirationDate

Normalize the raw value with the inventory method and compare it with the approved UTC instant. This readback verifies the selected server's value; where the workflow depends on other domain controllers, arrange their verification through the directory team's process. A saved approval without a readback does not establish that the attribute changed as intended.

For planned departures, retain evidence that the assigned rights-removal and service-transition tasks completed. For extensions, schedule the next review around the new deadline. In both cases, distinguish the account attribute from tickets, connections and application access. The record should say what was verified and what remains with another owner.

How EtcSec Detects This

EtcSec's etc-collector implements ACCOUNT_EXPIRE_SOON in public/internal/audit/detectors/ad/accounts/status/account-expire-soon.go. Static inspection of Detect() shows the following logic; no customer audit was executed for this article:

  1. Take the reference time from data.Now.
  2. Calculate the upper boundary with now.AddDate(0, 0, 30).
  3. Skip any user whose Disabled flag is true.
  4. Skip any user whose parsed AccountExpires is the zero time.
  5. Select only dates strictly after the reference time and strictly before the upper boundary.

The lower and upper bounds are both excluded. Already expired accounts are excluded, as is an expiration exactly equal to the audit reference time. Count is the number of selected users, not the number of domains, approvals or missing offboarding tasks. The detector returns a Medium finding with that count; it attaches affected-user details only when IncludeDetails is enabled and the count is positive.

There is no privileged-group, service-account or secret-related condition in this check. It covers all collected users meeting the date and enabled-state conditions. The thirty-day window is a declared product choice, not a Microsoft, ANSSI or CIS requirement. The implementation uses calendar-date addition; retain the audit reference and boundaries when explaining a specific result.

The LDAP parser populates AccountExpires through parseFileTime in public/internal/providers/ldap/parser.go. Empty input, 0, the maximum signed 64-bit sentinel and integer-parsing errors become zero time without a per-value diagnostic in that helper. Values before the Unix-epoch conversion boundary also become zero. Consequently, a zero parsed date can represent either never-expiring data or unavailable/unusable input. The detector skips both.

Use ACCOUNT_EXPIRE_SOON to start the owner-and-deadline review. A positive count does not establish an attack, password expiration, an unauthorized extension or a need to renew. Close the review with a documented end-date decision, verified attribute state and separately owned access-removal or dependency tasks.

Explore the identity security pages that support this topic