Link to home
Start Free TrialLog in
Avatar of bjblackmore
bjblackmore

asked on

Visual Basic Code to Get AD Details & Validate Input

I need our users to supply a 4 -6 digit PIN number, which will be stored in their account in AD (as customAttribute2), so if/when they phone support, the helpdesk/support tech can ask them for the PIN as a method of authentication.

I'd like to program a small visual basic application that we can roll out to the user's desktop, or get to launch once when the user logons, that will ask them to set the PIN number.

I've just downloaded Visual Studio 2012, and have started programing a small application. I've done some VB Script programming and some very basic visual basic programming a few years ago, however I enjoy the challenge & learning. So far I've build the basic form, with the button's and text/input boxes.

User generated image
I've got the cancel/exit button, and have set the input box to number only. But I'm having trouble getting it to query Active Directory. I wondered if anyone had some example snippets of code that I could use/modify.

I need code that will get the current logged on user's Windows ID, which will be show in the text box by the 'Hello'.
I also need code that will query CustomAttribute2 for an existing PIN, which will be shown by Current PIN:
Finally code that will update CustomAttribute2 with a new PIN when 'OK' is pressent.

Although I've set the Max length to 6, I'd also like to set a Mix length to 4, but there doesn't seem to be a Min length property.

Code I have so far:
Allow number only:
    Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress
        If Not Char.IsDigit(e.KeyChar) And Not Char.IsControl(e.KeyChar) Then
            e.Handled = True
        End If
    End Sub

Open in new window


