VB.NET Remove user from active directory - vb.net

Hi I am trying to create a VB.NET application which will (hopefully) reduce some time spent on some of my departments helpdesk calls. The part that I am stuck with is how to use VB.NET to remove a user from a group. The following is code that I have been playing with:
Public Shared Sub RemoveUserFromGroup(ByVal deUser As String, ByVal GroupName As String)
Dim entry As DirectoryEntry = ADEntry()
Dim mySearcher As DirectorySearcher = New DirectorySearcher(entry)
mySearcher.Filter = "(&(ObjectClass=Group)(CN=" & GroupName & "))"
mySearcher.PropertiesToLoad.Add("OrganizationalUnit")
mySearcher.PropertiesToLoad.Add("DistinguishedName")
mySearcher.PropertiesToLoad.Add("sAMAccountName")
Dim searchResults As SearchResultCollection = mySearcher.FindAll()
If searchResults.Count > 0 Then
Dim group As New DirectoryEntry(searchResults(0).Path)
Dim members As Object = group.Invoke("Members", Nothing)
For Each member As Object In CType(members, IEnumerable)
Dim x As DirectoryEntry = New DirectoryEntry(member)
MessageBox.Show(x.Properties("sAMAccountName").Value)
If x.Properties("sAMAccountName").Value = deUser Then
MessageBox.Show(searchResults.Item(0).Path.ToString)
MessageBox.Show(x.Properties("sAMAccountName").Value)
'group.Invoke("Remove", New Object() {x.Properties("OrganizationalUnit").Value})
group.Properties("member").Remove(x.Properties("OrganizationalUnit").Value)
End If
Next
End If
When I run the program, I recevie a COMException was unhandled, unspecified error at the group.properties line. When using group.invoke I receive the error TargetInvocationException was unhandled.
My aim is to pass as a string the username (sAMAccountName) and the groupname (sAMAccountName) to the function which will locate the user and remove them from the group.
I am new to VB.NET and would appreciate any assistance people can provide.
I am coding in .NET 2.0 as I am unsure if the server it will live on will have 3.5 installed.

Well the error message 0x80004005 E_FAIL Unspecified failure is not very helpful. I often get frustrated when working with Active Directory.
Try changing line:
group.Properties("member").Remove(x.Properties("OrganizationalUnit").Value)
to
group.Invoke("Remove", New Object() {x.Path.ToString()})
If you need more reference take a look at this article on VB.net Heaven by Erika Ehrli. The article covers various use cases with Active Directory.
I hope that helps.

Related

Display operatingsystem property in a message box

I am having trouble getting the operatingsystem property for computers in Active Directory(AD). Here is the code I am having trouble with and the function that gets the property from AD. The hostname works perfectly but the OS_Name displays a blank message box.
Alternatively, I would also like help formatting the filter to make things easier down the road. My goal is to check the operating system and only process the Windows 10 Enterprise and Windows 7 Enterprise operating systems but I can't even display the operating system in a message box. Please help.
Dim enTry As DirectoryEntry = New
DirectoryEntry("GC://my.work.com/DC=AB27,DC=my,DC=work,DC=com")
Dim mySearcher As DirectorySearcher = New DirectorySearcher(enTry)
mySearcher.Filter = ("(objectClass=computer)")
mySearcher.PropertiesToLoad.Add("dNSHostName")
mySearcher.PropertiesToLoad.Add("operatingSystem")
Dim ds As SearchResult
For Each ds In mySearcher.FindAll()
Dim OS_Name As String = GetProperty(ds, "operatingSystem")
Dim hostName As String = GetProperty(ds, "dNSHostName")
MessageBox.Show(hostName)
MessageBox.Show(OS_Name)
Next
Function GetProperty(ByVal searchResult As SearchResult, ByVal
PropertyName As String)
If searchResult.Properties.Contains(PropertyName) Then
Return searchResult.Properties(PropertyName)(0).ToString()
Else
Return String.Empty
End If
End Function
I expect the message for the operating system to display the operating system property. Instead it displays a blank message box.
When I use debug.writeline instead of a message box it leaves a blank line between each hostname as if it reads the OS but just adds a line to hold the space.
Not sure what is in your DirectoryEntry nor if why you have structured your filter is causing a problem but the following shows me my hostname and operating system...
Dim enTry As DirectoryEntry = New DirectoryEntry
Dim mySearcher As DirectorySearcher = New DirectorySearcher(enTry)
mySearcher.PropertiesToLoad.Add("dNSHostName")
mySearcher.PropertiesToLoad.Add("operatingSystem")
mySearcher.PropertiesToLoad.Add("operatingSystemVersion")
mySearcher.Filter = "(&(objectClass=computer)(operatingSystem=*server*))"
Dim resEnt As SearchResult
For Each resEnt In mySearcher.FindAll()
Dim OS_Name As String
Dim hostName As String
hostName = GetProperty(resEnt, "dNSHostName")
OS_Name = GetProperty(resEnt, "operatingSystem")
Next

