2

Requirement
I want to get a list of all files from a given folder (and it's sub folders) based on creation date being within a given date range

My Knowledge
I know I can loop through each file in a folder using:

For Each oFile In oFolder.Files

Or I can use DIR to do something similar but both these options mean that I will be looping through each file in every folder (and sub folders).

My Resolution - So Far
What I am planning to do is to run a DOS command (through Sheel) and get the names of all files (that meet my requirement) into a text file and then perform my tasks on these files

Question
Is there a way that I can just get the names of all files (recursively through the folders) rather then looping through all files in each folder?

Pᴇʜ
  • 56,719
  • 10
  • 49
  • 73
Zac
  • 1,924
  • 1
  • 8
  • 21
  • View this [SO question](https://stackoverflow.com/questions/31414106/get-list-of-excel-files-in-a-folder-using-vba) – GMalc Jan 29 '19 at 13:47
  • You can take a look at this https://superuser.com/questions/32771/list-all-files-in-all-subfolders but you still need to go through txt file for getting data –  Jan 29 '19 at 13:49
  • Also if you take a look at https://stackoverflow.com/questions/9827715/get-list-of-sub-directories-in-vba It seems like in [VBA] we cant get subfolders (and subfolders) unless we use loops(With `FSO` or `Dir`) –  Jan 29 '19 at 13:50
  • Hi @GMalc: thanks for the reply. Unfortunately that's the very thing I'm trying to avoid (looping through all files). I was hoping for something that would give me a list of all files – Zac Jan 29 '19 at 13:52
  • You can use `dir /s /b "Parent%Folder%Path"` to get whole list in a text file or you can use Shell object's `stdout` functionality to get the output into array. – shrivallabha.redij Jan 29 '19 at 13:52
  • @TanmayGawankar: Thanks for that. That's the solution I'm going for at the moment :) – Zac Jan 29 '19 at 13:52
  • @shrivallabha.redij: thanks for that. That is what I plan to do but was wondering if there was a similar thing within VBA to achieve that rather than me get the output in a file and then reading the file – Zac Jan 29 '19 at 13:55
  • @Zac May I know the reason for avoiding loops? Is it because of speed? You can use Shell object (Microsoft Shell Controls and Automation) which is quite fast that FSO method. – shrivallabha.redij Jan 29 '19 at 13:57
  • 1
    @shrivallabha.redij: that's exactly it, for speed. I have thousands of files in the main folder and its sub folders. `Shell` is the option I'm going with for the very reason you have mentioned. My query was is there anything within VBA that would do something similar.. I don't know of one but there are a lot more knowledgeable people out there than me.. just thought I'd scratch there brains :) – Zac Jan 29 '19 at 14:00

2 Answers2

1

Using DIR is fast to get all the files you need up front, and it works recursively. I'm unsure if DIR can filter files based on create date, so I mixed that approach with FSO. I'm getting good performance. I'm able to return ~45,000 or so files in ~8 seconds.

A quick note on the FolderPattern parameter. This is really a Folder or File Pattern. So you can pass in part of the path that should exist for each file you want to match. You can also use wildcards, for example *.* will return all files, or *.txt would return all text files.

 'Adapted from --> https://stackoverflow.com/a/31132876/4839827
