Reporting Active Directory Logons in PowerShell

October 30, 2012

Reporting on AD Logons has always been much more difficult than it should be. Before Windows 2003, there was no central log of logons at all. The lastLogon attribute on each user object would seem to do the trick; however, it only records the last logon time on the queried domain controller—it is not synchronized between DC’s. Even in Windows 2012, there the central logging attribute, lastLogonTimeStamp, is only completely replicated every 14 days.

graph1

Goal

So, if you want to find out who has logged today across the domain, or who has logged on in the last hour, you need to do some scripting work. The best tool to do that these days is PowerShell.

This first script tallies all users who log on within a 24 hour period and creates two csv files: one with all the users’ names and a second called DailyReport.csv. Each day it creates a new line in the latter csv file. After a number of days, you can make a pretty picture like the ones here.

PSLoggedOnToday.ps1

#!powershell.exe
# ======================================================================
# PSLoggedOnToday.ps1
# Logs a list of users who logged on during the current day
# Created by Matthew Newton
# 10.17.2012
#
# Queries each Domain Controller in the current domain and compiles a
# list of distinguishedName and lastLogon attributes for each user. Then
# it outputs only those users who have logged on during the current day
# (since midnight) as determined by the local computer.
# ======================================================================
#
# Heavily based on Richard L. Mueller's PSLastLogon.ps1.
# Link: http://www.rlmueller.net/PowerShell/PSLastLogon.txt
# And:  http://www.rlmueller.net/Last%20Logon.htm
#
# Richard's header is reproduced below:
#
# ----------------------------------------------------------------------
# Copyright (c) 2011 Richard L. Mueller
# Hilltop Lab web site - http://www.rlmueller.net
# Version 1.0 - March 16, 2011
#
# This program queries every Domain Controller in the domain to find the
# largest (latest) value of the lastLogon attribute for each user. The
# last logon dates for each user are converted into local time. The
# times are adjusted for daylight savings time, as presently configured.
#
# You have a royalty-free right to use, modify, reproduce, and
# distribute this script file in any way you find useful, provided that
# you agree that the copyright owner above has no warranty, obligations,
# or liability for such use.


Trap {"Error: $_"; Break;}

$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]"LDAP://$D"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 200
$Searcher.SearchScope = "subtree"

$Searcher.Filter = "(&(objectCategory=person)(objectClass=user))"
$Searcher.PropertiesToLoad.Add("distinguishedName") > $Null
$Searcher.PropertiesToLoad.Add("lastLogon") > $Null

# Create hash table of users and their last logon dates.
$arrUsers = @{}

# Enumerate all Domain Controllers.
ForEach ($DC In $D.DomainControllers)
{
    $Server = $DC.Name
    $Searcher.SearchRoot = "LDAP://$Server/" + $Domain.distinguishedName
    $Results = $Searcher.FindAll()
    ForEach ($Result In $Results)
    {
        $DN = $Result.Properties.Item("distinguishedName")
        $LL = $Result.Properties.Item("lastLogon")
        If ($LL.Count -eq 0)
        {
            $Last = [DateTime]0
        }
        Else
        {
            $Last = [DateTime]$LL.Item(0)
        }
        If ($Last -eq 0)
        {
            $LastLogon = $Last.AddYears(1600)
        }
        Else
        {
            $LastLogon = $Last.AddYears(1600).ToLocalTime()
        }
        If ($LastLogon -gt [DateTime]::Today)
  {

      If ($arrUsers.ContainsKey("$DN"))
            {
                If ($LastLogon -gt $arrUsers["$DN"])
                {
                    $arrUsers["$DN"] = $LastLogon
                }
            }
            Else
            {
                $arrUsers.Add("$DN", $LastLogon)
            }
  }
    }
}

# Output file names
$fileName = (Get-Date).ToString("yyyy-MM-dd") + "_" + $arrUsers.Count + ".csv"
$reportName = "DailyReport.csv"

# Output List of Users to CSV
&{ $arrUsers.GetEnumerator() | ForEach { New-Object PSObject -Property @{DN = $_.name; lastLogon = $_.value}} } | Export-CSV $fileName -NoType

