Visual Basic: Drive Searcher


Finding files with Visual Basic

A few times a month, I will see people asking in the forums on how to search the hard drive for a file. In the past, I would normally refer them to an old thread where I had provided a link to a file scan utility I had written. I got to thinking about it and I thought it was high time that I take that example and turn it into a re-usable class that could be easily integrated into someone else's search interface.

Source Code

Please be aware that I will be providing a complete example project on how to use the 'DriveSearcher' class, but the interface only shows how to use the DriveSearcher class.

I would also like to note that if you wish to see the comments for the code, that you will need to download the entire sample project from the MSDN Gallery, as this article is more of a supporting explanation for what the DriveSearcher does.

Download

You can download the sample project at the following link:

DriveSearcher Sample Project

Please be sure to vote 5 stars while you're there!

Introducing the DriveSearcher Class

The drive searcher class automatically runs on its own thread. This means that your application will remain responsive while the drive searcher performs its search routines.  When calling the DriveSearcher.SearchDrive Subroutine, you will notice that this routine is a shared routine. This means that you need not construct an instance of the DriveSearcher class. This class will create its own internal instances each time the DriveSearcher.SearchDrive method is called. We will get more into that later.

search parameters

In order to search a drive, you need only call the DriveSearcher.SearchDrive subroutine. Before you are able to call that subroutine, you need to be able to pass the required parameter to the sub(ByVal params As SearchParamaters). 

What is the Object Type SearchParamters?

This is a special class that was created to complement the use of DriveSearcher. This object reduces the size of the parameter for the DriveSearcher.SearchDrive method. This object also contains all of the fields that are required to instantiate a search and to handle the events of the search.

Let us examine the SearchParamaters Class:

01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Public Class  SearchParamaters
05. Inherits EventArgs
06. Private _DriveRootPath As String
07. Private _FindFile As String
08. Private _Invoker As Control
09. Public FileFoundEventHandler As DriveSearcher.FileFoundEventHandler
10. Public SearchStatusChangedEventhandler As DriveSearcher.StatusChangedEventHandler
11. Public ReadOnly  Property DriveRootPath As String
12. Get
13.  Return _DriveRootPath
14. End Get
15. End Property
16. Public ReadOnly  Property FindFile As String
17. Get
18.  Return _FindFile
19. End Get
20. End Property
21. Public ReadOnly  Property Invoker As Control
22. Get
23.  Return _Invoker
24. End Get
25. End Property
26. Sub New(ByVal DriveRootPath As String, _
27.  ByVal FindFile As String, _
28.  ByVal Invoker As Control, _
29.  Optional ByVal  FileFoundEventHandler As DriveSearcher.FileFoundEventHandler = Nothing, _
30.  Optional ByVal  SearchStatusChangedEventhandler As DriveSearcher.StatusChangedEventHandler = Nothing)
31. If GetDrives.IndexOf(DriveRootPath.ToLower) < 0 Then
32.  Throw New ArgumentException("The specified Drive Root Path is invalid.", "DriveRootPath")
33. End If
34. If Invoker Is Nothing  Then Throw  New ArgumentException("You must specify an invoker.", "Invoker")
35. Me._DriveRootPath = DriveRootPath
36. Me._FindFile = FindFile
37. Me._Invoker = Invoker
38. Me.FileFoundEventHandler = FileFoundEventHandler
39. Me.SearchStatusChangedEventhandler = SearchStatusChangedEventhandler
40. End Sub
41. Private Function  GetDrives() As  List(Of String)
42. Dim drives As IO.DriveInfo() = IO.DriveInfo.GetDrives()
43. Dim availableDrives As New List(Of String)
44. For Each  drive As  IO.DriveInfo In  drives
45.  Dim d As String  = drive.RootDirectory.FullName
46.  If d.Length = 3 Then
47.  availableDrives.Add(d.ToLower)
48.  End If
49. Next
50. Return availableDrives
51. End Function
52.End Class

First I will note that the first 3 lines are dealing with options. This is the recommended way to set your options in Visual Basic when possible. Also note that many people seem to have varying perceptions of what "when possible" means, especially when it comes to Option Strict...

