Solved

Visual Basic Code to Get AD Details & Validate Input

Posted on 2014-11-13
13
248 Views
Last Modified: 2014-11-20
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.

Dialog Box
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

0
Comment
Question by:bjblackmore
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 7
  • 6
13 Comments
 

Author Comment

by:bjblackmore
ID: 40439902
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

0
 

Author Comment

by:bjblackmore
ID: 40440043
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

0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 40442129
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
0
Online Training Solution

Drastically shorten your training time with WalkMe's advanced online training solution that Guides your trainees to action. Forget about retraining and skyrocket knowledge retention rates.

 

Author Comment

by:bjblackmore
ID: 40442188
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

0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 40442263
Have you imported System.DirectoryServices? And have you added a reference to System.DirectoryServices to your project?

Chris
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 40442266
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
0
 

Author Comment

by:bjblackmore
ID: 40442298
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.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 40442318
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
0
 

Author Comment

by:bjblackmore
ID: 40442485
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

0
 

Author Comment

by:bjblackmore
ID: 40442733
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

0
 
LVL 71

Accepted Solution

by:
Chris Dent earned 500 total points
ID: 40442748
Looks much better :)

You know though, you can do this:
If Not searchResults Is Nothing Then
  Dim dirEntryResults as DirectoryEntry = searchResults.GetDirectoryEntry()
  ' ...
End If

Open in new window

This is especially useful if you went to lengths to authenticate your DirectorySearcher, you won't have to re-authenticate when it gets to dirEntryResults.

It's perhaps worth noting at this time that if that PIN is sensitive you should review how you're storing it. By default Active Directory allows all authenticated users read access, so anyone in your directory could (at least in theory) grab the PIN numbers for every user.

Chris
0
 

Author Comment

by:bjblackmore
ID: 40455453
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.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 40455957
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
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Recently, Microsoft released a best-practice guide for securing Active Directory. It's a whopping 300+ pages long. Those of us tasked with securing our company’s databases and systems would, ideally, have time to devote to learning the ins and outs…
After seeing many questions for JRNL_WRAP_ERROR for replication failure, I thought it would be useful to write this article.
Show developers how to use a criteria form to limit the data that appears on an Access report. It is a common requirement that users can specify the criteria for a report at runtime. The easiest way to accomplish this is using a criteria form that a…
Are you ready to implement Active Directory best practices without reading 300+ pages? You're in luck. In this webinar hosted by Skyport Systems, you gain insight into Microsoft's latest comprehensive guide, with tips on the best and easiest way…

728 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question