HighParts, LowParts, Low Points and (eventually) High Points.
So this morning I got in early to work and realised that a couple of application user accounts were locked out."I know" I thought I'll write a console app to flag these up to our Helpdesk and what's more I'll do it all before the rest of my team get in, in thirty minutes time.
Sounds too good to be true, yeah, of course it was...what an idiot! I probably could have achieved this if only I was a C# programmer, but alas not, it's VB.Net for me.
Here's What I Wanted
A console app to check the locked out status of each each within a certain Active Directory structure OU (named Application Accounts, snazzy eh?). An added bonus would be if I could tell when the account was locked out.Here's What Happened
As hoped the app took only a few minutes to write, except of course the 'added bonus' which has taken me the rest of the day.So Why The Delay?
To check whether an account is locked you retrieve the lockoutTime property for a directory entry. This 'large integer' value represents the nanoseconds between 1st January 1601 and the time the account was locked out.Large integers need to split into a HighPart and a LowPart using Reflection.
(Are you still with me? God knows this confused the hell out of me.)
All the examples I found were in C# and most included bitshifting '<<' which didn't seem to work for me.
(At this point my memory thought of '32 bits of a bus' in John Cleese's Compaq Advert.)
So I settled for copying the best bits (that is, the bits I understood) from many posts/articles and I cobbled them together giving the final result.
NOTE: I'd love to thank particular blogs, articles, people, but truth be told I've read so many articles today I wouldn't know which ones I took which bit from.
What Was The End Result?
An application that detects locked out users and emails someone else. Great, nothing more for me to do.Here's The Code
Imports System.DirectoryServices
Imports System.IO
Imports System.Net.Mail
Module Module1
Sub Main()
Dim results As SearchResultCollection
Dim srch As New DirectorySearcher()
srch.Filter = "objectClass=User"
srch.SearchRoot = New DirectoryEntry("LDAP://OU=Application Accounts,DC=XXXXX,DC=XXXXX,DC=XXXXX")
results = srch.FindAll
For Each result As SearchResult In results
TestLockedOut(result)
Next
srch.Dispose()
End Sub
''' <summary>
''' Test the account to see if locked out, if so send an email
''' </summary>
''' <param name="user">Active directory search result</param>
''' <remarks></remarks>
Private Sub TestLockedOut(ByVal user As SearchResult)
Dim strUser As String, intValue As Integer
strUser = user.GetDirectoryEntry.Properties("sAMAccountName").Value.ToString
intValue = CType(user.GetDirectoryEntry.Properties("userAccountControl").Value, Integer)
Console.Write(strUser)
Dim objLockout As Object = user.GetDirectoryEntry.Properties("lockoutTime").Value
If Not (objLockout Is Nothing) Then
Dim intLargeInteger As Int64 = GetLargeIntegerValue(objLockout)
If intLargeInteger > 0 Then
Console.Write(" locked out " & DateTime.FromFileTime(intLargeInteger))
SendEmail(strUser, DateTime.FromFileTime(intLargeInteger))
End If
Console.WriteLine(" ")
End If
End Sub
''' <summary>
''' Retrieve the value from the object and convert to an amount of nanoseconds since 1st Jan 1601
''' </summary>
''' <param name="largeInteger">The retrieved value from 'lockoutTime'</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function GetLargeIntegerValue(ByVal largeInteger As Object) As Int64
Dim type As System.Type = largeInteger.GetType()
Dim reflectionBinder As Reflection.Binder = System.Type.DefaultBinder
Dim args() As Object
Dim highPart As Integer = CType(type.InvokeMember("HighPart", Reflection.BindingFlags.GetProperty, reflectionBinder, largeInteger, args), Integer)
Dim lowPart As Integer = type.InvokeMember("LowPart", Reflection.BindingFlags.GetProperty, reflectionBinder, largeInteger, args) ', Integer)
Dim result As Long
result = CType(highPart * (2 ^ 32), Long) + (lowPart And &H7FFFFFFF)
If lowPart And &H80000000 Then result += (2 ^ 31)
Return result
End Function
Private Sub SendEmail(ByVal AccountName As String, ByVal LockedDateTime As DateTime)
' lots of help on the system.net.mail namespace is available here : http://www.systemnetmail.com/
' do you own thing to send a mail
End Sub
End Module