Grab list of ActiveDirectory groups a user belongs to using VB.NET

Hi there.

On my corporate Intranet, we grab the user's username and store it in a variable.  Our system is on ActiveDirectory.

Is there a way for me, with VB.NET code, to grab a list of the AD groups that a user belongs to?

Thanks,
Steve
LVL 1
HeitmanProgrammersAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Fred GoodwinVP of Software DevelopmentCommented:

usr is system.environment.username
you will need to change the domain in the line
Dim entry As New System.DirectoryServices.DirectoryEntry("WinNT://domain.net/" & usr)


This will return an arraylist with all of the groups in it.


Public Function ADGetAllGroups(ByVal usr As String) As ArrayList

        Dim XRay As New ArrayList

        Dim entry As New System.DirectoryServices.DirectoryEntry("WinNT://domain.net/" & usr)
        Dim obGroups As IEnumerable = DirectCast(entry.Invoke("Groups"), IEnumerable)
        Dim obGpEntry As System.DirectoryServices.DirectoryEntry
        Dim ob As Object

        For Each ob In obGroups
            obGpEntry = New System.DirectoryServices.DirectoryEntry(ob)
            XRay.Add(obGpEntry.Name)
        Next
        XRay.TrimToSize()
       Return XRay
    End Function

Hope it helps
TorrwinCommented:
Here is the function that I use, it returns the groups in a pipe delimited string:

Imports System.Text
Imports System.Collections
Imports System.DirectoryServices
Dim _Path As String = "LDAP://YOUR_PATH"

Public Function GetGroups() As String
        Dim search As New DirectorySearcher(_path)
        search.Filter = "(cn=" + _filterAttribute + ")"
        search.PropertiesToLoad.Add("memberOf")
        Dim groupnames As New StringBuilder()
        Try
            Dim result As SearchResult = search.FindOne()
            Dim propertyCount, equalsIndex, commaIndex As Integer
            propertyCount = result.Properties("memberOf").Count
            Dim dn As String

            Dim propertyCounter As Integer = 0
            While (propertyCounter < propertyCount)

                dn = (result.Properties("memberOf")(propertyCounter)).ToString

                equalsIndex = dn.IndexOf("=", 1)
                commaIndex = dn.IndexOf(",", 1)
                If (equalsIndex = -1) Then
                    Return ""
                End If
                groupnames.Append(dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1))
                groupnames.Append("|")

                propertyCounter += 1
            End While

        Catch
            Throw New Exception("Error obtaining group names. " + Err.Description)
        End Try
        Return groupnames.ToString()
    End Function
HeitmanProgrammersAuthor Commented:
Thanks!

When I run that initially, I get an error on the line:

Dim obGroups as IEnumerable = DirectCast(entry.Invoke("Groups"), IEnumerable)

The error is, "Access is denied."

Does my domain administrator need to grant access to the AspNet user?  How do I communicate this to him?

Thanks,
Steve
OWASP: Forgery and Phishing

Learn the techniques to avoid forgery and phishing attacks and the types of attacks an application or network may face.

HeitmanProgrammersAuthor Commented:
My comment above was for imsolost's code.  Torrwin, I'll try yours momentarily.
Steve
HeitmanProgrammersAuthor Commented:
Torrwin, what's the "filterattribute"?  
HeitmanProgrammersAuthor Commented:
Torrwin, when I try your method, I get, "Error obtaining group names. The specified domain either does not exist or could not be contacted."

I guess I'm not sure how to point to our domain.  I know it's called "HEITMAN" but that's it.  I've tried LDAP:\\HEITMAN\ and WinNT:\\HEITMAN, but neither work.
Fred GoodwinVP of Software DevelopmentCommented:
You may need to import
Imports System.DirectoryServices
Imports System.Collections.Specialized
Imports System.Web.UI

You may check with the admin but that should be open for you to call
Fred GoodwinVP of Software DevelopmentCommented:
the WinNT will be a fill domain name.  so it will be HEITMAN.com  You can check with your sysadmin they should be able to give you the full domain name
HeitmanProgrammersAuthor Commented:
Hmm.

When I hit Control-Alt-Delete, it says I'm logged on as HEITMAN\gadlins

So I don't think we have the .COM in our domain.

So do I use LDAP or something?  What's the difference?  I think the error is that I'm unable to point to the domain properly.

I'll go ask my admin what the path should be.
Fred GoodwinVP of Software DevelopmentCommented:
My guess is that HEITMANwhen run through DNS is really HEITMAN.domain.com or something like that.  Your admin should be able to tell you though
HeitmanProgrammersAuthor Commented:
Should I prepend that with LDAP:\\ or WinNT:\\ ?

What's the difference?
raterusCommented:
Do you want all groups?  Using memberOf, will not get all the groups, only groups the user is directly added to.  Nested groups will not be retrieved, that is "userA is a member of groupA, groupA is in groupB", memberOf will NOT return groupB as a group for userA.  If this is could be a problem, I'd rethink your strategy to use tokenGroups.
TorrwinCommented:
Heitman,

