Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Basic AD programming question with vb.Net

134 views
Skip to first unread message

Michael Barrett

unread,
Feb 16, 2004, 11:18:20 AM2/16/04
to
I have an application that needs certain features enabled for Admin users.
Instead of creating 2 apps, I wanted to enable features based on a group the
user belongs to. We run Win 2K with Active Directory.

I have never programmed Active Directory before. I have done some research
this morning but can't seem to find what I need. I found a basic example but
can't get it to run.

-----------------------------------------
Sub ADTest()

Dim entry As New DirectoryServices.DirectoryEntry(LDAP://<insert path>)
Dim mySearcher As New System.DirectoryServices.DirectorySearcher(entry)
Dim result As System.DirectoryServices.SearchResult

Try
mySearcher.Filter = ("user=mbarrett")
For Each result In mySearcher.FindAll()
Console.WriteLine(result.GetDirectoryEntry().Path)
Next

Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
-----------------------------------------

1 - I am looking for a certain user. What should my filter look like? Do I
use the user name or the person's real name

2 - LDAP Path -- What portions are required? Do I need the domain
controller?

All help truly appreciated.

Mike Barrett


Joe Kaplan (MVP - ADSI)

unread,
Feb 16, 2004, 12:02:44 PM2/16/04
to
There are a couple of constructed attributes that will help you determine
programmatically what rights the currently bound security context has on the
currently bound object. AllowedAttributesEffective tells you want
attributes you can modify and allowedChildClassesEffective tells you want
attributes you what object classes you can create as children of the current
object.

You generally need to do an explicit refresh cache to have them added to
your DirectoryEntry property cache:

entry.RefreshCache(New String() {"allowedAttributesEffective ",
"allowedChildClassesEffective"})

I don't know of any shortcuts that tell you if you can delete the current
object or its children. For that, i think you need to check the security
descriptor.

Regarding whether or not you need to specify a server name in your binding
string, that depends. If you are running under the security context of a
domain account, then ADSI will find a domain controller for you based on
that security context. If you always want to talk to a specific server,
then you should specify. Also, if you are running under an account that is
not in the domain you wish to operate against, you must supply a server name
and credentials.

For searches, you can search against any attributes in the directory. There
is a good set of information about building search filters in the AD
reference:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ad/ad/searching_active_directory.asp?frame=true

For the logon user name, you probably want to use:

(samAccountName=mbarrett)

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in
message news:efQQ$hK9DH...@TK2MSFTNGP12.phx.gbl...

Michael Barrett

unread,
Feb 16, 2004, 2:19:34 PM2/16/04
to
<snip>

Thanks.

This works:

Sub SomeTest()
Try
Dim entry As New
DirectoryServices.DirectoryEntry("LDAP://cn=Users, dc=ImpactRx, dc=com")


Dim mySearcher As New
System.DirectoryServices.DirectorySearcher(entry)
Dim result As System.DirectoryServices.SearchResult

mySearcher.Filter = "(samAccountName=mbarrett)"

For Each result In mySearcher.FindAll()

Console.WriteLine(result.GetDirectoryEntry.Path)
MsgBox(result.GetDirectoryEntry.Path)


Next
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub

In other words, I can validate that a user is a member of Users in the
Domain. However, when I try to go to another level and check to see if a
user is a member of a group (ex cn=Development), I get an error. It says
the object doesn't exist.

Again, all I want to know is if User X is a member of Group Y. Any ideas?

Thanks..

Mike


Joe Kaplan (MVP - ADSI)

unread,
Feb 16, 2004, 4:16:15 PM2/16/04
to
If you want to do security group tests, this is best done by comparing group
SIDs. There are a bunch of ways to do this, all varying from good to bad.

The best way is to let Windows do this for you. If you have the security
context of the user you want to check (i.e. the user whose access you are
checking is the current logged on user), then you can do this with the
WindowsPrincipal class. You basically call IsInRole on the WindowsPrincipal
class with the NT account name of the group (domain\groupname) and it will
return true or false. You can create a WindowsPrincipal from an
WindowsIdentity which you can bootstrap from the current security context by
calling the Shared method GetCurrent().

If you don't have the current security context of the user or need to look
up the group membership of a user from Active Directory, you have a couple
of choices. The best way that I'm aware of is to use the tokenGroups
attribute on the user. I've posted sample code here various times that
shows how to call this (try a google search for kaplan +tokenGroups). You
can either resolve the user's groups from SIDs into names, or resolve the
group you are looking for into a SID and compare SIDs for equality. There
is a Windows API for doing that, but you can also compare the byte arrays
that comprise the SIDs directly.

The downside of tokenGroups is that it doesn't really help with cross domain
or interforest group membership, and as I have recently learned, it doesn't
tell you groups you are a member of because the group contain Everyone or
Authenticated Users.

Finally, you can check the user's memberOf attribute to get the groups they
are a direct member of. This approach is the bad one in my opinion
(although you will find published MS samples demonstrating this) because it
doesn't include the primary group or nested group membership and can contain
non-security groups (DLs) as well as security groups. It also has the
downsides that tokenGroups has. It is easier to code than tokenGroups, but
that is it's only advantage.

I hope you can make use of the WindowsPrincipal class as it is by far the
easiest way to go and relies on platform services to do the hard work for
you.

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:%23tyARHM...@TK2MSFTNGP10.phx.gbl...

Dmitri Gavrilov [MSFT]

unread,
Feb 16, 2004, 4:27:33 PM2/16/04
to
There are also a couple more tokenGroups attributes that can be useful. One
is called tokenGroupsGlobalAndUniversal, and it will only return global and
universal group sids, i.e. you'll get the same answer no matter which DC you
are talking to. Another one is tokenGroupsNoGCAcceptable. This one will not
talk to GC to expand the group list (the other two will).

--
Dmitri Gavrilov
SDE, Active Directory Core

This posting is provided "AS IS" with no warranties, and confers no rights.
Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm

"Joe Kaplan (MVP - ADSI)" <joseph....@removethis.accenture.com> wrote
in message news:#evRgIN9...@tk2msftngp13.phx.gbl...

Michael Barrett

unread,
Feb 16, 2004, 5:17:36 PM2/16/04
to
Thank you very much. It sounds like we have a winner.

You were very helpful.

Mike Barrett


"Joe Kaplan (MVP - ADSI)" <joseph....@removethis.accenture.com> wrote

in message news:%23evRgIN...@tk2msftngp13.phx.gbl...

Joe Kaplan (MVP - ADSI)

unread,
Feb 16, 2004, 6:06:32 PM2/16/04
to
I always wondered exactly what those other two did. Thanks for the details.

Joe K.

"Dmitri Gavrilov [MSFT]" <dmi...@online.microsoft.com> wrote in message
news:OH2T6ON9...@TK2MSFTNGP11.phx.gbl...

Michael Barrett

unread,
Feb 17, 2004, 8:44:49 AM2/17/04
to
Joe/Dmitri:

WindowsPrincipal only seems to work for local groups such as Guest, Users,
Admin, etc. I can't find any other groups.

I am playing with tokenGroups now but am not having much success. I am
looking through past posts but haven't had much luck. If you get a chance,
I'd love to see a specific code example. If not, I'll keep hacking away.
Thanks.

This is driving me nuts. All I want to do is use a login name to determine
if the user is a member of group x. I never imagined I'd spend a day + on
it. :-(

Mike

Damon Allison

unread,
Feb 17, 2004, 9:18:02 AM2/17/04
to
Michael,

If its any consolation, I am frustrated also. The problem is I've been
working for 2 days on it, reading everything from ADSI SDK to MSDN
System.DirectoryServices to google searches for anything remotely close.

I am doing much the same thing - trying to get a list of groups a login name
belongs to. I have a function that works, but is very slow because I am
looping through all the objects in the AD root. The problem I am having is
I need to use both the WinNT and LDAP providers, thus I am having a problem
with the DirectorySearcher. I can do it using COM interop to ActiveDS.dll,
but I'd rather not use COM interop if I can avoid it. I'd rather not need
to create a dependency to something that should be provided in
System.DirectoryServices.

Good luck,
Damon


Private Function GetGroupsFromAD(ByVal UserName As String) As
StringCollection
UserName = UserName.ToLower()
Dim ret As New StringCollection
For Each de As DirectoryEntry In New
DirectoryEntry(AD_ROOT).Children
If de.SchemaClassName.ToLower() = "group" Then
Dim members As Object = de.Invoke("Members", Nothing)
For Each o As Object In CType(members, IEnumerable)
Dim child As DirectoryEntry = New DirectoryEntry(o)
If child.Name.ToLower() = UserName Then ret.Add(de.Name)
Next
End If
Next
Return ret
End Function

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:#Ukl3wV9...@tk2msftngp13.phx.gbl...

Carlos Magalhaes

unread,
Feb 17, 2004, 9:48:41 AM2/17/04
to
Message:
http://groups.yahoo.com/group/ADSIANDDirectoryServices/message/1422

Has some code that Joe posted on that group, it shows how to use the
tokenGroups..

If you have any issues let me know

Active Directory programming? -
http://groups.yahoo.com/group/adsianddirectoryservices
Carlos Magalhaes MVP

Michael Barrett

unread,
Feb 17, 2004, 9:51:57 AM2/17/04
to
Thanks.

The Function errors out on the the first line ("For Each de ...") and
returns and unspecified error. Maybe I am using the wrong AD root name? I am
guessing it is my company name or Users, right?

"Damon Allison" <d...@NOOSPAMdnradvertising.com> wrote in message
news:eaFJ0FW...@TK2MSFTNGP12.phx.gbl...

Michael Barrett

unread,
Feb 17, 2004, 10:23:32 AM2/17/04
to
All ---

Is there any reason the following fuction does not work? I have applied a
lot of what I have read and I believe this should work.

----------------------------------------------
Sub AD_Test()
Dim entry As New
DirectoryServices.DirectoryEntry("LDAP://cn=users,dc=ImpactRx,dc=com")


Dim mySearcher As New
System.DirectoryServices.DirectorySearcher(entry)
Dim result As System.DirectoryServices.SearchResult

mySearcher.Filter = "(samAccountName=mbarrett)"
mySearcher.PropertiesToLoad.Add("memberOf")

For Each result In mySearcher.FindAll()

If result.Properties("memberOf").Contains("Development") Then
MsgBox("Hit!")
Else
MsgBox("Miss!")
End If
Next
End Sub
---------------------------------------------------

Now, the user (mbarrett) is a member of Development. I have also tried <my
company name>\Development and other strings, and it won't validate me. It
does return a result and it goes into the For Each code and fires the Else
code.


Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 10:40:56 AM2/17/04
to
memberOf contains the distinguishedNames of the groups you are a member of,
not the account names, so you must use their fully qualified name. Like I
said before, it also has a lot of shortcomings.

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:%23ZCECoW...@TK2MSFTNGP10.phx.gbl...

Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 10:42:44 AM2/17/04
to
What did you try with WindowsPrincipal? It will definitely have all of your
domain groups in it if the account is a domain account. What did you try
that made you decide they weren't there?

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:%23Ukl3wV...@tk2msftngp13.phx.gbl...

Michael Barrett

unread,
Feb 17, 2004, 10:51:12 AM2/17/04
to

> What did you try with WindowsPrincipal? It will definitely have all of
your
> domain groups in it if the account is a domain account. What did you try
> that made you decide they weren't there?

Sub SomeTest()
Dim id As WindowsIdentity = WindowsIdentity.GetCurrent()
Dim wp As New WindowsPrincipal(id)

Try
MsgBox(wp.IsInRole("Development"))


Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub

-----------------
I also ran a loop. It returned all of the local groups. That function works
for all local groups. (Returns TRUE)


Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 10:51:32 AM2/17/04
to
Here is a repost of one of the samples I've posted here in the past as well.

A few points:
If you are comparing a single group, it is probably faster to look up the
SID of the group (get it objectSID attribute) and compare the binary value
to the array of binary SIDs in tokenGroups for the use. That way, you only
need to do 2 binds instead of N (1 for each group in tokenGroups).

TokenGroups Lookup:

Public Sub LookupSecurityGroups(userEntry as DirectoryEntry)


dim groupSids as PropertyValueCollection
dim binarySids as ArrayList
dim octetSid as String

'this property isn't loaded into the cache by default
userEntry.RefreshCache(New String() {"tokenGroups"})

groupSids = userEntry.Properties("tokenGroups")
binarySids = New ArrayList(groupSids.Count)

binarySids.AddRange(groupSids)


'Convert to string SIDs and lookup
For i = 0 To binarySids.Count - 1


binSid = DirectCast(binarySids(i), Byte())
'you have to convert the binary sid format into an octet string so
that you can bind with it
octetSid = ConvertToOctetString(binSid)
'this is the undocumented sid binding string for ADSI;
cool, huh?
groupPath = String.Format("<SID={0}>", octetSid)

'you will need to add your correct path and credentials
to the constructor here, but you get the idea...
Dim groupEntry as New DirectoryEntry(groupPath)

Try
'do something like this with the group once you have bound it
groupName =
groupEntry.Properties("samAccountName").Value.ToString()

Catch e as COMException

End Try

Next

End Sub

Public Overloads Shared Function ConvertToOctetString(ByVal values
As Byte()) As String
Return ConvertToOctetString(values, False, False)
End Function

Public Overloads Shared Function ConvertToOctetString(ByVal values
As Byte(), ByVal isAddBackslash As Boolean) As String
Return ConvertToOctetString(values, isAddBackslash, False)
End Function

Public Overloads Shared Function ConvertToOctetString(ByVal values
As Byte(), ByVal isAddBackslash As Boolean, ByVal isUpperCase As Boolean) As
String
Dim iterator As Integer
Dim builder As StringBuilder

Dim slash As String
If isAddBackslash Then
slash = "\"
Else
slash = String.Empty
End If

Dim formatCode As String
If isUpperCase Then
formatCode = "X2"
Else
formatCode = "x2"
End If
builder = New StringBuilder(values.Length * 2)
For iterator = 0 To values.Length - 1
builder.Append(slash)
builder.Append(values(iterator).ToString(formatCode))
Next

Return builder.ToString()

End Function

"Carlos Magalhaes" <car...@trencor.net> wrote in message
news:9ca430tk38e5ld6p1...@4ax.com...

Michael Barrett

unread,
Feb 17, 2004, 10:54:20 AM2/17/04
to

> What did you try with WindowsPrincipal? It will definitely have all of
your
> domain groups in it if the account is a domain account. What did you try
> that made you decide they weren't there?

This function lists all local groups only:

Public Shared Sub DemonstrateWindowsBuiltInRoleEnum()
Dim myDomain As AppDomain = Thread.GetDomain()
myDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal)
Dim myPrincipal As WindowsPrincipal = CType(Thread.CurrentPrincipal,
WindowsPrincipal)

Console.WriteLine("{0} belongs to: ",
myPrincipal.Identity.Name.ToString())

Dim wbirFields As Array =
[Enum].GetValues(GetType(WindowsBuiltInRole))
Dim roleName As Object
For Each roleName In wbirFields
Try

Console.WriteLine("{0}? {1}.", roleName, _
myPrincipal.IsInRole(CType(roleName,
WindowsBuiltInRole)))

Catch

Console.WriteLine("{0}: Could not obtain the role for this
RID.", roleName)

End Try
Next roleName

End Sub


Michael Barrett

unread,
Feb 17, 2004, 11:05:28 AM2/17/04
to
> > What did you try with WindowsPrincipal? It will definitely have all of
> your
> > domain groups in it if the account is a domain account. What did you
try
> > that made you decide they weren't there?


I just realized I limited my search to WindowsBuiltInRole. How do I go
outside the local machine?


Mike

Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 11:10:09 AM2/17/04
to
You have to prefix the group names with the domain name for
WindowsPrincipal:

domain\group name

Also, if you are using Framework 1.0, they are case sensitive. If you are
using 1.1, this bug is fixed. I point it out because it can be frustrating.

The annoying thing with WindowsPrincipal is that it is difficult to see the
actual list of groups for debugging purposes. There is a way to do this
with reflection if you need to though.

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:usV%23f3W9D...@TK2MSFTNGP09.phx.gbl...

Michael Barrett

unread,
Feb 17, 2004, 12:09:42 PM2/17/04
to
> You have to prefix the group names with the domain name for
> WindowsPrincipal:
>
> domain\group name

I did. I tried it every which way. I can't get it to recognize. I am also
using 1.1.

> Also, if you are using Framework 1.0, they are case sensitive. If you are
> using 1.1, this bug is fixed. I point it out because it can be
frustrating.
>
> The annoying thing with WindowsPrincipal is that it is difficult to see
the
> actual list of groups for debugging purposes. There is a way to do this
> with reflection if you need to though.

I need something! :-) I am going nuts with this.

Mike


Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 12:10:08 PM2/17/04
to
You need to use the overload that takes a string.

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:eE9Ce$W9DHA...@TK2MSFTNGP11.phx.gbl...

Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 12:49:29 PM2/17/04
to
Here is a link to the reflection code to get the actual list of roles as an
array:

http://groups.google.com/groups?selm=uRRZz3wgDHA.524%40tk2msftngp13.phx.gbl&oe=UTF-8&output=gplain

You need to import System.Reflection most likely.

Note that if the current user isn't a domain user, then you definitely won't
have the domain groups for that user. However, if they are, you should.
You should be able to tell if the current identity is a domain user by
looking at the Name property on WindowsIdentity.

Sorry this is such a pain.

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:em%23GXjX9...@TK2MSFTNGP09.phx.gbl...

Michael Barrett

unread,
Feb 17, 2004, 1:13:14 PM2/17/04
to
> You need to use the overload that takes a string.

Where? What method?

Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 1:50:14 PM2/17/04
to
WindowsPrincipal has three versions of IsInRole:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemSecurityPrincipalWindowsPrincipalClassIsInRoleTopic.asp?frame=true

The one that takes a string is the one I was talking about (the one that is
part of the IPrincipal interface). For example:

Dim currentPrincipal as New WindowsPrincipal(WindowsIdentity.GetCurrent())
Dim isAdmin as Boolean
isAdmin = currentPrincipal.IsInRole("mydomain\Administrators")

The others allow you to check the built-in accounts or to check group
membership by RID. The RID is last sub-authority in the SID (don't worry
about this if you don't understand; you don't need to know this).

Here is the reflection code to see the roles in the WindowsPrincipal.
DISCLAIMER: You should never ever use this in production code, only for
testing! Reflecting on private members is a no-no. MS may change the
implementation at any time and breaking encapsulation is not a good idea.

'Imports System.Reflection

Dim id As WindowsIdentity = WindowsIdentity.GetCurrent()

Dim idType As Type
idType = GetType(WindowsIdentity)
Dim result As Object = idType.InvokeMember( _
"_GetRoles", _
BindingFlags.Static Or BindingFlags.InvokeMethod Or
BindingFlags.NonPublic, _
Nothing, _
id, _
New Object() {id.Token}, _
Nothing _
)
Dim roles() As String = DirectCast(result, String())

HTH,

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:esox2GY9...@TK2MSFTNGP11.phx.gbl...

Michael Barrett

unread,
Feb 17, 2004, 1:50:18 PM2/17/04
to
> Here is a link to the reflection code to get the actual list of roles as
an
> array:
>
>
http://groups.google.com/groups?selm=uRRZz3wgDHA.524%40tk2msftngp13.phx.gbl&oe=UTF-8&output=gplain
>
> You need to import System.Reflection most likely.

Okay, I got it to work.

Next question -- how do I specify a user based on login to the app and not
the CurrentUser on the machine?

Thanks again.


Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 2:52:05 PM2/17/04
to
The right way to do that is to simply use the RunAs command from Windows
(either command line or through the shell). If you run the program itself
as a different user, then all the Windows security stuff inside will just
work.

However, if this isn't an option, you have more trouble. You can either try
to do what RunAs does, which is essentially a call to CreateProcessAsUser,
or you can try to reinvent the the entire Windows security stack in your own
code. I'd recommend against the latter. To call CreateProcessAsUser, you
need to first call LogonUser to get the token you need to pass to
CreateProcessAsUser. There are probably P/Invoke samples somewhere on the
net that show how to do this. Hopefully you can avoid it.

HTH,

Joe K.

"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:uQPZlbY9...@TK2MSFTNGP11.phx.gbl...

Michael Barrett

unread,
Feb 17, 2004, 3:34:08 PM2/17/04
to
> The right way to do that is to simply use the RunAs command from Windows
> (either command line or through the shell). If you run the program itself
> as a different user, then all the Windows security stuff inside will just
> work.

The users will run the program. I just wanted to test it as other users. If
that isn't possible, I will work it out.

Thanks.


Mike


Joe Kaplan (MVP - ADSI)

unread,
Feb 17, 2004, 4:04:10 PM2/17/04
to
For testing, use RunAs. You could probably even set up VS.NET to do this
for you by setting the build action to run an external program, and have
that run RunAs with the command line arguments pointing to your executable
in the bin directory. You may need to attach the debugger manually though.

Glad to hear you are making progress and don't have to try to look up the
group membership.

Joe K.


"Michael Barrett" <mchaelfbarrett@remthis_yahoo_thistoo.com> wrote in

message news:ebIEmVZ9...@TK2MSFTNGP10.phx.gbl...

0 new messages