Wednesday, September 08, 2010

Powershell script to get Active Directory LastLogon

As you probably know the Active Directory LastLogon attribute is not synchronized between servers so you need to query each server in the domain to find out the last logon of a user. A while back I knocked up this little function:

Function Get-LastLogon {
param(
  $Username = $(throw "Mandatory parameter -Username is missing")
)
$rootDse = [adsi]"LDAP://RootDSE"
$configNamingContext = $rootDse.psbase.properties.configurationnamingcontext[0]
if($username.ToLower() -notcontains "dc="){
  $defaultNamingContext = $rootDse.psbase.properties.defaultNamingContext[0]
  $searcher = new-object DirectoryServices.DirectorySearcher $([ADSI]"LDAP://$defaultNamingContext"),"(samaccountname=$username)",$([string[]]"distinguishedName")
  $result = $searcher.findall()
  if($result.Count -ne 1){ throw "Username not found"
  }
  $userDN = $result[0].properties.distinguishedname[0]
} else {
  if($username -match "^LDAP://"){
    $userDN = $([adsi]$username).psbase.distinguishedName[0]
  }
  $userDN = $username
}
$lastLogonObj = "" | select UserDN,ServerNameLastLogonMap
$lastLogonObj.UserDN = $userDN
$confSearcher = new-object DirectoryServices.DirectorySearcher $([ADSI]"LDAP://$configNamingContext"),"(objectclass=ntdsdsa)"
$lastLogonObj.ServerNameLastLogonMap = $confSearcher.FindAll() | % {
  $record = "" | select ServerName,LastLogon
  $serverDN = $_.GetDirectoryEntry().psbase.parent.psbase.properties.serverReference[0]
  $serverDnsName = $([adsi]"LDAP://$serverDN").psbase.properties.dnshostname[0]
  write-progress -Activity "Reading LastLogon value for $userdn from" -Status "server $serverDnsName"
  $record.Servername = $serverDnsName
  if(($([ADSI]"LDAP://$serverDnsName/$userDN").psbase.properties.LastLogon.count) -eq 1){
    $adsLargeInteger = $([ADSI]"LDAP://$serverDnsName/$userDN").psbase.properties.LastLogon[0]     $highPart = $adsLargeInteger.GetType().InvokeMember("HighPart",[System.Reflection.BindingFlags]::GetProperty,$null, $adsLargeInteger, $null)
    $lowPart = $adsLargeInteger.GetType().InvokeMember("LowPart",[System.Reflection.BindingFlags]::GetProperty,$null, $adsLargeInteger, $null)
    $bytes = [System.BitConverter]::GetBytes($highPart)
    $tmp = [System.Byte[]]@(0,0,0,0,0,0,0,0)
    [System.Array]::Copy($bytes, 0, $tmp, 4, 4)
    $highPart = [System.BitConverter]::ToInt64($tmp, 0)
    $bytes = [System.BitConverter]::GetBytes($lowPart)
    $lowPart = [System.BitConverter]::ToUInt32($bytes, 0)
    $value = ($lowPart + $highPart)
    $record.LastLogon = [datetime]::FromFileTimeUTC($value)
  }
  $record
}
$getDomainLastLogon = {$this.ServerNameLastLogonMap| ? {$_.Lastlogon -ne $null}|sort -Property LastLogon -Descending |select -first 1}

$lastLogonObj | add-member -MemberType ScriptProperty -Name DomainLastLogon -Value $getDomainLastLogon -PassThru

}

Run it as Get-LastLogon samaccountname or Get-LastLogon distinguishedname. This will return a object with three properties UserDN, SererNameLastLogonMap which holds all the DC's LastLogon value for the user and DomainLastLogon which holds the latest LastLogon value in the domain.


Please enjoy!

1 comment:

  1. Nice script, but I got this error:

    Unexpected token 'highPart' in expression or statement.
    At C:\temp\ad\lastlogonsamaccount.ps1:30 char:109
    + $adsLargeInteger = $([ADSI]"LDAP://$serverDnsName/$userDN").psbase.properties.LastLogon[0] $highPart <<<< =
    $adsLargeInteger.GetType().InvokeMember("HighPart",[System.Reflection.BindingFlags]::GetProperty,$null, $adsLargeIntege
    r, $null)
    + CategoryInfo : ParserError: (highPart:String) [], ParseException
    + FullyQualifiedErrorId : UnexpectedToken

    ReplyDelete