# Append Total Count to CSV
$totalRow = "User Count: " + $arrUsers.Count
Add-Content $fileName $totalRow

# Output Time and User Count to report CSV
$reportRow = """" + (Get-Date).ToString() + """," + $arrUsers.Count
Add-Content $reportName $reportRow

Resulting report:

graph2

The second script is similar to the first. It tallies all users who log on within a 1 hour period and creates two csv files: one with all the users’ names and a second called HourlyReport.csv. Each hour it creates a new line in the latter csv file.

PSLoggedOnLastHour.ps1

#!powershell.exe
# ======================================================================
# PSLoggedOnLastHour.ps1
# Logs a list of users who logged on during the current day
# Created by Matthew Newton
# 10.17.2012
#
# Queries each Domain Controller in the current domain and compiles a
# list of distinguishedName and lastLogon attributes for each user. Then
# it outputs only those users who have logged on during the current hour.
#
# Heavily based on Richard L. Mueller's PSLastLogon.ps1.
# Link: http://www.rlmueller.net/PowerShell/PSLastLogon.txt
# And:  http://www.rlmueller.net/Last%20Logon.htm
#
# Richard's header is reproduced below:
#
# ----------------------------------------------------------------------
# Copyright (c) 2011 Richard L. Mueller
# Hilltop Lab web site - http://www.rlmueller.net
# Version 1.0 - March 16, 2011
#
# This program queries every Domain Controller in the domain to find the
# largest (latest) value of the lastLogon attribute for each user. The
# last logon dates for each user are converted into local time. The
# times are adjusted for daylight savings time, as presently configured.
#
# You have a royalty-free right to use, modify, reproduce, and
# distribute this script file in any way you find useful, provided that
# you agree that the copyright owner above has no warranty, obligations,
# or liability for such use.


Trap {"Error: $_"; Break;}

$oneHourAgo = (Get-Date).AddHours(-1)

$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]"LDAP://$D"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 200
$Searcher.SearchScope = "subtree"

$Searcher.Filter = "(&(objectCategory=person)(objectClass=user))"
$Searcher.PropertiesToLoad.Add("distinguishedName") > $Null
$Searcher.PropertiesToLoad.Add("lastLogon") > $Null

# Create hash table of users and their last logon dates.
$arrUsers = @{}

# Enumerate all Domain Controllers.
ForEach ($DC In $D.DomainControllers)
{
    $Server = $DC.Name
    $Searcher.SearchRoot = "LDAP://$Server/" + $Domain.distinguishedName
    $Results = $Searcher.FindAll()
    ForEach ($Result In $Results)
    {
        $DN = $Result.Properties.Item("distinguishedName")
        $LL = $Result.Properties.Item("lastLogon")
        If ($LL.Count -eq 0)
        {
            $Last = [DateTime]0
        }
        Else
        {
            $Last = [DateTime]$LL.Item(0)
        }
        If ($Last -eq 0)
        {
            $LastLogon = $Last.AddYears(1600)
        }
        Else
        {
            $LastLogon = $Last.AddYears(1600).ToLocalTime()
        }
        If ($LastLogon -gt $oneHourAgo)
  {

      If ($arrUsers.ContainsKey("$DN"))
            {
                If ($LastLogon -gt $arrUsers["$DN"])
                {
                    $arrUsers["$DN"] = $LastLogon
                }
            }
            Else
            {
                $arrUsers.Add("$DN", $LastLogon)
            }
  }
    }
}

# Output file names
$fileName   = (Get-Date).ToString("yyyy-MM-dd-hh-mm") + "_" + $arrUsers.Count + ".csv"
$reportName = "HourlyReport.csv"

# Output List of Users to CSV
&{ $arrUsers.GetEnumerator() | ForEach { New-Object PSObject -Property @{DN = $_.name; lastLogon = $_.value}} } | Export-CSV $fileName -NoType

# Append Total Count to CSV
$totalRow = "User Count: " + $arrUsers.Count
Add-Content $fileName $totalRow

# Output Time and User Count to report CSV
$reportRow = """" + (Get-Date).ToString() + """," + $arrUsers.Count
Add-Content $reportName $reportRow

Resulting report:

graph3

Enjoy.