Searching Active Directory for Members of Specific Group Fails When User Has No Group - vb.net

the following code is supposed to run through all users in active directory and find everyone in a specific group. Once it gets the users, it will add them to a datatable which will be the source of a gridview for a final export to Excel.
However, while running and stepping through, I've noticed it gets stopped on a certain user with no group. I tried adding a conditional statement to skip that instance, but it isn't working, the code still stops running when it finds the user with no group.
Can someone show me what I could be doing differently?
Caveat: This is from a legacy site that I inherited, knowing very little about active directory and being a novice vb coder.
Dim entry As DirectoryEntry = New DirectoryEntry("LDAP://DOMAIN.EDU", "USERNAME", "PASSWORD", AuthenticationTypes.Secure)
Dim search As DirectorySearcher = New DirectorySearcher(entry) With {
.Filter = "(&(objectCategory=User)(objectClass=person))",
.PageSize = 4000
}
search.PropertiesToLoad.Add("userPrincipalName").ToString
search.PropertiesToLoad.Add("name").ToString
Dim mySearchResultColl As SearchResultCollection = search.FindAll
Dim results As DataTable = New DataTable
results.Columns.Add("User ID")
results.Columns.Add("Full Name")
Dim CurrRow = 0
For Each sr As SearchResult In mySearchResultColl
Dim dr As DataRow = results.NewRow
Dim de As DirectoryEntry = sr.GetDirectoryEntry
!!!! line below is the problem !!!!
If de.Properties("memberOf") IsNot Nothing AndAlso de.Properties("memberOf").Value.ToString = "CN=MYGROUP,OU=Security Groups,OU=Students,DC=DOMAIN,DC=edu" Then
dr("User ID") = de.Properties("userPrincipalName").Value
dr("Full Name") = de.Properties("name").Value
results.Rows.Add(dr)
de.Close
End If
Next
gvNot.DataSource = results
gvNot.DataBind()
gvNot.Visible = True
gvAgreed.Visible = False
ExportToExcel("notagreed", gvNot)