LDAP Vb.net simple query

I'm trying to create a vb.net code for a simple query through LDAP but having an issue and can't find where it is.
Dim ldapServerName As String = "xxx.test.intranet.xxx.ca"
Dim oRoot As DirectoryEntry = New DirectoryEntry("LDAP://" & ldapServerName & "/c=ca, DC=xxx,DC=corp,DC=xxx,DC=ca")
oRoot.Username = "ou=Tool,ou=applications,o=xxx,c=ca"
oRoot.Password = "something#2015"
Dim LDAPSearcher As New DirectorySearcher()
LDAPSearcher.Filter = "(&(employeenumber=6012589))"
Dim SearchResult As SearchResult = LDAPSearcher.FindOne()
Dim UserEntry As DirectoryEntry = SearchResult.GetDirectoryEntry()
EDTEST.Text = UserEntry.Properties("employeenumber").Value.ToString
it is giving me an error saying that the object is not valid. The searcher variable is in fact empty so it has to do with my query somehow.
This is my first time with LDAP¨and I have tried some of the solution i could find on the net but nothing is working so far.
Error: Object not set to an instance of an object.
Unless you're adding another attribute to search by, you don't need the AND operator in your filter syntax - a search for simply (employeenumber=6012589) should work just fine.
If do you have another attribute you'd like to search by, the filter syntax would be similiar to what you have now, only with the additional attribute :
(&(employeenumber=6012589)(objectClass=user))
EDIT:
I put together an example using the lower level System.DirectoryServices and System.DirectoryServices.Protocols namespaces. This helps break up the actual login and search functions, and will also provide better context when errors occur. For the example, I've replaced all of my variables with the ones you're using in your question. I tested this against our own Active Directory instance over unsecured port 389 using my creds and a base domain similar to the one you're using.
Imports System.DirectoryServices.Protocols
Imports System.Net
Module Module1
Sub Main()
' setup your creds, domain, and ldap prop array
Dim username As String = "ou=Tool,ou=applications,o=xxx,c=ca"
Dim pwd As String = "something#2015"
Dim domain As String = "DC=xxx,DC=corp,DC=xxx,DC=ca"
Dim propArray() As String = {"employeenumber"}
' setup your ldap connection, and domain component
Dim ldapCon As LdapConnection = New LdapConnection("xxx.test.intranet.xxx.ca:389")
Dim networkCreds As NetworkCredential = New NetworkCredential(username, pwd, domain)
' configure the connection and bind
ldapCon.AuthType = AuthType.Negotiate
ldapCon.Bind(networkCreds)
' if the above succceeded, you should now be able to issue search requests directly against the directory
Dim searchRequest = New SearchRequest(domain, "(employeenumber=6012589)", SearchScope.Subtree, propArray)
' issue the search request, and check the results
Dim searchResult As SearchResponse = ldapCon.SendRequest(searchRequest)
Dim searchResultEntry As SearchResultEntry
If (searchResult.Entries.Count > 0) Then ' we know we've located at least one match from the search
' if you're only expecting to get one entry back, get the first item off the entries list
searchResultEntry = searchResult.Entries.Item(0)
' continue to do whatever processing you wish against the returned SearchResultEntry
End If
End Sub
End Module

VB.NET adding a user to distribution list. An operations error occurred