I'm sorry, the "_filterattribute" variable is just a string containing your username.  So, it gets the groups based on your username.

Here is an example of a path:
string _Path = "LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com"

HeitmanProgrammersAuthor Commented:
Hmm.

This is frustrating, as my available admins can't really answer my question.

I'm pretty sure that the string_Path is the line that's giving me the error now.  All I know is that my domain is called "Heitman."

What are some possible values for that path, then?  I'll just try a bunch until one works.
ihenryCommented:
>> So do I use LDAP or something?  What's the difference? ...
You should really be using LDAP provider for active directory or WinNT provider for NT4.0 domain. There're some features in LDAP provider that do not exist in WinNT provider.

>> What are some possible values for that path, then?...
Essentially, an LDAP path would have the following format:

LDAP://HostName[:PortNumber][/DistinguishedName]

Where HostName can be the IP address, the server name or the domain name of your AD machine. PortNumber determines a port that should be used for the connection, if you don't specify the PortNumber, it is set to 389 for non SSL or 636 for SSL connection. And DistinguishedName is used when you want to bind to a specific object.

If you're running the code in an intranet web application with IIS Integrated Windows Authentication is enabled and impersonation in web.config is set to true, you can just ignore the HostName and PortNumber then binding to AD serverlessly and anonymously. Anonymously here means, DirectoryServices uses whatever security context of the calling thread in the binding process.

Dim ldapPath As String =   "LDAP://DC=xxx,DC=xxx"
Dim entry As New DirectoryEntry( ldapPath )
Dim searcher As New DirectorySearcher()
searcher.SearchRoot = entry
searcher.Filter = string.Format( "(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))", logonUserName )
...
...

The next step is you should start to think is that what group membership you want to be in the list. memberOf attribute can do a good job to retrieve all group types including security, universal, distribution group. But you won't be seeing primary group or indirect group membership in the attribute (as @Raterus has mentioned this very clearly) and you need another way to achieve this.

NOTE: in IWA scenario, another problem you might encounter is delegation issue. So make sure all participants in the binding process, e.g. IIS, user account, AD and client browser have delegation set to enabled.


HTH
Henry
HeitmanProgrammersAuthor Commented:
Wow, that's a lot of helpful information.  I'll pore over this over today, and award expert points tomorrow.  Thanks, everyone, I really appreciate your help.
HeitmanProgrammersAuthor Commented:
Maybe I spoke too soon.

My web server is set up to only allow Windows Integrated Authentication, but impersonation is turned OFF.

Here's the string I'm trying to use to connect:

Dim _Path As String = "LDAP://HEITMAN:389"

I've also replaced HEITMAN with HEITMAN.COM and with the IP address of our AD server, still no luck.

I'm using Torrwin's code from above (without the try), and the line I get an error on is this:

Dim result As SearchResult = search.FindOne()

The error I get is "The specified domain either does not exist or could not be contacted."

I'm pretty certain the domain exists, so what might be some reasons that it can not be contacted?
ihenryCommented:
I think you need to have some more talks to your sys admin about your problem. Ok, if you really need to do it yourself, try to execute nslookup.exe utility from DOS command and see what domain name returns. And also try to ping the domain name and see if it can echo back the AD server ip address.
HeitmanProgrammersAuthor Commented:
My main tech fella is on vacation, unfortunately.

I executed nslookup.exe, and it returns the Nameserver machine.  I try adding that IP and server name to the LDAP address, but get the same error.

Here's the exact code I'm using now... can you see any other reason other than the Dim _Path line that would make me get that error?

Public Function GetGroups() As String
        Dim _Path As String = "LDAP://199.196.161.114"
        Dim search As New DirectorySearcher(_Path)
        search.Filter = "(cn=" + "gadlins" + ")"
        search.PropertiesToLoad.Add("memberOf")
        Dim groupnames As New StringBuilder

        Dim result As SearchResult = search.FindOne()
        Dim propertyCount, equalsIndex, commaIndex As Integer
        propertyCount = result.Properties("memberOf").Count
        Dim dn As String

        Dim propertyCounter As Integer = 0
        While (propertyCounter < propertyCount)

            dn = (result.Properties("memberOf")(propertyCounter)).ToString

            equalsIndex = dn.IndexOf("=", 1)
            commaIndex = dn.IndexOf(",", 1)
            If (equalsIndex = -1) Then
                Return ""
            End If
            groupnames.Append(dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1))
            groupnames.Append("|")

            propertyCounter += 1
        End While

        Return groupnames.ToString()
    End Function


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        TextBox1.Text = GetGroups()
    End Sub
ihenryCommented:
Your path..
 Dim _Path As String = "LDAP://199.196.161.114"

