Link to home
Start Free TrialLog in
Avatar of fruitloopy
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:
User generated imageI 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

Open in new window

Nothing is returned though.
Avatar of Ed OConnor
Ed OConnor
Flag of Ireland image

This is so simple using Powershell:

Get-ADComputer -filter "description -like '<some_username>'"

Open in new window


I'm curious why you would use vb for something like this?
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:
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

Open in new window

-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

Open in new window


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

Open in new window

Produces the following output -User generated image
-saige-
Avatar of fruitloopy
fruitloopy

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.
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:
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

Open in new window


-saige-
Fantastic work again. Thank you.

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

Open in new window

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-
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

Open in new window

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:
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

Open in new window


-saige-
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
ASKER CERTIFIED SOLUTION
Avatar of it_saige
it_saige
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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

Open in new window

Awesome job. Thanks very much for your help.