0

I need to search the file system (a drive usually) for a fully defined file path while given only a fragment of the file name.

The fragment is actually the part number of the part, and the files to search are all of type '.idw'. Further, they are named with a series that helps sort them; ie 1XX-XXXX.idw, 2XX-XXX.idw.

There are 50,000+ files and just using a FileScriptingObject and recursive reading each folder then comparing them takes something like 2 minutes per search.

(Given a list of part numbers, I need to populate a column in Excel with the full file name)

I'm guessing my best way to go about this is to generate an indexed list of all of the idw files I'm looking for, reducing the full file string to only the base name and using that as the key. However, this would still required the timely run at the start of each search assuming I use this dictionary/collection/list over and over per run.

Is there any way to store a dictionary in an external file, so I can generate the indexed list once per day or a lot less frequently?

Otherwise, is there a better way to do this with VBA that I have not thought of?

  • 2
    You can export your dictionary to a tab-delimited plain text file, or just store it on a worksheet. – Tim Williams Aug 04 '16 at 18:56
  • What you describe is basically what Windows file searching does for non-indexed locations, and I'd guess the wait for Windows Explorer results would likely be on the same order. You could create your own index, but then the question would be how important it is for it to be in synch when your macro runs. – Comintern Aug 04 '16 at 18:56
  • The tab-delimited plain text file sounds like something I will have to look up! It being in sync is not extremely critical; it only looks up the files to populate a list so that another macro knows where to print the file from. The end user will be warned/easily able to see if a line isn't populated and then can manually search for it there. And like you said, seems like the Windows Explorer search time is pretty close to what my macro does. – MechMachineMan Aug 04 '16 at 19:23
  • Maybe you don't need to re-invent the wheel. I suggest a look at http://www.voidtools.com/support/everything/file_lists/ and http://www.voidtools.com/support/everything/command_line_interface/ – Andre Aug 04 '16 at 21:08
  • Have you tried `dir` to retrieve a list of files? Something like `sResult = CreateObject("WScript.Shell").Exec("%comspec% /c dir /B /A-D /S ""C:\Test\*.idw""").StdOut.ReadAll` – omegastripes Aug 04 '16 at 23:36

1 Answers1

1

Following up the comment from @omegastripes, you can combine three methods to achieve the objective.

  1. Use the Exec method of WScript.Shell to run a Dir command - likely faster than using FileSystemObject
  2. Split the StdOut to get a Variant array of all the filenames returned - this is the one-time hit to get the list of files you want to search over
  3. Use the Filter function to reduce the array to just the filenames including the ones you are interested in displaying on the spreadsheet.

The DIR command leverages some switches that are important to the task:

  • /S - recursive through sub-directories
  • /B - bare names only
  • /A:-D - exclude directories from output, i.e. files only

Here's the sample code:

Option Explicit

Sub Test()

    Dim arrFiles As Variant
    Dim arrSearchTerms As Variant
    Dim arrMatches As Variant
    Dim intTargetCounter As Integer
    Dim intMatchCounter As Integer

    'get files
    arrFiles = GetFileList("C:\WINDOWS", "idw")
    If UBound(arrFiles) = 0 Then
        MsgBox "No files found"
        Exit Sub
    End If

    'iterate search terms and check collection
    arrSearchTerms = Array("1XX-XXXX", "2XX-XXXX")
    For intTargetCounter = LBound(arrSearchTerms) To UBound(arrSearchTerms)
        arrMatches = Filter(arrFiles, arrSearchTerms(intTargetCounter))
        For intMatchCounter = LBound(arrMatches) To UBound(arrMatches)
            Debug.Print arrMatches(intMatchCounter)
        Next intMatchCounter
    Next intTargetCounter

End Sub

Function GetFileList(strRoot As String, strExtensionFilter As String) As Variant

    Dim objShell As Object
    Dim strCommand As String
    Dim objShellExe As Object

    On Error GoTo CleanUp

    'call cmd
    Set objShell = CreateObject("WScript.Shell")
    strCommand = "%COMSPEC% /C DIR /S /B /A:-D *." & strExtensionFilter
    objShell.CurrentDirectory = strRoot
    Set objShellExe = objShell.Exec(strCommand)

    'wait for listing
    While objShellExe.Status <> 1
        DoEvents
    Wend

    'convert std out to array
    GetFileList = Split(objShellExe.StdOut.ReadAll, vbCrLf)

CleanUp:
    If Err.Number <> 0 Then
        Debug.Print Err.Number & ": " & Err.Description
    End If
    Set objShellExe = Nothing
    Set objShell = Nothing

End Function
Robin Mackenzie
  • 18,801
  • 7
  • 38
  • 56
  • Interesting method! Haven't tried anything running through command prompt before. I changed directories, but so far all this does is pop up a blank command prompt window? – MechMachineMan Aug 08 '16 at 17:44
  • The pop-up cmd window will last as long as the search is running. If you try the code on a small-ish set of files first you will see how it works. Then you can let it loose on your 50,000+ files. I am hoping this method works quicker than 'FileSystemObject` for that amount of files . – Robin Mackenzie Aug 09 '16 at 00:22