Do you know the default distinguishedName of your AD domain, no? use this code

    Private Shared Function GetDnsHostFromRootDSE() As String
        Dim root As New DirectoryEntry
        root.Path = String.Format("LDAP://rootDSE")
        Try
            Return DirectCast(root.Properties("defaultNamingContext").Value, String)
        Catch
            Throw
        Finally
            root.Close()
            root.Dispose()
        End Try
    End Function

 Dim _Path As String = "LDAP://199.196.161.114/" + GetDnsHostFromRootDSE()
ihenryCommented:
And you should really not using this query filter
search.Filter = "(cn=" + "gadlins" + ")"

cn attribute is unique only in an OU not in a domain or a forest and the filter above have a serious performance problem as DirectoryServices will need to crawl almost object by object for any match cn attribute value.
To search for a user account based on its logon name use the filter I posted above.
HeitmanProgrammersAuthor Commented:
Our domain is named HEITMAN, so I changed it (along with the right IP) to this:

Dim _Path As String = "LDAP://199.196.184.34/HEITMAN"

I tried your code, but it kicks me out at the line

THROW

And says "The specified domain either does not exist or could not be contacted"
ihenryCommented:
It's wrong, can you please try the code I posted above. If failed, try to bind using explicit domain user credentials. Like so

    Private Shared Function GetDnsHostFromRootDSE() As String
        Dim root As New DirectoryEntry
        root.Path = String.Format("LDAP://rootDSE")
        root.Username = "domain\user"
        root.Password = "P@ssw0rd"
        Try
            Return DirectCast(root.Properties("defaultNamingContext").Value, String)
        Catch
            Throw
        Finally
            root.Close()
            root.Dispose()
        End Try
    End Function

Use it like this

Dim ldapPath As String = "LDAP://" + GetDnsHostFromRootDSE()
HeitmanProgrammersAuthor Commented:
I am using this code:

    Private Shared Function GetDnsHostFromRootDSE() As String
        Dim root As New DirectoryEntry
        root.Path = String.Format("LDAP://rootDSE")
        root.Username = "heitman\gadlins"
        root.Password = "xxxxxxxx"
        Try
            Return DirectCast(root.Properties("defaultNamingContext").Value, String)
        Catch
            Throw
        Finally
            root.Close()
            root.Dispose()
        End Try
    End Function

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim ldapPath As String = "LDAP://" + GetDnsHostFromRootDSE()
        TextBox1.Text = ldapPath
    End Sub

I get an error on the line that says "Throw", with "The specified domain either does not exist or could not be contacted "
ihenryCommented:
Can you telnet 199.196.184.34 389 ?
HeitmanProgrammersAuthor Commented:
It does not appear so.
ihenryCommented:
What did it say?
HeitmanProgrammersAuthor Commented:
Well, the screen goes blank, and the cursor blinks in the top left corner for a while.  Then it quits back to DOS.
ihenryCommented:
Ok, that works.
You're running the code in a trusted domain, I don't see why you cannot use serverless binding.
If you use this in the GetDnsHostFromRootDSE function?

root.Path = "LDAP://199.196.184.34/rootDSE"
HeitmanProgrammersAuthor Commented:
Now we're on to something...

that returns:

DC=heitman,DC=com
ihenryCommented:
gosh..use this path

"LDAP://199.196.184.34/DC=heitman,DC=com"

And you need again to pass explicitly the user credentials in the code, because without impersonation your code is running under aspnet or network services local user account which does not have privilege to access your AD server.
ihenryCommented:
Or either of these paths should also works

"LDAP://heitman/DC=heitman,DC=com"

or

"LDAP://heitman.com/DC=heitman,DC=com"
ihenryCommented:
If you think memberOf attribute is good enought for you, here is the code snipet to play around with

Dim de As New DirectoryEntry()
de.Path = "LDAP://199.196.184.34/DC=heitman,DC=com"
de.Username = "domain\user"
de.Password = "password"

Dim searcher As New DirectorySearcher(de)
searcher.Filter = String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))", logonName)
searcher.PropertiesToLoad.AddRange( new String() { "memberOf" } )

Dim results As SearchResultCollection
Try
    results = searcher.FindAll()
    If (results.Count > 0) Then
      Dim result As SearchResult = results(0)
      For Each role As String in result.Properties("memberOf")
            Console.WriteLine( "Role distinguishedName: {0}" , role )
      Next
    End If
Catch ex As Exception
    Throw ex
Finally
    If (Not results Is Nothing) Then
      results.Dispose()
    End If
    searcher.Dispose()
End Try

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
HeitmanProgrammersAuthor Commented:
Ok, that last code snippet gives me no errors, but doesn't write anything to the web page.

How do I gather, then, a list of the groups that this person is a member of?
HeitmanProgrammersAuthor Commented:
Nevermind, I think I've got this now.

Thanks for your help, everyone!
HeitmanProgrammersAuthor Commented:
Hmm.

Using this method, is it possible to determine whether or not a user has read access to a specific file on the network?
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
ASP.NET

From novice to tech pro — start learning today.