fruitloopy
asked on
VB.NET - Search Active Directory computers description attribute and return computer name
Background explanation:
We have a logon script that updates the Description attribute of the current logged on computer with the username of the user who just logged in. When a user logs in the computer description attribute will change to display the username:
I am trying to find the correct way to search all active directory computers description field and return the computer name. This will help us to immediately help a user who phones for support. We dont have to waste time asking them for their computer name, we just ask for their username, type that into the app and it will return the computer name which we can remote to.
This is my code so far:
We have a logon script that updates the Description attribute of the current logged on computer with the username of the user who just logged in. When a user logs in the computer description attribute will change to display the username:
I am trying to find the correct way to search all active directory computers description field and return the computer name. This will help us to immediately help a user who phones for support. We dont have to waste time asking them for their computer name, we just ask for their username, type that into the app and it will return the computer name which we can remote to.
This is my code so far:
Dim searchRoot As New DirectoryEntry("LDAP://DC=DOMAIN,DC=local")
Dim searcher As New DirectorySearcher(searchRoot) With { _
.Filter = "(objectClass=computer)" _
}
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
searcher.Filter = "(&(objectClass=computer))"
Dim results = searcher.FindAll
For Each result In results
If result.Properties.Contains(txtUsernameSc.Text) Then
txtHostName.Text.ToString(result.GetDirectoryEntry().Properties("name").Value())
End If
Next
Nothing is returned though.
Considering that there is the possibility that a user can be logged into multiple workstations (or have multiple entries in the description field), either of these will return all associated computer names:
Each of which produce the same results.
Proof of concept -
-saige-
Function GetComputers(term As String) As IEnumerable(Of String)
Using searcher As New DirectorySearcher("(&(objectClass=computer))")
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
Return (From computer As SearchResult In searcher.FindAll()
Let entry = computer.GetDirectoryEntry()
Where Not String.IsNullOrEmpty(entry.Properties("description").Value) _
AndAlso entry.Properties("description").Value.ToString().IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
Select CType(entry.Properties("name").Value, String))
End Using
End Function
-Or-
Iterator Function GetComputers(term As String) As IEnumerable(Of String)
Using searcher As New DirectorySearcher("(&(objectClass=computer))")
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
For Each computer As SearchResult In searcher.FindAll()
Dim entry = computer.GetDirectoryEntry()
If Not String.IsNullOrEmpty(entry.Properties("description").Value) AndAlso entry.Properties("description").Value.ToString().IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1 Then
Yield entry.Properties("name").Value.ToString()
End If
Next
End Using
End Function
Each of which produce the same results.
Proof of concept -
Imports System.DirectoryServices
Module Module1
Sub Main()
For Each computer In GetComputers(Environment.GetEnvironmentVariable("USERNAME"))
Console.WriteLine("{0} is logged into: {1}", Environment.GetEnvironmentVariable("USERNAME"), computer)
Next
Console.ReadLine()
End Sub
Function GetComputers(term As String) As IEnumerable(Of String)
Using searcher As New DirectorySearcher("(&(objectClass=computer))")
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
Return (From computer As SearchResult In searcher.FindAll()
Let entry = computer.GetDirectoryEntry()
Where Not String.IsNullOrEmpty(entry.Properties("description").Value) _
AndAlso entry.Properties("description").Value.ToString().IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
Select CType(entry.Properties("name").Value, String))
End Using
End Function
End Module
Produces the following output --saige-
ASKER
That. is. AWESOME!
It works great. You're right, there are instances where a user is logged onto more than one PC.
What I would like to do is return the result "User is logged onto more than one computer" but when I tried this with an IF statement it failed.
It works great. You're right, there are instances where a user is logged onto more than one PC.
What I would like to do is return the result "User is logged onto more than one computer" but when I tried this with an IF statement it failed.
If that is the case then we would change a couple of things (I would also add a third returnable value that would indicate when a user is not described as being logged into a computer). Proposed changes:
-saige-
Imports System.DirectoryServices
Module Module1
Sub Main()
Console.WriteLine(GetComputer(Environment.GetEnvironmentVariable("USERNAME")))
Console.ReadLine()
End Sub
Function GetComputer(term As String) As String
Using searcher As New DirectorySearcher("(&(objectClass=computer))")
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
Dim computers = (From computer As SearchResult In searcher.FindAll()
Let entry = computer.GetDirectoryEntry()
Where Not String.IsNullOrEmpty(entry.Properties("description").Value) _
AndAlso entry.Properties("description").Value.ToString().IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
Select CType(entry.Properties("name").Value, String))
If computers.Count = 0 Then
Return String.Format("{0} is not currently logged into any computers", term)
ElseIf computers.Count > 1 Then
Return String.Format("{0} is logged into {1} computers", term, computers.Count)
Else
Return String.Format("{0} is logged into: {1}", term, computers.First())
End If
End Using
End Function
End Module
-saige-
ASKER
Fantastic work again. Thank you.
I am struggling to get this to return the results to the txtHostName.Text text box.
I used...
I am struggling to get this to return the results to the txtHostName.Text text box.
I used...
If computers.Count = 0 Then
txtHostName.Text = "No results"
ElseIf computers.Count > 1 Then
txtHostName.Text = computers.Count & " Found"
Else
txtHostName.Text = computers.First()
End If
But this tells me I am logged into 236 computers! I'm assuming this is how many computer objects we have in our AD
Possibly (you would have to get a count of the ad objects with a class of computer in order to verify). The only possible explanation for the discrepancy (so long as you are using the code as posted) is that the search term is in each of the description fields. What is the full code you are using?
-saige-
-saige-
ASKER
Here's all the code:
Private Sub btnSearchUser_Click(sender As Object, e As EventArgs) Handles btnSearchUser.Click
Cursor = Cursors.WaitCursor
GetComputers(txtUsernameSc.Text)
Cursor = Cursors.Default
End Sub
Function GetComputers(term As String) As IEnumerable(Of String)
Application.DoEvents()
Using searcher As New DirectorySearcher("(&(objectClass=computer))")
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
Dim computers = (From computer As SearchResult In searcher.FindAll()
Let entry = computer.GetDirectoryEntry()
Where Not String.IsNullOrEmpty(entry.Properties("description").Value) _
AndAlso entry.Properties("description").Value.ToString().IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
Select CType(entry.Properties("name").Value, String))
If computers.Count = 0 Then
txtHostName.Text = "No results"
ElseIf computers.Count > 1 Then
txtHostName.Text = computers.Count & " Found"
Else
txtHostName.Text = computers.First()
End If
End Using
End Function
Make sure that txtUsernameSc.Text is not null or empty (which I assume that it is not if you are entering it on a form). If you are getting all of the domain computers as results then that means that the text you are entering is in the description field of the computers returned.
To troubleshoot, let's do this:
-saige-
To troubleshoot, let's do this:
Function GetComputers(term As String) As IEnumerable(Of String)
Using searcher As New DirectorySearcher("(&(objectClass=computer))")
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
Dim computers = (From computer As SearchResult In searcher.FindAll()
Let entry = computer.GetDirectoryEntry()
Where Not String.IsNullOrEmpty(entry.Properties("description").Value) _
AndAlso entry.Properties("description").Value.ToString().IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
Select New With {.Name = CType(entry.Properties("name").Value, String), .Description = CType(entry.Properties("description").Value, String)})
If Not System.IO.File.Exists("descriptions.txt") Then
System.IO.File.WriteAllLines("descriptions.txt", computers.Select(Function(x) x.ToString()).ToArray())
Process.Start("descriptions.txt")
End If
If computers.Count = 0 Then
txtHostName.Text = "No Results"
ElseIf computers.Count > 1 Then
txtHostName.Text = String.Format("{0} computers Found", computers.Count)
Else
txtHostName.Text = computers.First().Name
End If
Return computers.Select(Function(x) x.Name)
End Using
End Function
-saige-
ASKER
The first test was to search for a username that I know is currently logged into 3 computers. This opened the "description.txt" file with all 3 PCs listed.
The second test was with a username with an exact match in the description field of the computers attribute. The first time I clicked the button it populated the txtHostName.Text field with "236 computers found"
The second time I clicked the button it entered the correct computer name in txtHostName.Text
The second test was with a username with an exact match in the description field of the computers attribute. The first time I clicked the button it populated the txtHostName.Text field with "236 computers found"
The second time I clicked the button it entered the correct computer name in txtHostName.Text
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
That seems to have done it, thanks it_saige. Here's the final code:
Function GetComputers(term As String) As IEnumerable(Of String)
Application.DoEvents()
Using searcher As New DirectorySearcher("(&(objectClass=computer))")
searcher.PropertiesToLoad.Add("name")
searcher.PropertiesToLoad.Add("description")
Dim computers = (From computer As SearchResult In searcher.FindAll()
Let entry = computer.GetDirectoryEntry()
Where Not String.IsNullOrEmpty(entry.Properties("description").Value) _
AndAlso entry.Properties("description").Value.ToString().IndexOf(term, StringComparison.OrdinalIgnoreCase) > -1
Select New With {.Name = CType(entry.Properties("name").Value, String), .Description = CType(entry.Properties("description").Value, String)})
If computers.Count = 0 Then
txtHostName.Text = "No Results"
ElseIf computers.Count > 1 Then
txtHostName.Text = String.Format("{0} computers Found", computers.Count)
Else
txtHostName.Text = computers.First().Name
End If
Return computers.Select(Function(x) x.Name)
End Using
End Function
ASKER
Awesome job. Thanks very much for your help.
Open in new window
I'm curious why you would use vb for something like this?