Public Sub GetAllFilesMatchingPattern(StartingFolder As String, FolderPattern As String, StartingDate As Date, EndingDate As Date)
    If Right$(StartingFolder, 1) <> "\" Then StartingFolder = StartingFolder & "\"
    Dim StandardOutput      As String
    Dim ws                  As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
    Dim Files               As Variant
    Dim FileArr             As Variant
    Static fso              As Object
    Dim FileCreationDate    As Date
    Dim j                   As Long

    If fso Is Nothing Then Set fso = CreateObject("Scripting.FileSystemObject")

    StandardOutput = CreateObject("WScript.Shell").Exec("CMD /C DIR """ & StartingFolder & FolderPattern & """ /S /B /A:-D").StdOut.ReadAll

    'Exit if there was no output
    If StandardOutput = vbNullString Then Exit Sub

    'Get all files that match initial filter
    Files = Split(StandardOutput, vbCrLf)
    ReDim FileArr(LBound(Files) To UBound(Files))
    j = LBound(Files)

    'Only include those which still exist and are in date range
    For i = LBound(Files) To UBound(Files)
        FileCreationDate = #1/1/1900#
        If fso.FileExists(Files(i)) Then FileCreationDate = fso.GetFile(Files(i)).DateCreated

        If FileCreationDate >= StartingDate And FileCreationDate <= EndingDate And FileCreationDate <> #1/1/1900# Then
            FileArr(j) = Files(i)
            j = j + 1
        End If
    Next

    ReDim Preserve FileArr(j)
    'Dump Data
    ws.Range("A1").Resize(UBound(Files), 1).Value2 = Application.Transpose(FileArr)
End Sub

Sub Example()
    GetAllFilesMatchingPattern "E:\", "*.*", #1/1/2000#, #1/29/2019#
End Sub
Ryan Wildry
  • 5,612
  • 1
  • 15
  • 35
  • Thanks Ryan. This is along the lines of what I was thinking off but seems better as you are not getting the file list in a file. I will try both answers and which ever yields better performance, I will select as an answer. Saying that, both answers seem perfectly viable solution so thanks for all your efforts – Zac Jan 30 '19 at 10:08
1

There actually is a way to do this by using WMI and executing queries on CIM_DataFile. The subroutine below would recursively query each subfolder and gather files based on the CreationDate property.

Sub WMIGetFile()
Dim strComputer As String
Dim strDateFrom As String
Dim strDateTo As String
Dim fso, f, subf, oWMI, colFiles, cf

strComputer = "."
strDateFrom = "20180101000000.000000+00" ' 01/01/2018
strDateTo = "20191231000000.000000+00"   ' 12/31/2019

Set oWMI = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")

Set fso = CreateObject("Scripting.FileSystemObject")
Set f = fso.getFolder("C:\FolderName\")

For Each subf In f.SubFolders
    Debug.Print subf.Path
    Set colFiles = oWMI.ExecQuery( _
        "SELECT * FROM CIM_DataFile" & _
        " WHERE Drive = 'C:' AND Path = '\\" & Replace(Right(subf.Path, Len(subf.Path) - 3), "\", "\\") & "\\'" & _
        " AND CreationDate >= '" & strDateFrom & "'" & _
        " AND CreationDate <= '" & strDateTo & "'")

        For Each cf In colFiles
            Debug.Print cf.Name
        Next cf

    Set colFiles = Nothing

Next subf

End Sub

Path is formatted with \\ instead of \ as a path delimiter starting from the drive specified in Drive, hence the Replace(Right()) method.

Also worth noting that WMI dates are formatted as strings by yyyymmddhhmmss.000000.

EDIT:

My brain missed the part where you need to execute this on the main folder as well. In that case, I'd just define it as a function and pass the parameters like this

Sub WMIGetFile()
    Dim fso, f, subf, oWMI
    Dim strComputer As String

    strComputer = "."

    Set oWMI = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set f = fso.getFolder("C:\FolderName\")

    QueryCIMDATAFILE oWMI, f

    For Each subf In f.SubFolders
        QueryCIMDATAFILE oWMI, subf
    Next subf

End Sub


Function QueryCIMDATAFILE(conn, path)
    Dim colFiles, cf

    Dim strDateFrom As String
    Dim strDateTo As String


    strDateFrom = "20180101000000.000000+00" ' 01/01/2018
    strDateTo = "20191231000000.000000+00"   ' 12/31/2019

    Set colFiles = conn.ExecQuery( _
        "SELECT * FROM CIM_DataFile" & _
        " WHERE Drive = 'C:' AND Path = '\\" & Replace(Right(path.path, Len(path.path) - 3), "\", "\\") & "\\'" & _
        " AND CreationDate >= '" & strDateFrom & "'" & _
        " AND CreationDate <= '" & strDateTo & "'")

    For Each cf In colFiles
        Debug.Print cf.Name
    Next cf

    Set colFiles = Nothing
End Function
Tate Garringer
  • 1,509
  • 1
  • 6
  • 9