Get Logged on Windows Username (doesn't work/needs modifying):
   
 Function GetUserName() As String
        If TypeOf My.User.CurrentPrincipal Is 
          Security.Principal.WindowsPrincipal Then
            Dim parts() As String = Split(My.User.Name, "\")
            Dim username As String = parts(1)
            Return username
        Else
            ' The application is using custom authentication.
            Return My.User.Name
        End If
    End Function

Open in new window

Avatar of bjblackmore
bjblackmore

ASKER

Think I have the username bit:
        Dim currentADUser As System.DirectoryServices.AccountManagement.UserPrincipal
        currentADUser = System.DirectoryServices.AccountManagement.UserPrincipal.Current
        Dim DisplayName As String = currentADUser.GivenName & " " & currentADUser.Surname
        Label2.Text = "Hello " & DisplayName

Open in new window

Have managed to find code to validate minimum 4 numbers:
 Private Sub EnterPIN_Leave(sender As Object, e As EventArgs) Handles EnterPIN.Leave
        If EnterPIN.Text.Length < 4 Then
            MsgBox("Please enter 4 - 6 numbers")
        End If
    End Sub

Open in new window

Avatar of Chris Dent
Have you thought about validating the input using a regular expression? A simple regular expression like \d{4,6} would let you determine whether or not the user had entered 4 to 6 ({4,6} digits (\d). It's more expensive than a length check, but would stop someone entering "  123" or "1a2b3c" and so on.

Chris
Thanks for the reply.
The code I have above works near perfectly for the input validation. It will only allow you to enter 0-9, alpha characters just won't work, which is fine.

My main stumbling block currently is the AD data push/pull.

I've found the below, but I keep getting errors on DirectoryEntry & DirectorySearcher not being defined.

        Dim ADEntry As New DirectoryEntry(OUDistinguishedName)
        Dim ADSearch As New DirectorySearcher(ADEntry)
        ADSearch.Filter = ("(&(objectClass=user)(sAMAccountName=ADName))")
        ADSearch.SearchScope = System.DirectoryServices.SearchScope.OneLevel
        ADSearch.PropertiesToLoad.Add("sAMAccountName")
        ADSearch.PropertiesToLoad.Add("extensionAttribute1")
        Dim ADSearchResult As SearchResultCollection = ADSearch.FindAll()
        If ADSearchResult Is Nothing Then
            Return Nothing
        Else
            Return ADSearchResult
        End If

Open in new window

Have you imported System.DirectoryServices? And have you added a reference to System.DirectoryServices to your project?

Chris
Oh and since you're using FindAll(), if your directory is larger than 1000 users you will need to enable Paging on the query:

ADSearch.PageSize = 1000

That will allow it to exceed the 1000 limit (returns multiple pages, 1000 per page where 1000 is the maximum supported by Active Directory).

Chris
yes, I've added reference 'System.Directoryservices.AccountManagement' & 'System.DirectoryServices'. I wasn't sure if I needed both. The first worked on it's own for getting the logged in username. But when I got the errors in the new AD code, I also added 'System.Directoryservices'.

I don't really need to use FindAll() thinking about it. As I'm searching for the samAccountName of the user, of which there should/can only be one in AD.
Then FindOne() would be good for you for sure. That'll make:
Dim ADSearchResult As SearchResult = ADSearch.FindOne()

Open in new window

Once you have that you'll be able to address the properties of the search result without resorting to a loop.

If you're still getting errors about instantiating DirectoryEntry it implies "Imports System.DirectoryServices" has been missed.

Chris
The references were added, but not imported into the name space. Seems to get less erorrs now.

However, 1 error that still exists on the below is line 8 (Dim dirEntryResults As New DirectoryEntry(results.Path)). I get
'results' is not declared. It may be inaccessible due to its protection level.

    Public Sub UpdateUserADAccount(ByVal ADName As String, ByVal EnterPIN As String)
        Dim dirEntry As DirectoryEntry = GetDirectoryEntry()
        Dim dirSearcher As DirectorySearcher = New DirectorySearcher(dirEntry)
        dirSearcher.Filter = "(&(objectCategory=Person)(objectClass=user) (SAMAccountName=ben.blackmore)"
        dirSearcher.SearchScope = SearchScope.Subtree
        Dim searchResults As SearchResult = dirSearcher.FindOne()
        If Not searchResults Is Nothing Then
            Dim dirEntryResults As New DirectoryEntry(results.Path)
            SetADProperty(dirEntryResults, "extensionAttribute1", EnterPIN)
            dirEntryResults.CommitChanges()
            dirEntryResults.Close()
        End If
        dirEntry.Close()
    End Sub

Open in new window

Think I fixed that. Should be

Dim dirEntryResults As New DirectoryEntry(searchResults.Path)
not
Dim dirEntryResults As New DirectoryEntry(results.Path)

        Dim EnterPIN As String
        Dim dirEntry As DirectoryEntry = GetDirectoryEntry()
        Dim dirSearcher As DirectorySearcher = New DirectorySearcher(dirEntry)
        dirSearcher.Filter = "(&(objectClass=user)(objectCategory=person)(sAMAccountName=" & ADName & "))"
        dirSearcher.SearchScope = SearchScope.Subtree
        Dim searchResults As SearchResult = dirSearcher.FindOne()
        If Not searchResults Is Nothing Then
            Dim dirEntryResults As New DirectoryEntry(searchResults.Path)
            SetADProperty(dirEntryResults, "extensionAttribute3", EnterPIN)
            dirEntryResults.CommitChanges()
            dirEntryResults.Close()
        End If
        dirEntry.Close()

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Apologies for my delay in replying. Hectic week. All seems to be working now. I can pull the username & attribute from AD, display it, and update it.

The PIN isn't really sensative. Just a way of being able to quickly authenticate users when they call the service desk. They'd have to have an AD account to be able to read the attribute. Plus know where to look, and what that attribute was for. And none of our users actually know how to query AD anyway, so it should be OK.
Should it become a problem later I did think of one way around the security problem. Obviously you have no need to use it, but it's a point of interest (at least for me :)).

If you were to generate a SHA1 (or even MD5) hash from the PIN, perhaps with a bit of salt, you could easily obscure the value in AD.

That is, if the user enters "1234" and you add some salt, let's say the string "PIN obscured by bjblackmore" to make "1234 PIN obscured by bjblackmore" you get this SHA1 hash:

97635531f0d33f81d69281b9f2939f2d77e49293

Hashing is one way, so short of trying every combination (and figuring out the salt) it wouldn't be easy to get back to the PIN (not impossible, just not easy).

Whatever app the service desk uses would need to compare the hash based on the user PIN (and the salt only the code knows about, or knows how to generate / acquire) and tell them good or bad. If the hash generated by the service desk matches the hash in AD the PIN must match and it's all good.

Chris