Finding the ‘LastLogon’ Date from all Domain Controllers with PowerShell

In an Active Directory environment, probably the most reliable way to query the last logon time of a computer is to use the Last-Logon attribute.  The Last-Logon-Timestamp attribute could be used, but this will not likely be up-to-date due to the replication lag.  If you are using PowerShell, the LastLogonDate attribute can also be used, however this is also a replicated attribute which suffers from the same delay and potential inaccuracy.

The Last-Logon attribute is not replicated, however, it is only stored on the DC that the computer authenticated against.  If you have multiple domain controllers, you will get multiple values for this attribute depending on which DC the computer has authenticated with and when.

To find the Last-Logon date from the DC that the computer has most recently authenticated with, you need to query all domain controllers for this attribute, then select the most recent.

Following is a PowerShell script I wrote that will read a list of domain controllers from an Active Directory OU, query each one, then return the most recent Last-Logon value.  It uses parallel processing to return the result more quickly than processing each DC in turn, which is useful in a multi-DC environment.

To use the script, simply pass the computer name and optionally the AD OU containing your domain controllers, to the function.  You can hard-code the ‘DomainControllersOU’ parameter in the script if you prefer, so you don’t need to call it.  You need the Active Directory module installed to use this.

Example


Get-ADLastLogon -ComputerName PC001 -DomainControllersOU "OU=Domain Controllers,DC=contoso,DC=com"

capture

Get-ADLastLogon


function Get-ADLastLogon {

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [string]$DomainControllersOU = "OU=Domain Controllers,DC=contoso,DC=com",

        [string]
        $ComputerName
    )

    # Multithreading function
    function Invoke-InParallel {
        [CmdletBinding()]
        param(
            [parameter(Mandatory = $True,ValueFromPipeline=$true,Position = 0)]
            $InputObject,
            [parameter(Mandatory = $True)]
            [ScriptBlock]$Scriptblock,
            [string]$ComputerName,
            [parameter()]
            $ThrottleLimit = 32,
            [parameter()]
            [switch]$ShowProgress
        )

        Begin
        {
            # Create runspacepool, add code and parameters and invoke Powershell
                [void][runspacefactory]::CreateRunspacePool()
                $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
                $script:RunspacePool = [runspacefactory]::CreateRunspacePool(1,$ThrottleLimit,$SessionState,$host)
                $RunspacePool.Open()

            # Function to start a runspace job
            function Start-RSJob
            {
                param(
                    [parameter(Mandatory = $True,Position = 0)]
                    [ScriptBlock]$Code,
                    [parameter()]
                    $Arguments
                )
                if ($RunspacePool.GetAvailableRunspaces() -eq 0)
                    {
                        do {}
                        Until ($RunspacePool.GetAvailableRunspaces() -ge 1)
                    }

                $PowerShell = [powershell]::Create()
                $PowerShell.runspacepool = $RunspacePool
                [void]$PowerShell.AddScript($Code)
                foreach ($Argument in $Arguments)
                {
                    [void]$PowerShell.AddArgument($Argument)
                }
                $job = $PowerShell.BeginInvoke()

                # Add the job and PS instance to the arraylist
                $temp = '' | Select-Object -Property PowerShell, Job
                $temp.PowerShell = $PowerShell
                $temp.Job = $job
                [void]$Runspaces.Add($temp)  

            }

        # Start a 'timer'
        $Start = Get-Date

        # Define an arraylist to add the runspaces to
        $script:Runspaces = New-Object -TypeName System.Collections.ArrayList
        }

        Process
        {
            # Start an RS job for each computer
            $InputObject | ForEach-Object -Process {
                Start-RSJob -Code $Scriptblock -Arguments $_, $ComputerName
            }
        }

        End
        {
            # Wait for each script to complete
            foreach ($item in $Runspaces)
            {
                do
                {
                }
                until ($item.Job.IsCompleted -eq 'True')
            }

            # Grab the output from each script, and dispose the runspaces
            $return = $Runspaces | ForEach-Object -Process {
                $_.powershell.EndInvoke($_.Job)
                $_.PowerShell.Dispose()
            }
            $Runspaces.clear()
            [void]$RunspacePool.Close()
            [void]$RunspacePool.Dispose

            # Stop the 'timer'
            $End = Get-Date
            $TimeTaken = [math]::Round(($End - $Start).TotalSeconds,2)

            # Return the results
            $return
        }
    }

    # Get list of domain controllers from OU
    try {
    Import-Module ActiveDirectory | out-null
    $DomainControllers = Get-ADComputer -Filter * -SearchBase $DomainControllersOU -Properties Name -ErrorAction Stop | Select -ExpandProperty Name | Sort
    }
    catch {}

    # Define Code to run in each parallel runspace
    $Code = {
        param($DC,$ComputerName)
        Import-Module ActiveDirectory | out-null
        $Date = [datetime]::FromFileTime((Get-ADComputer -Identity $ComputerName -Server $DC -Properties LastLogon | select -ExpandProperty LastLogon))
        $Result = '' | Select 'Domain Controller','Last Logon'
        $Result.'Domain Controller' = $DC
        $Result.'Last Logon' = $Date
        Return $Result
    }

    # Run code in parallel
    $Result = Invoke-InParallel -InputObject $DomainControllers -Scriptblock $Code -ComputerName $ComputerName -ThrottleLimit 64

    # Return most recent logon date
    return $Result | sort 'Last Logon' -Descending | select -First 1
}

 

7 thoughts on “Finding the ‘LastLogon’ Date from all Domain Controllers with PowerShell

  1. Hi,
    you script is very interesting but i try to run the script at win10 and get no output and no errors 😦

  2. Hi Trevor,
    yes i have. I have try many way to run the script. But i get no errors and no result.
    I have hard-code the ‘DomainControllersOU’ in the script and run the script with the machinename.
    Possible a idea for the next version from this script: Check not only one machine, check a ou and export a list with the data for the ou.

  3. I know this is an older post, but this is exactly what I was looking for. How can I run this for multiple machines and add the machine name to the results?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.