inactive users in AD

michael lustig 356 Reputation points
2023-04-17T07:03:16+00:00

Hi everyone I would like to knew what the differentiate between 2 commands: 1)Search-ADAccount –AccountInActive -UsersOnly –TimeSpan 90:00:00:00 –ResultPageSize 2000 –ResultSetSize $null |?{$.Enabled –eq $True} | Select-Object Name, SamAccountName, DistinguishedName| Export-CSV “C:\Temp\InactiveUsers.CSV” –NoTypeInformation $date= (get-date).AddDays(-90)   2)Get-ADUser-Filter {LastLogonDate-lt $date} -Property Enabled|Where-Object {$.Enabled -like “true”} |SelectName,SamAccountName,DistinguishedName|Export-CSV “C:\Temp\InactiveUsers.CSV” -NoTypeInformation Because, It output differnt results/informations. Thanks

Windows for business Windows Client for IT Pros Directory services Active Directory
Windows for business Windows Server User experience PowerShell
0 comments No comments
{count} votes

Accepted answer
  1. Rich Matheisen 47,901 Reputation points
    2023-04-27T15:07:13.8933333+00:00

    I posted a corrected version of my code on 11 Apr. Did you see it? Did you try using it?

    Assuming you haven't, here's the corrected script:

    $date = (Get-Date).AddDays(-90) 
    $fdate = $date.tofiletime()
    
    $DCs = (Get-ADDomainController -Filter *).Name
    $users = @{}
    ForEach ($dc in $DCs) {
        Get-ADUser -Filter { enabled -eq 'true' -AND ((-not ( LastLogon -like "*")) -OR (LastLogon -lt $fdate)) } -Server $dc -Property LastLogon |
            ForEach-Object {
                if ($users.ContainsKey($_.sAMAccountname)) {
                        if ($users.($_.sAMAccountname)[0] -lt $_.LastLogon) {
                            $users.($_.sAMAccountname)[0] = $_.LastLogon
                        }
                    }
                    else {
                        $users[$_.sAMAccountname] = $_.LastLogon, $_.Name, $_.distinguishedName
                    }
                }
            }
    $users.GetEnumerator()|
        ForEach-Object{
            if ($null -ne $_.Value[0] -AND $_.Value[0] -gt 0){
                $last = [DateTime]::FromFileTime($_.Value[0])
            } 
            else {
                $last = 'Never'
            }
            [PSCustomObject]@{
                Name = $_.Value[1]
                SamAccountName = $_.Key
                DistinguishedName = $_.Value[2]
                LastLogon = $last
            }
        } | Export-CSV "C:\Temp\InactiveUsers.CSV" -NoTypeInformation
    
    0 comments No comments

4 additional answers