So this is what I've got -
Public Shared Function GetDirectoryEntry() As DirectoryEntry
Try
Dim entryRoot As New DirectoryEntry("LDAP://RootDSE")
Dim Domain As String = DirectCast(entryRoot.Properties("defaultNamingContext")(0), String)
Dim de As New DirectoryEntry()
de.Path = "LDAP://" & Domain
de.AuthenticationType = AuthenticationTypes.Secure
Return de
Catch
Return Nothing
End Try
End Function
Protected Sub rbAddUser_Click(sender As Object, e As EventArgs) Handles rbAddUser.Click
AddMemberToGroup("LDAP://DOMAIN.local/CN=" & !DISTRIBUTIONNAME! & ",CN=Users,DC=DOMAIN,DC=local", "/CN=" & !SELECTEDUSER! & ",CN=Users,DC=DOMAIN,DC=local")
End Sub
Private Sub AddMemberToGroup(ByVal bindString As String, ByVal newMember As String)
Dim ent As DirectoryEntry = GetDirectoryEntry()
ent.Properties("member").Add(newMember)
ent.CommitChanges()
End Sub
I hope this is easy enough for people to read, anyway the group and user are selected by the users in a table and when they click the add button I want the selected users to be adding to the selected distribution list.
when it gets to the CommitChanges() I get this error
An exception of type 'System.DirectoryServices.DirectoryServicesCOMException' occurred in System.DirectoryServices.dll but was not handled in user code Additional information: An operations error occurred.Error -2147016672
This is a common issue with the Process Model application pool configuration, from the official documentation:
By using the <processModel> element, you can configure many of the security, performance, health, and reliability features of application pools on IIS 7 and later.
This issue exists as CommitChanges() requires elevated privileges, and can be fixed by setting your web-application to run under NetworkManager; this can be done in two ways:
Directly in your code, place the problem code inside this Using statement:
Using HostingEnvironment.Impersonate()
'Problem code goes here.
End Using
Via IIS Manager:
Navigate to your website's application pool;
Navigate to Advanced Settings;
Scroll down to the Process Model group;
Change Identity to NetworkService
I solved the error by passing through my user credentials
Private Sub AddMemberToGroup(ByVal bindString As String, ByVal newMember As String)
Dim ent As New GetDirectoryEntry(bindString)
ent.Properties("member").Add(newMember)
ent.Username = "DOMAIN\USERNAME"
ent.Password = "PASSWORD"
ent.CommitChanges()
End Sub
However my code still doesn't work, I just get no errors.

Using DirectoryEntry.Invoke("SetPassword", ...) to set initial AD account password, I get "RPC server is unavailable" error

The company I'm working in has a web service that can create new Active Directory accounts based on information that is typed in e.g. username, firstname, lastname, OU1, OU2, etc.
This web service has been working fine on Windows Server 2003. Now I'm working on moving the web service to 2008 R2 servers. However a certain functionality doesn't work anymore, which is when it tries to set a random initial password to the newly created account.
The exception that is thrown is a TargetInvocationException containing an inner exception of COMException with a message of "The RPC Server is unavailable".
Imports System.DirectoryServices
Dim ldapPath As String = "..." 'assume valid LDAP path
Dim objContainer As DirectoryEntry = New DirectoryEntry(ldapPath, vbNullString, vbNullString, AuthenticationTypes.Secure)
objUser = objContainer.Children.Add("cn=" & Username.Trim, "user")
'sets objUser.Properties e.g. givenName, displayName, userPrincipalName, samAccountName, etc. Not important...
objUser.CommitChanges()
strPassword = RandomPassword() 'function that generates random password
objUser.Invoke("SetPassword", New Object() {strPassword})
objUser.Properties("useraccountcontrol").Value = "512" ' set as normal account
objUser.CommitChanges()
'and so on...
The error happens on the line that says:
objUser.Invoke("SetPassword", New Object() {strPassword})
Strangely the account creation itself works and I can see the new user from Active Directory Users and Computers.
I have consulted a few different people who manage the security of the DCs and the web servers, they don't really know why...
In the end I figured out a different way of setting the password, which is using the System.DirectoryServices.AccountManagement library.
So the code becomes something like this:
Imports System.DirectoryServices
Imports System.DirectoryServices.AccountManagement
Dim ldapPath As String = "..." 'assume valid LDAP path
Dim objContainer As DirectoryEntry = New DirectoryEntry(ldapPath, vbNullString, vbNullString, AuthenticationTypes.Secure)
Dim objUser As DirectoryEntry = objContainer.Children.Add("cn=" & Username.Trim, "user")
'sets objUser.Properties e.g. givenName, displayName, userPrincipalName, samAccountName, etc. Not important...
objUser.CommitChanges()
Dim domainContext As PrincipalContext = New PrincipalContext(ContextType.Domain, ...)
Dim user As UserPrincipal = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, Trim(Username))
strPassword = RandomPassword() 'function that generates random password
user.SetPassword(strPassword)
user.Enabled = True 'setting these two properties
user.PasswordNotRequired = False 'result in useraccountcontrol value = 512 (normal account)
user.Save()
'and so on...
I'm mixing old code with new code in this one, but it appears to be working really well on the new servers. One thing to note is that sometimes the UserPrincipal.FindByIdentity call initially returns Nothing, I believe due to delay in the account creation or with the replication. So I have to make sure that the user object is not Nothing by trying FindByIdentity multiple times until I get the object.
Despite finding a solution (more like a way around) to the problem, still I'm confused as to why the old code does not work on the new servers, but the new one does. The error is very generic and searching the internet for clues have resulted in nothing but more confusion.
Would really appreciate if experts out there can shed some lights or even comment on the way my new code looks, any problems?
Thanks in advance.

