I have been spending a lot of time working on Powershell scripts. Specifically, I have been re-writing many VB scripts and processes into more efficient powershell versions. In my last post I mentioned that I planed to share how I made a task that took 8 hours to execute run in only 28 minutes. There are two parts to this efficiency — Start-Job and Group-Object. In a future post I’ll write more about the Start-Job functionality, but the greatest benefit was from Group-Object — and thats what I’ll focus on in this post.
The task I re-wrote looked at about 50,000 user accounts and collected many details about the accounts for auditing purposes. Due to some constraints, I needed to use the lastLogon attribute instead of lastLogonTimeStamp. You can read more about the constraints here: http://www.rlmueller.net/Last%20Logon.htm, but this line pretty much sums it up: Because the lastLogon attribute is not replicated in Active Directory, a different value can be stored in the copy of Active Directory on each Domain Controller. In VB script, I was using an LDAP bind to each domain controller for each user account and then evaluated the lastLogon attribute, which was very inefficient.
Here is the powershell version of this code, which is much more efficient and flexible (as you can get the last login time from each/all domain controllers very easy).
[cc lang=”powershell”]
$lastLogons = @()
$domainControllers | %{ # I used an LDAP query for computers with primaryGroupID=516 to get a list of domain controllers distinguished names
$objDC = New-Object DirectoryServices.DirectoryEntry “LDAP://$_”
$dcName = $objDC.dnsHostName.ToString()
$de = New-Object System.DirectoryServices.DirectoryEntry (“LDAP://$dcName”)
$Rech = New-Object System.DirectoryServices.DirectorySearcher($de)
$Rech.filter = “(&(objectCategory=User)(!objectClass=Computer)(lastLogon>=1))”
$Rech.SearchScope = “subtree”
$Rech.sizelimit = “90000”
$Rech.pagesize = “90000”
$Rech.PropertiesToLoad.Add(“distinguishedName”);
$Rech.PropertiesToLoad.Add(“lastLogon”);
$liste = $Rech.FindAll()
$lastLogons += ($liste | select @{n=’DN’;e={$_.properties.distinguishedname}},@{n=’LastLogon’;e={$_.properties.lastlogon}}, @{n=’DC’;e={$dcName}})
}
$groupedLastLogin = $lastLogons | Group-Object -Property DN -AsHashTable -AsString
# To get an individuals last login information, just select it from the hash table
$trueLastLogin = $groupedLastLogin.Item($objUser.distinguishedName.ToString()) | sort LastLogon -Descending | select -First 1
$lastDC = $trueLastLogin.DC
$lastLogonTrueDate = [datetime]::FromFileTimeUTC($trueLastLogin.lastLogon)
[/cc]
After creating this code, I attempted to select user information from the $lastLogons variable. This was very slow — even slower than the previous VB script that did excessive LDAP binds. The efficiency in this script was gained by grouping the objects into a hash table ($groupedLastLogin) by distinguished name, and then accessing the specific key value as needed.
I know several of my posts have strayed from the normal VMware/vSphere/PowerCLI topics recently, but I have some plans to get back on track early next year. Please stay tuned!