Sort by: Most helpful
  1. Rich Matheisen 47,901 Reputation points
    2023-04-17T15:09:20.88+00:00

    How are the two results different?

    Do you have more than one Domain Controller?

    1. The LastLogonDate is convenient way to use the LastLogontimestamp (which is a 64-bit integer that needs converting) -- but while that property is replicated between DCs, it's only replicated at a random interval between 14 and 21(?) days. Unless you examine every DC in the users' domain you have a good chance of using an obsolete value.
    2. The LastLogon property is updated only on the DC that handled the logon. Again, you have to examine every DC in the users' domain to get the most recent one.

  2. Rich Matheisen 47,901 Reputation points
    2023-04-18T15:19:59.1066667+00:00

    You're using the LastLogonDate in a filter. LastLogonDate is a calculated property that doesn't exist in the AD.

    If you want to filter on the time the user last logged on you'll have to convert the variable $date to a FILE date/time and use the property LastLogonTimeStamp.

    But that leaves you a problem: if the LastLogonTimeStamp is empty (because the user never logged on) you can't use in in a comparison. So you have to check for the absence of a value (which is probably what the Search-ADAccount is doing).

    $date= (get-date).AddDays(-90) 
    $fdate = $date.tofiletime()
    Get-ADUser -Filter {(-not ( LastLogonTimeStamp -like "*")) -OR (LastLogonTimeStamp -lt $fdate)} -Property Enabled|
        Where-Object {$_.Enabled -like "true"} |
            Select-Object Name,SamAccountName,DistinguishedName |
                Export-CSV "C:\Temp\InactiveUsers.CSV" -NoTypeInformation
    
    

    Also, you should add the "enabled" test to the filter. Why return disabled users if you're going to eliminate them??


  3. Rich Matheisen 47,901 Reputation points
    2023-04-23T15:11:36.1333333+00:00

    I don't think you ever confirmed whether you have more than on domain controller. Do you? If you do, try running this:

    $date = (Get-Date).AddDays(-90) 
    $fdate = $date.tofiletime()
    
    $DCs = (Get-ADDomainController -Filter *).Name
    $users = @{}
    ForEach ($dc in $DCs) {
        Get-ADUser -Filter { enabled -eq 'true' -AND ((-not ( LastLogon -like "*")) -OR (LastLogon -lt $fdate)) } -Server $dc -Property LastLogon |
            ForEach-Object {
                if ($users.ContainsKey($_.sAMAccountname)) {
                        if ($users.($_.sAMAccountname)[0] -lt $_.LastLogon) {
                            $users.($_.sAMAccountname)[0] = $_.LastLogon
                        }
                    }
                    else {
                        $users[$_.sAMAccountname] = $_.LastLogon, $_.Name, $_.distinguishedName
                    }
                }
            }
    $users.GetEnumerator()|
        ForEach-Object{
            if ($null -ne $_.Value[0] -AND $_.Value[0] -gt 0){
                $last = [DateTime]::FromFileTime($_.Value[0])
            } 
            else {
                $last = 'Never'
            }
            [PSCustomObject]@{
                Name = $_.Value[1]
                SamAccountName = $_.Key
                DistinguishedName = $_.Value[2]
                LastLogon = $last
            }
        } | Export-CSV "C:\Temp\InactiveUsers.CSV" -NoTypeInformation
    

    EDIT: I posted the original (not at all correct) script. The answer now contains the working script.


  4. Gary Nebbett 6,216 Reputation points
    2023-04-24T08:30:23.25+00:00

    Hello All,

    There may be several strands of questions and answers intermingled in this thread. Here is my attempt to untangle some of them.

    The various PowerShell cmdlets in the ActiveDirectory module use and combine lower-level APIs to present a simplified and scripting-friendly interface to Active Directory Domain Services. Three ways of comparing/differentiating apparently "similar" cmdlets are:

    1. Read and compare the documentation carefully.
    2. Reverse engineer the cmdlets and understand at the implemenation-level their differences.
    3. Perform several tests of the cmdlets and speculate about the differences.

    Each attribute/property in AD/LDAP has a SystemFlags attribute in the schema which indicates, among other things, whether the attribute/property is replicated or constructed.

    Most attributes are replicated but "lastLogon" is one of the few which are not.

    Some attributes are "constructed" (not saved explicitly in the directory but are constructed/calculated by the LDAP server); "canonicalName" and "tokenGroups" are examples of such attributes. I mention this because "LastLogonDate" has been called a calculated attribute, but this "calculation" is purely internal to the PowerShell cmdlet (just an alternative "presentation" of the "lastLogonTimestamp" attribute).

    There is another attribute/property that might be helpful in this case: msDS-LastSuccessfulInteractiveLogonTime.

    The LDAP interactions between client and server can be traced with ETW (Event Tracing for Windows) by using the Microsoft-Windows-LDAP-Client provider. Some registry settings are needed to get the most out of this provider but even without them some useful summary information is available. Such traces could help understand differences in results between PowerShell cmdlets; for each search issued, the following is logged:

    1 ScopeOfSearch = 2 
    2 SearchFilter = (anr=gary) 
    3 DistinguishedName = dc=home,dc=org 
    4 AttributeList = lastLogonTimestamp;msDS-LastSuccessfulInteractiveLogonTime 
    5 ProcessId = 0x6CC 
    
    

    Gary


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.