vb.net: listbox.items.add() throws exception in same class

I'm not even sure I understand this situation enough to come up with a proper title. I come from a modest understanding of VB6 and having to climb a steep learning curve for VB 2010.
I am trying to create a multi-client server program that will communicate with my Enterprise iPhone app. I found a relatively simple example to build upon here: http://www.strokenine.com/blog/?p=218. I have been able to modify the code enough to make it work with my app, but with one glitch: I can't get access to the controls on the form to add items, even though the method is invoked within the form's class. (I tried this on the original code too, and it does the same thing. I don't know how the author managed to get it to work.)
Here's the code segment in question:
Public Class Server 'The form with the controls is on/in this class.
Dim clients As New Hashtable 'new database (hashtable) to hold the clients
Sub recieved(ByVal msg As String, ByVal client As ConnectedClient)
Dim message() As String = msg.Split("|") 'make an array with elements of the message recieved
Select Case message(0) 'process by the first element in the array
Case "CHAT" 'if it's CHAT
TextBox3.Text &= client.name & " says: " & " " & message(1) & vbNewLine 'add the message to the chatbox
sendallbutone(message(1), client.name) 'this will update all clients with the new message
' and it will not send the message to the client it recieved it from :)
Case "LOGIN" 'A client has connected
clients.Add(client, client.name) 'add the client to our database (a hashtable)
ListBox1.Items.Add(client.name) 'add the client to the listbox to display the new user
End Select
End Sub
Under Case "LOGIN" the code tries to add the login ID to the listbox. It throws an exception: "A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll" The listbox (all controls, for that matter) is in the same class, Server.vb and Server.vb [Design].
The data comes in from another class that is created whenever a client logs on, which raises the event that switches back to the Server class:
Public Class ConnectedClient
Public Event gotmessage(ByVal message As String, ByVal client As ConnectedClient) 'this is raised when we get a message from the client
Public Event disconnected(ByVal client As ConnectedClient) 'this is raised when we get the client disconnects
Sub read(ByVal ar As IAsyncResult) 'this will process all messages being recieved
Try
Dim sr As New StreamReader(cli.GetStream) 'initialize a new streamreader which will read from the client's stream
Dim msg As String = sr.ReadLine() 'create a new variable which will be used to hold the message being read
RaiseEvent gotmessage(msg, Me) 'tell the server a message has been recieved. Me is passed as an argument which represents
' the current client which it has recieved the message from to perform any client specific
' tasks if needed
cli.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf read, Nothing) 'continue reading from the stream
Catch ex As Exception
Try 'if an error occurs in the reading purpose, we will try to read again to see if we still can read
Dim sr As New StreamReader(cli.GetStream) 'initialize a new streamreader which will read from the client's stream
Dim msg As String = sr.ReadLine() 'create a new variable which will be used to hold the message being read
RaiseEvent gotmessage(msg, Me) 'tell the server a message has been recieved. Me is passed as an argument which represents
' the current client which it has recieved the message from to perform any client specific
' tasks if needed
cli.GetStream.BeginRead(New Byte() {0}, 0, 0, AddressOf read, Nothing) 'continue reading from the stream
Catch ' IF WE STILL CANNOT READ
RaiseEvent disconnected(Me) 'WE CAN ASSUME THE CLIENT HAS DISCONNECTED
End Try
End Try
End Sub
I hope I am making sense with all this. It all seems to bounce back and forth, it seems so convoluted.
I've tried using Me.listbox1 and Server.listbox1 and several other similar structures, but to no avail.
I'm reading a lot about Invoke and Delegates, but would that be necessary if the method and the control are in the same class? Or do I have a fundamental misperception of what a class is?
Many thanks for any help I can get.
Private Delegate Sub UpdateListDelegate(byval itemName as string)
Private Sub UpdateList(byval itemName as string)
If Me.InvokeRequired Then
Me.Invoke(New UpdateListDelegate(AddressOf UpdateList), itemName)
Else
' UpdateList
' add list add code
ListBox1.Items.Add(itemName)
End If
End Sub
Add above, then replace:
ListBox1.Items.Add(client.name)
to
UpdateList(client.name)
Does it work? check the syntax, may have typo as I type it.