I'm not sure what you mean by "gets stopped", but you can change this up so it performs better and fixes any issues you have.
This loop will take forever since you have to inspect every user on the domain. If you only want users in a specific group, then you can speed this up by only asking for members of the group. You do that by adding a condition in the query for the memberOf attribute. This way, you only get results you care about.
(Note that if you have more than one domain in your forest, using memberOf to find members may not work the way you want. I wrote about that here. But if you only have one domain, then it'll be fine.)
In the loop, don't create a DirectoryEntry for each result, since that will force it to go back out to AD and request the attributes for the object again, even though you already got what you needed in the search results. So use the values in the SearchResult object instead.
The documentation for SearchResultCollection says:
Due to implementation restrictions, the SearchResultCollection class cannot release all of its unmanaged resources when it is garbage collected. To prevent a memory leak, you must call the Dispose method when the SearchResultCollection object is no longer needed.
So you should put that in a Using clause.
I'm also not sure why you're calling ToString on PropertiesToLoad.Add when you're not using the return value. You can just remove that.
Here it is all together:
Dim entry As DirectoryEntry = New DirectoryEntry("LDAP://DOMAIN.EDU", "USERNAME", "PASSWORD", AuthenticationTypes.Secure)
Dim search As DirectorySearcher = New DirectorySearcher(entry) With {
.Filter = "(&(objectCategory=User)(objectClass=person)(memberOf=CN=MYGROUP,OU=Security Groups,OU=Students,DC=DOMAIN,DC=edu))",
.PageSize = 4000
}
search.PropertiesToLoad.Add("userPrincipalName")
search.PropertiesToLoad.Add("name")
Dim results As DataTable = New DataTable
results.Columns.Add("User ID")
results.Columns.Add("Full Name")
Dim CurrRow = 0
Using mySearchResultColl As SearchResultCollection = search.FindAll
For Each sr As SearchResult In mySearchResultColl
Dim dr As DataRow = results.NewRow
dr("User ID") = sr.Properties("userPrincipalName")(0)
dr("Full Name") = sr.Properties("name")(0)
results.Rows.Add(dr)
Next
End Using
gvNot.DataSource = results
gvNot.DataBind()
gvNot.Visible = True
gvAgreed.Visible = False
ExportToExcel("notagreed", gvNot)
I don't see you using CurrRow anywhere, but I left it in case you're using it in other code you didn't show here.

Try using String.Equals function with StringComparer.OrdinalIgnoreCase parameter instead of using equals operator.
Also what's the error you are getting?

Related

How to Select Rows in a Datatable?

How can I use the Variable Itmnmbr instead of hard-coding its value, 'i-2051'?
Dim fr() As DataRow
Dim Itmnmbr As string = "i-2051"
fr = dt.Select("item = 'i-2051'")
The most direct way is to use an Interpolated String, which are available from Visual Studio 2015, VB.Net 14:
Dim Itmnmbr As string = "i-2051"
fr = dt.Select($"item = '{Itmnmbr}'")
As a suggestion, let's change the names of Variables / Fields so it's easier to read them and also understand what these objects are used for. For example:
Dim dt as New DataTable()
'[...]
Dim itemNunmber As string = "i-2051"
Dim filteredRows As DataRow() = dt.Select(...)
itemNunmber is easier to read than Itmnmbr and filteredRows is more explicit than fr. There are some convetions that most are used to, as dt for DataTable, ds for DataSet etc., in this context. Better be sure that when you read your code after some time you don't get mad with yourself :)
Note that an Interpolated String is the same as a string formatted with String.Format(), so these two are actually the same thing:
Dim filteredRows As DataRow() = dt.Select($"item = '{itemNumber}'")
Dim filteredRows As DataRow() = dt.Select(String.Format("item = '{0}'", itemNumber))
Setting Option Infer On (should be On already), to make use of local type inference, you can write:
Dim filteredRows = dt.Select($"item = '{itemNumber}'")
and let the compiler infer the Type. In Visual Studio, if you move the mouse pointer over the variable, it will tell you what Type that is.
You have other options, if you need more dynamic selections.
The DataTableExtensions (which require a Project Reference to the System.Data.DataSetExtensions assembly - usually already linked along with System.Data), let you use the the AsEnumerable() method.
In LINQ to Objects style:
Here, using the default string Comparer
Dim filteredRows =
dt.AsEnumerable().Where(Function(dr) dr("item").ToString().Equals(itemNumber))
Or in LINQ to SQL style:
Here, using the InvariantCulture for the comparison.
Dim filteredRows =
From row In dt.AsEnumerable()
Where row.Field(Of String)("item").Equals(itemNumber, StringComparison.InvariantCulture)
Select row
See also: StringComparison and Best practices for comparing strings in .NET
These two last methods don't return an array of DataRow objects references, but a EnumerableRowCollection. The advantage is (when you can make use of it) that the collection is returned only when you actually use it (the execution is deferred).
When used correctly, it can improve the performance of your code. Try it out.
Instead of DataTable.Select(), you could also filter your DataTable, using its DefaultView.RowFilter property.
dt.DefaultView.RowFilter = $"item = '{itemNumber}'"
' You can save the filter to restore it later, if needed
Dim previousFilter = dt.DefaultView.RowFilter
When you present the Rows of your DataTable, only the Rows that meet the criteria defined by the Filter are shown (e.g., in a DataGrid of sort).
As mentioned, you're working with References here. The Collection of Rows returned by DataTable.Select() contain references of the Rows in the DataTable.
For example, if you consider the Collection and the filtered DataTable:
Dim filteredRows = dt.Select($"item = '{itemNumber}'")
dt.DefaultView.RowFilter = $"item = '{itemNumber}'"
Assume that filteredRows contains a single Row. Then you apply a Filter.
If you now change the value of filteredRows(0)("item"):
filteredRows(0)("item") = "Some other value"
when you present your DataTable in a UI, no Rows will be shown, since the Filter is active and now none of the Rows meet the filter's criteria: setting filteredRows(0)("item") has changed the value of the Row it refers to.
To remove a Filter, set it to string.Empty:
dt.DefaultView.RowFilter = Sting.Empty
To restore the previously saved filter:
dt.DefaultView.RowFilter = previousFilter

Checking Someone is a Member Of One of Many Groups Using Partial Group Name

I'm a bit stuck trying to work something into my code.
What I'm looking to do is to work out whether someone is a member of any one of a collection of groups. I'm not worried about which group specifically, I only want to know:
"Is user "X" a member of at least one of this collection of groups?"
The good news is, ALL these group names start in the same way:
Google-FullAccess
Google-RestrictedAccess
Google-MailOnly
Google-Enterprise etc.
Here's what I'm using to check for a specific group:
Dim ctx As DirectoryServices.AccountManagement.PrincipalContext = New DirectoryServices.AccountManagement.PrincipalContext(DirectoryServices.AccountManagement.ContextType.Domain, "net.mydomain.co.uk")
Dim user As DirectoryServices.AccountManagement.UserPrincipal = DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity(ctx, tbxuserID.Text)
Dim googleFull As DirectoryServices.AccountManagement.GroupPrincipal = DirectoryServices.AccountManagement.GroupPrincipal.FindByIdentity(ctx, "Google-FullAccess")
If user.IsMemberOf(googleFull) Then
GoogleAccess = 1
GoTo Proceed
End If
I then repeat this block of code to check for the next group and so on.
Is there a way I can adapt this to check for any group starting with "Google-"? Here's what I'd like to do but obviously doesn't work:
Dim googleCheck As DirectoryServices.AccountManagement.GroupPrincipal = DirectoryServices.AccountManagement.GroupPrincipal.FindByIdentity(ctx, "Google-*")
Help much appreciated!
I found the solution! The following works for me (after working out and storing the DistinguishedName in a String variable from a previous query - I also declare GoogleCheck as a Boolean variable beforehand):
Dim rootEntry As DirectoryServices.DirectoryEntry = New DirectoryServices.DirectoryEntry("LDAP://DC=net,DC=mydomain,DC=co,DC=uk")
Dim srch As DirectoryServices.DirectorySearcher = New DirectoryServices.DirectorySearcher(rootEntry)
srch.SearchScope = DirectoryServices.SearchScope.Subtree
srch.Filter = "(&(CN=Google-*)(objectCategory=group)(member=" + DistinguishedName + "))"
Dim res = srch.FindOne()
If res IsNot Nothing Then
GoogleCheck = True
Else
GoogleCheck = False
End If

vb.net form linq nullreferenceexception

I'm working on a Sindows Forms application to help keep inventory of some scanners. I'm using Linq2Sql, each table has an id column. On my repair history form. I'm trying to use the serial number from the inventory table so it goes to the database and looks up the sID from the table and it returns the correct value, but when I go to send all the entered data to the history table it gets a null reference exception.
Dim db As New DataClasses1DataContext
Dim rep As Scanner_Repair_History
Dim scan = (From Scanner_Inventory In db.Scanner_Inventories Where scannerid.Text = Scanner_Inventory.SN Select Scanner_Inventory.SID).FirstOrDefault
rep.SID = scan
rep.Date_Broken = datebroke.Value
rep.Description = description.Text
rep.Send_Date = senddate.Text
rep.Recieve_Date = recievedate.Text
rep.Cost = cost.Text
rep.PlantID = plantid.Text
rep.BID = brokenid.Text
rep.RMAnumber = rmanum.Text
db.Scanner_Repair_Histories.InsertOnSubmit(rep)
db.SubmitChanges()
is that me but you didn't instanciate your "rep" variable
You don't have a defined object for placement with a 'new' keyword but I am also curious if it is a system.type.
Update based on Jinx88909
You may be returning an entire POCO Object that may be null and have a null property. You can adjust this most times by doing a null condition if you are using .NET 4.5 and up. '?.' operator.
Dim db As New DataClasses1DataContext
'I need to be a new object and not instantiated as Nothing
Dim rep As New Scanner_Repair_History
'You have the potential for a nothing value here as 'FirstOrDefault' includes a potential Nothing'.
'I would get the entire object and then just a property of it after the fact
Dim scan = (From Scanner_Inventory In db.Scanner_Inventories Where scannerid?.Text = Scanner_Inventory?.SN Select Scanner_Inventory).FirstOrDefault?.Sid
If scan IsNot Nothing Then
rep.SID = scan 'Could you maybe want scan.Id or something similar?
rep.Date_Broken = datebroke.Value
rep.Description = description.Text
rep.Send_Date = senddate.Text
rep.Recieve_Date = recievedate.Text
rep.Cost = cost.Text
rep.PlantID = plantid.Text
rep.BID = brokenid.Text
rep.RMAnumber = rmanum.Text
db.Scanner_Repair_Histories.InsertOnSubmit(rep)
db.SubmitChanges()
Else
Console.WriteLine("I got nothing for you with the inputs you put in!")
End If

How do I get Active Directory Dynamic Distribution Groups using PrincipalContext?

Writing a vb.net application that needs to display a list of Active Directory Dynamic Distribution Groups. The code below works when retrieving a list of groups - but returns nothing when I add the OU=DynamicDistributionGroups to the context definition. Can Dynamic Distribution Groups be obtained using the GroupPrincipal this way? Suggestions are welcome? Thanks
Using ctx As New PrincipalContext
(ContextType.Domain, "MYLAN", "OU=DynamicDistributionGroups,OU=Email
Groups,DC=mylan,DC=ac,DC=mycompany,DC=com")
Dim pGroup As New GroupPrincipal(ctx)
pGroup.Name = "*"
Dim pSearcher As New PrincipalSearcher()
pSearcher.QueryFilter = pGroup
Dim results As PrincipalSearchResult(Of Principal) = pSearcher.FindAll()
For Each p As Principal In results
listGroup.Items.Add(p.ToString())
Next
End Using
I was able to accomplish this task using DirectorySearcher rather than Principal Context as follows:
Dim results As SearchResultCollection
Dim srch As New DirectorySearcher("LDAP://MYLAN/OU=DynamicDistributionGroups,OU=Email
Groups,DC=mylan,DC=ac,DC=mycompany,DC=com")
srch.Filter = "(objectClass=msExchDynamicDistributionList)"
srch.PropertiesToLoad.Add("displayName")
srch.PageSize = 1000
results = srch.FindAll()
For Each result As SearchResult In results
Dim props As ResultPropertyCollection = result.Properties
For Each propName As String In props.PropertyNames
Dim groupName As String = props(propName)(0)
Next
Next

Why won't my variable set to an instance of an object?

The program is a booking system (amongst other things) for a holiday letting company. I am working on the screen where you can see properties and ammend them or add more (etc)
Okay so It works fine in my other cases, but this one it just doesn't want to accept...I expect it's something stupid. Basically In the initial loading of the entire program I filled the Data Tables with the relevant info and then accessed them when needs be, in this case I am in the Form Properties and want to access Bookings (Which were made in FrmBookings) to see when the property is next booked to have guests in.
Dim Intcounter As Integer = 0
Dim NumberBookingRecords As Integer = BookingsNumRecs
Dim PropertyName As String
Dim PropertyFromBookings As String
Do
PropertyName = DTProperties(Intcounter)("Property Name").ToString
PropertyFromBookings = (DTBookings(NumberBookingRecords)("Property").ToString)
If PropertyName = PropertyFromBookings Then
lblDateOfArrival.Text = (DTBookings(NumberBookingRecords)("Arrival").ToString)
Intcounter = Intcounter + 1
Else
If Not NumberBookingRecords = 0 Then
NumberBookingRecords = NumberBookingRecords - 1
Else
End If
End If
Loop Until Intcounter >= intNumPropertyRecs
However when I get to PropertyFromBookings = (DTBookings(NumberBookingRecords)("Property").ToString)
it tells me that it could not be set to an instance of an object...no matter what I try an access from DTBookings I get the same response.
This is in the initial load form at the opening of the program
Dim FSBookings As New FileStream(strFileNameBookings, FileMode.OpenOrCreate, FileAccess.Read)
Application.DoEvents()
If FileLen(strFileNameBookings) > 0 Then
DTBookings.ReadXmlSchema(strFileNameBookings)
DTBookings.ReadXml(strFileNameBookings)
BookingsNumRecs = DTBookings.Rows.Count
intCurrRec = 1
Else
End If
FSBookings.Close()
blnStopAuto = True
blnStopAuto = False
Based on your code sample, DTBookings() is a function call. There are two possibilties here. Either:
The result of that function is Nothing, and when you try to use a Nothing as if there were an actual object there, (in this case, when trying to look up the ("Property") indexer) you'll get that exception, or ...
The result of the ("Property") index returns Nothing, in which case you'll get that exception when you try to call the .ToString() method.