The Following fields are a part of the SearchParameters Class:

  • DriveRootPath 
    • Example = "C:\
  • FindFile 
    • Examples:
      • *.*
      • *partialnam*.ext
      • ?ileNa?e.ext
      • ????.*
      • exactfilename.ext
      • Note how this uses the same search conventions as windows.
  • Invoker
    • As this is a multithreaded routine, there are delegates involved. Sooner or later, these delegates need to map back to your UI thread. When they do, they will need to know what your user UI thread is. That is where this field comes into play... More on this later.
  • FileFoundEventHandler
    • This is a delegate sub that you will assign using "AddressOf"  that points to a subroutine located within your UI. This sub routine will handle each FileFound event that is raised, and what to do with the found file path, internally, using the Invoker.Invoke method, the DriveSearcher will invoke your FileFoundEventHander whenever a new location of a file that matches your query is discovered.
  • SearchStatusChangedEventHandler
    • This delegate sub works kind of like how the FileFoundEventHandler works, in that it is invoked internally from within the DriveSearcher when the status of the search changes.
  • GetDrives(Function)
    • This function is used exclusively(in this class) to ensure that you have not provided an unavailable drive letter.
  • New (Constructor) 
    • Required Parameters(The DriveSearcher cannot work without this information)
      • DriveRootPath
      • FindFile
      • Invoker
    • Optional Parameters(The DriveSearcher can work without this information, but it's pointless...)
      • FileFoundEventHandler
      • SearchStatusChangedEventHandler

I have made the above optional (and writable) for the following reasons:

Allow you to change or assign the event handlers at a time that is not the same time as the time that the parameters were created.

So that's pretty much it for the search parameters class. But wait, there were a couple of event handlers that you probably never heard of, considering that they're not part of Visual Basic or Visual Studio.

FileFoundEventHandler

This is our delegate sub for when a file is discovered, this is used for multi-threading.

This is declared as follows, and is located in our DriveSearcher class:

Public Delegate  Sub FileFoundEventHandler(ByVal e As FileFoundEventArgs)

Pretty simple and straight forward, right? 

Well look, part of its signature is "e as FileFoundEventArgs"

What are those? That's another class that is written just for the DriveSearcher class.

Here is that class:

01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Public Class  FileFoundEventArgs
05. Inherits EventArgs
06. Private _SearchQuery As String
07. Private _CurrentFoundFile As String
08. Private _AllFoundFiles As List(Of String)
09. Public ReadOnly  Property SearchQuery As String
10. Get
11.  Return _SearchQuery
12. End Get
13. End Property
14. Public ReadOnly  Property FoundFile As String
15. Get
16.  Return _CurrentFoundFile
17. End Get
18. End Property
19. Public ReadOnly  Property AllFoundFiles As String()
20. Get
21.  Return _AllFoundFiles.ToArray
22. End Get
23. End Property
24. Sub New(ByVal SearchQuery As String, ByVal  FoundFile As  String, ByVal AllFoundFiles As List(Of String))
25. Me._SearchQuery = SearchQuery
26. Me._CurrentFoundFile = FoundFile
27. Me._AllFoundFiles = AllFoundFiles
28. End Sub
29.End Class

The FileFoundEventArgs class contains the following members:

  • SearchQuery
    • This is your original search query.
  • CurrentFoundFile 
    • This is the path of the file found that caused this event to be raised
  • AllFoundFiles
    • This is a list of all of the results for the current search query.

When FileFoundEventHandler is invoked, then a new instance of the FileFoundEventArgs class will be exposed to your FileFoundEventHandler, at that time, you can process those results however you want to.

StatusChangedEventHandler

This is our delegate sub for when the status of the search changes, this is used for multi-threading. This is declared as follows, and is located in our DriveSearcher class:

Public Delegate  Sub StatusChangedEventHandler(ByVal e As SearchStatusEventArgs)

Again, pretty simple and straight forward, right?

This method exposes an object called SearchStatusEventArgs

Again, this is something that is written specifically for the DriveSearcher class, so lets do a quick overview:

Here is the code:

01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Public Class  SearchStatusEventArgs
05. Inherits EventArgs
06. Private _SearchStatus As Status
07. Private _FilesComparedSoFar As Integer
08. Private _DirectoriesSearchedSoFar As Integer
09. Private _UnsearchableDirectories As Integer
10. Public ReadOnly  Property SearchStatus As Status
11. Get
12.  Return _SearchStatus
13. End Get
14. End Property
15. Public ReadOnly  Property FilesComparedSoFar As Integer
16. Get
17.  Return _FilesComparedSoFar
18. End Get
19. End Property
20. Public ReadOnly  Property DirectoriesSearchedSoFar As Integer
21. Get
22.  Return _DirectoriesSearchedSoFar
23. End Get
24. End Property
25. Public ReadOnly  Property UnsearchableDirectories As Integer
26. Get
27.  Return _UnsearchableDirectories
28. End Get
29. End Property
30. Public Sub  New(ByVal NewStatus As Status, ByVal FilesSearched As Integer, ByVal  DirectoriesSearchedSoFar As Integer, ByVal  UnsearchableDirectories As Integer)
31. Me._SearchStatus = NewStatus
32. Me._FilesComparedSoFar = FilesSearched
33. Me._DirectoriesSearchedSoFar = DirectoriesSearchedSoFar
34. Me._UnsearchableDirectories = UnsearchableDirectories
35. End Sub
36.End Class

The SearchStatusEventArgs class contains the following members:

SearchStatus

This is an instance of the following Enum

Public Enum  Status
 NotStarted
 Initializing
 GettingSubDirectories
 GettingFiles
 ComparingFileNames
 Complete
End Enum

FilesComparedSoFar

This integer is incremented each time the search query is compared to a file name

DirectoriesSearchedSoFar

This integer is incremented each time a list of files from a directory is searched.

UnsearchableDirectories

This integer is incremented each time an "Access denied" error occurs when attempting to read the contents of a directory.

DriveSearcher Class

Now you have read about the supporting classes that are needed in order to use the DriveSearcher class.

Now we will do a quick overview of the DriveSearcher class and how it works

01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Imports System.Threading
05.Imports System.IO
06.Public Class  DriveSearcher
07. Private Property  QueuedDirectories As  New List(Of String)
08. Private Property  Results As  New List(Of String)
09. Private Property  SearchString As  String
10. Private Property  InvokingControl As  Control
11. Private Property  FileFound As  FileFoundEventHandler
12. Private Property  SearchStatus As  StatusChangedEventHandler
13. Private Property  FileSearchCount As  Integer
14. Private Property  DirectoryCount As  Integer
15. Private Property  UnsearchableDirectories As Integer
16. Public Delegate  Sub FileFoundEventHandler(ByVal e As FileFoundEventArgs)
17. Public Delegate  Sub StatusChangedEventHandler(ByVal e As SearchStatusEventArgs)
18. Private Sub SetStatus(ByVal Status As Status)
19. If Not  Me.SearchStatus Is Nothing  Then
20.  Me.InvokingControl.Invoke(SearchStatus, _
21.  New SearchStatusEventArgs(Status, FileSearchCount, DirectoryCount, UnsearchableDirectories))
22. End If
23. End Sub
24. Private Sub SearchMain()
25. Do
26.  SetStatus(Status.GettingSubDirectories)
27.  Dim nextDirectory As String  = QueuedDirectories(0)
28.  Select Case  True
29.  Case ChildDirectories(nextDirectory).GetType = GetType(Boolean)
30.   UnsearchableDirectories += 1 'increment the error counter
31.  Case ChildDirectories(nextDirectory).GetType = GetType(String())
32.   For Each  Directory As  String In  CType(ChildDirectories(nextDirectory), String())
33.   DirectoryCount += 1
34.   QueuedDirectories.Add(Directory)
35.   Next
36.  End Select
37.  SetStatus(Status.ComparingFileNames)
38.  Dim TryResult As Object  = FilesInDirectory(nextDirectory)
39.  Select Case  True
40.  Case TryResult.GetType = GetType(Boolean)
41.   'We already incremented for this access denied directory earlier
42.  Case TryResult.GetType = GetType(String())
43.   For Each  f As  String In  CType(TryResult, String())
44.   FileSearchCount += 1
45.   Dim filename As String  = System.IO.Path.GetFileName(f)
46.   If filename.ToLower Like SearchString.ToLower Then
47.    Results.Add(f)
48.    InvokingControl.Invoke(FileFound, New  FileFoundEventArgs(Me.SearchString, f, Results))
49.   End If
50.   Next
51.  End Select
52.  QueuedDirectories.RemoveAt(0)
53. Loop Until  QueuedDirectories.Count = 0
54. SetStatus(Status.Complete)
55. End Sub
56. Private Function  FilesInDirectory(ByVal  Folder As  String) As Object
57. SetStatus(Status.GettingFiles)
58. Try : Return Directory.GetFiles(Folder, "*.*", SearchOption.TopDirectoryOnly)
59. Catch : Return False
60. End Try
61. End Function
62. Private Function  ChildDirectories(ByVal  Folder As  String) As Object
63. Try : Return Directory.GetDirectories(Folder, "*", SearchOption.TopDirectoryOnly)
64. Catch : Return False
65. End Try
66. End Function
67. Public Shared  Sub SearchDrive(ByVal params As SearchParamaters)
68. Dim searcher As New DriveSearcher
69. With searcher
70.  .SearchStatus = params.SearchStatusChangedEventhandler
71.  .InvokingControl = params.Invoker
72.  .FileFound = params.FileFoundEventHandler
73.  .QueuedDirectories.Clear()
74.  .QueuedDirectories.Add(params.DriveRootPath)
75.  .SearchString = params.FindFile
76. End With
77. If Not params.SearchStatusChangedEventhandler Is  Nothing Then
78.  params.Invoker.Invoke(params.SearchStatusChangedEventhandler, _
79.     New SearchStatusEventArgs(Status.Initializing, 0, 0, 0))
80. End If
81. Dim searchThread As New Thread(AddressOf searcher.SearchMain)
82. searchThread.Start()
83. End Sub
84. Public Shared  Function GetDrives() As List(Of String)
85. Dim drives As IO.DriveInfo() = IO.DriveInfo.GetDrives()
86. Dim availableDrives As New List(Of String)
87. For Each  drive As  IO.DriveInfo In  drives
88.  Dim d As String  = drive.RootDirectory.FullName
89.  If d.Length = 3 Then
90.  availableDrives.Add(d)
91.  End If
92. Next
93. Return availableDrives
94. End Function
95.End Class

First, we will go over the properties/variables of the DriveSearcher class

  • QueuedDirectories
    • This is a list of directories that still need to be searched for the query and other for directories to search
  • Results
    • This list contains all of the results from the search
  • InvokingControl
    • This is the main form that will be processing the events for the DriveSearcher
  • FileFound
    • This is the FileFoundEventDelegate assigned to handle the implied File Found Event
  • SearchString
    • This is the original search string
  • SearchStatus
    • This is the StatusChangedEventHandler assigned to handle the implied Status Changed event
  • FileSearchCount
    • This is the counter that is incremented each time a filename is compared to the query
  • DirectoryCount
    • This counter is incremented each directory that has been searched
  • Unsearchable Directories
    • This counter is incremented for each directory that is found to be inaccessible

DriveSearcher Methods

  • SetStatus
    • This sub will invoke the StatusChangedEventHandler that the user-provided and in the process new SearchStatusEventArgs will be created. This invokes the user-supplied code for the StatusChangedEventHandler
  • SearchMain
    • This is the main search loop, One loop cycle will:
      • Add all subdirectories of the current directory to the queue
      • Compare all files of the current directory to the search string
      • Increment the counters
      • Invoke event handlers
      • Remove the current directory from the queue before starting a new cycle
  • FilesInDirectory
    • This sub will return the following
      • A boolean value(false) if the directory is "Access denied"
      • A list of files located in that directory
  • ChildDirectories
    • This sub will return the following
      • A boolean value(false) if the directory is "Access denied"
      • A list of directories located in that directory
  • SearchDrive
    • This method 
      • Declares a non-shared DriveSearcher object
      • Applies the parameters to the DriveSearcher object
      • Invokes the StatusChangedEventHandler, to reflect that the search is initializing
      • Creates a new thread with an entry point of searchMain
      • Starts the search thread
  • GetDrives
    • Returns a list of valid 'DriveRootPaths', these are valid strings that can be passed as the "DriveRootPath" parameter for the constructor of the object called "SearchParameters".

Resources

See also

Please view my other Technet Wiki Articles

HitWebCounter