1

I have a user-form that pastes folder-paths into a list. I then have the code below that is supposed to loop through that list and list all the sub-folders (then I'll probably have another code loop through the sub-folders to get the excel workbooks).

I know it's inelegant, because ultimately what I want is have my list of paths be looked in one a time, through each folder and subfolder to find and list the excel files. But there was a question like that and it was taken down. The question was then referred to a different q&a that I did not understand, that had to do with individual FILE NAMES, typed in a single cell not a range, nor as a path. I speak Russian, which some of his code was in, and still couldn't quite understand what his code meant and was referring to, and when I tried it, it kept telling met that "GetData" was undefined? so I've tried to ask a different but similar question in the hope that someone can explain to me what I need to do, as I've gone as far as I can and have tried to adapt both codes from the links in this post as well as many others. I have several modules with broken code that doesn't work, and the closest I've come is the code below. At this point I'd settle simply for a way to list the excel file names from a list of paths.

Option Explicit
Dim i As Long, j As Long
Dim searchfolders As Variant
Dim FileSystemObject

Sub ListOfFolders77()
Dim LookInTheFolder As String
'Dim ws As Worksheet: Set ws = Sheets("Output4")
Dim ws2 As Worksheet: Set ws2 = Sheets("Output3")
Dim rng As Range: Set rng = ws2.Range("A1:A" & Rows.Count).End(xlUp)
Dim mypath As Range
'Dim Region As Range: Set Region = ws.Range("A2")
'Dim district As Range: Set district = ws.Range("B2")
'Dim city As Range: Set city = ws.Range("C2")
'Dim atlas As Range: Set atlas = ws.Range("D2")

i = 1
For Each mypath In rng
    LookInTheFolder = mypath.Value
    Set FileSystemObject = CreateObject("Scripting.FileSystemObject")
    For Each searchfolders In FileSystemObject.GetFolder(LookInTheFolder).subfolders
        Sheets("Subfolders").Cells(i, 1) = searchfolders
        i = i + 1
        SearchWithin searchfolders
    Next searchfolders
Next mypath

End Sub

Sub SearchWithin(searchfolders)
On Error GoTo exits
For Each searchfolders In FileSystemObject.GetFolder(searchfolders).subfolders
j = UBound(Split(searchfolders, "\"))
Cells(i, j) = searchfolders
i = i + 1
SearchWithin searchfolders
Next searchfolders
exits:
End Sub

Ideally I want to get all the excel files in the folders and subfolders, and copy paste the data on the first sheet into one long list, but I'm still on step 1. I posted a more detailed explanation here last week and have yet to receive any feedback or potential tips.

I apologize if this doesn't make sense or seems half-hazard. I am self taught in excel VBA and am struggling to understand if what I need is even possible. I attempted using Directory but I've little success putting directory in a for each loop. I also tried using an array, which almost crashed by computer as it went to list ALL the folders and files in my entire computer.

Community
  • 1
  • 1
R.E.L.
  • 157
  • 1
  • 12
  • 1
    What's your question? Would it be "Given a folder path, find all Excel documents (`.xlsx`,`.xls`,`.xlsm`) in that folder, and all subfolders"? After that, opening each file and adding to a master sheet should be relatively easy - that's a common request and there are myriad SO questions and general Internet searches that should help with that part) – BruceWayne Jun 04 '18 at 17:59
  • @BruceWayne The first one "Given a folder path, find all Excel documents (.xlsx,.xls,.xlsm) in that folder, and all subfolders" but also "given a LIST of folder paths". Sorry, I tend to include too much information. I will work on it in future stackoverflow questions – R.E.L. Jun 04 '18 at 18:23

2 Answers2

1

Here's a quick way, slightly adapted from this answer.

Just add in your folder locations to the path() = ... list and it should work for you. It outputs, in the current excel sheet, the paths of all Excel files in folders you provide.

From there, you can do what you please. (Perhaps throw the file paths in to an array, so you have an array of files you want to open. From there you can do the copying of data).

'Force the explicit delcaration of variables
Option Explicit

Sub ListFiles()
'http://www.xl-central.com/list-the-files-in-a-folder-and-subfolders.html
'Set a reference to Microsoft Scripting Runtime by using
'Tools > References in the Visual Basic Editor (Alt+F11)

'Declare the variables
Dim objFSO  As Scripting.FileSystemObject
Dim objTopFolder As Scripting.Folder
Dim strTopFolderName As String

Dim path()  As Variant ' EDIT THE BELOW PATH LIST FOR WHATEVER YOU NEED!
path() = Array("C:\Users\USERNAME\Desktop\Stuff\New folder", "C:\Users\USERNAME\Desktop\Other Stuff\")

'Insert the headers for Columns
Range("A1").Value = "File Name"
Range("D1").Value = "File Path"

Dim i       As Long
For i = LBound(path) To UBound(path)
    strTopFolderName = path(i)
    'Create an instance of the FileSystemObject
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    'Get the top folder
    Set objTopFolder = objFSO.GetFolder(strTopFolderName)
    'Call the RecursiveFolder routine
    Call RecursiveFolder(objTopFolder, True)
    'Change the width of the columns to achieve the best fit
    Columns.AutoFit
Next i
End Sub

Sub RecursiveFolder(objFolder As Scripting.Folder, _
                    IncludeSubFolders As Boolean)

'Declare the variables
Dim objFile As Scripting.File
Dim objSubFolder As Scripting.Folder
Dim NextRow As Long

'Find the next available row
NextRow = Cells(Rows.Count, "A").End(xlUp).Row + 1

'Loop through each file in the folder
For Each objFile In objFolder.Files
    Debug.Print (objFile)
    If objFile.Type = "Microsoft Excel Worksheet" Then
        Cells(NextRow, "A").Value = objFile.Name
        Cells(NextRow, "D").Value = objFile.path
        NextRow = NextRow + 1
    End If
Next objFile

'Loop through files in the subfolders
If IncludeSubFolders Then
    For Each objSubFolder In objFolder.SubFolders
        Call RecursiveFolder(objSubFolder, True)
    Next objSubFolder
End If

End Sub
BruceWayne
  • 22,923
  • 15
  • 65
  • 110
  • I'm a bit confused: `path() = Array("C:\Users\USERNAME\Desktop\Stuff\New folder", "C:\Users\USERNAME\Desktop\Other Stuff\")` would mean I would have to put a list of all possible paths, the thing is, the folders and paths that follow are really long. I use a form to basically get to this point: `C:\Users\AButler\OneDrive - Sempra Energy\User Folders\Desktop\Field Inspection\SLIP Packages Prepared\Inland\Murrieta\Menifee\MNFE 3` and there can be multiple atlases (the MNFE) with path names listed, and `\Inland\Murrieta\Menifee\` can change based on what Region\district\city a person is in. – R.E.L. Jun 04 '18 at 19:33
  • It's the primary reason I use the form to give me a list of atlas file paths, because though there are a lot of subfolders (called work orders) in the atlases, there are far less than if I took a step back. There are hundreds of atlases per city, many cities per district, many districts per region, and several regions (hence C:\(many folders later)\Region\District\City\Atlas) – R.E.L. Jun 04 '18 at 19:37
  • 1
    @R.E.L. - If you have the paths, via a form, I'm not sure what you're asking. You could feed those results in to a list, or tweak my answer to work with each result of a form. Alternatively, you can just use `C:\Users\...\Inland` as the folder, and let it loop through that and it's subfolders? – BruceWayne Jun 04 '18 at 19:38
  • The results are already in a list of paths, which changes based on user inputs. Someone may be working in `C:\...\NORTHERN\CALIMESA\PALISADES\ ` and need the excel filepaths from the subfolders in atlases PAL 1, PAL 5, and PAL 932. That's 3 paths. That's why I use the form to determine the paths of the atlases. That list of paths will change. It's why I haven't used a static address like `C:\Users\...\Inland`. I could make several hundred excels for each city (1,000+) then several 100 lines of code like `"C:\...\NORTHERN\CALIMESA\PALISADES\" & textbox1.value, etc. etc.` for each atlas in array – R.E.L. Jun 04 '18 at 19:51
  • I don't mean to come off short or uninteligible, I've simply been looking at this for a long time and have tried several hundred types of adapted code to no avail. I thought a simple ` For Each` would suffice, but it hasn't worked. – R.E.L. Jun 04 '18 at 19:54
1

If I understand correctly, your requirements are as follows:

  • Begin with a set of root paths
  • Iterate recursively through all the files in each root path
  • For each file in the resulting collection, if it's an Excel file, add to final list for further processing

Let's start with the first two points. I would suggest the following code (make sure to add a reference to Microsoft Scripting Runtime via Tools -> References... in the VBA editor menus):

Public Function GetFiles(ByVal roots As Variant) As Collection
    Select Case TypeName(roots)
        Case "String", "Folder"
            roots = Array(roots)
    End Select

    Dim results As New Collection
    Dim fso As New Scripting.FileSystemObject

    Dim root As Variant
    For Each root In roots
        AddFilesFromFolder fso.GetFolder(root), results
    Next

    Set GetFiles = results
End Function

Private Sub AddFilesFromFolder(folder As Scripting.folder, results As Collection)
    Dim file As Scripting.file
    For Each file In folder.Files
        results.Add file
    Next

    Dim subfolder As Scripting.folder
    For Each subfolder In folder.SubFolders
        AddFilesFromFolder subfolder, results
    Next
End Sub

The GetFiles function can be called by passing in a single string (or Folder):

Debug.Print GetFiles("c:\users\win8\documents").Count

or anything that can be iterated over with For Each -- an array, collection, Dictionary, or even an Excel Range object:

Dim allFiles As Collection
Set allFiles = GetFiles(ws2.Range("A1:A" & Rows.Count).End(xlUp)) 'from question

GetFiles as it stands is flexible for many use cases, and doesn't use any Excel-specific objects. In order to limit the results to Excel files only, you can create a new collection, and only add the Excel files into the new collection:

'You could filter by the File object's Type property
Sub GetExcelFilesByType()
    Dim allFiles As Collection
    Set allFiles = GetFiles(ws2.Range("A1:A" & Rows.Count).End(xlUp)) 'from question

    Dim excelFiles As New Collection
    Dim file As Scripting.File
    For Each file In allFiles
        If file.Type = "Microsoft Excel Worksheet" Then excelFiles.Add file
    Next
End Sub

' Or you could filter by extension, using the FileSystemObject.GetExtensionName method
Sub GetExcelFilesByExtensionName()
    Dim allFiles As Collection
    Set allFiles = GetFiles(ws2.Range("A1:A" & Rows.Count).End(xlUp)) 'from question

    Dim excelFiles As New Collection
    Dim fso As New Scripting.FileSystemObject
    Dim file As Scripting.File
    For Each file In allFiles
        Select Case fso.GetExtensionName(file.path)
            Case "xls", "xlsb", "xlsm"
                excelFiles.Add file
        End Select
    Next
End Sub

Either will get you a Collection of File objects, of only Excel files, from the set of root folders.


Notes

  • This code is recursively adding all the files (not just Excel files) into one collection (in GetFiles) and then filtering out the non-Excel files into a new collection. This might be less performant than adding only Excel files into the original collection, but that would limit GetFiles to only this scenario.
  • If you want to paste the results into an Excel worksheet, you could iterate through excelFiles and paste each path into the sheet. Alternatively, you might convert excelFiles into an array, and use the Excel Range object's Value property to set all the values from the array, without using a For Each.

References

Microsoft Scripting Runtime

VBA

Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
  • Still working through to make sure I understand this answer, but it's looking more and more like what I'm looking for! Thank you a million times over--I bow to your superior knowledge. – R.E.L. Jun 06 '18 at 12:48
  • I can't get it to work. It's not the code as I understand from a logic standpoint how it's meant to work. I'm realizing maybe I'm just incapable of understanding how to implement this at my current level. I've read through everything you provided, copied, pasted, tested over and over and I can't even get it to run. Your answer is correct and should work--I just can't seem to grasp it. – R.E.L. Jun 06 '18 at 14:46
  • @R.E.L.Take your time, and have patience with yourself; everyone who has ever programmed has been where you are. You say you can't even get it to run -- what happens when you try to run it? There are three levels of error: 1. **compiler error** -- the computer cannot parse the instructions you're giving it; 2. **runtime error** -- the computer parses and starts executing the instructions, but it gets to an instruction that causes a problem; and 3. **logic errors** -- the computer successfully executes all the instructions, but does something unexpected or returns an invalid result. Which ... – Zev Spitz Jun 06 '18 at 14:56
  • @R.E.L.... of the three is happening here? Also, if you'd like to open a chat session, I am available now. – Zev Spitz Jun 06 '18 at 14:58
  • would love to, can't figure out how. Hopefully you are still available once I do figure out how to chat on here – R.E.L. Jun 06 '18 at 15:35
  • @R.E.L. I think you can navigate to https://chat.stackoverflow.com/rooms/info/172611/https-stackoverflow-com-a-50710138-111794 and click on **join this room** – Zev Spitz Jun 06 '18 at 15:41
  • This is the error I'm seeing [image link](https://s15.postimg.cc/rpvz1vwcb/Capture.png). My understanding is thus: Public function defines what types of things (roots) will be in the Getfiles collection, and then sets the roots as an array. For each root in roots a result is returned from fso and added into a collection (results). Getfiles is then set equal to this collection of results. I've attempted to look up more about Select Case, because I have a feeling I'm doing something wrong there. in Listfiles, I set getfiles equal to a range of cells containing paths... – R.E.L. Jun 06 '18 at 15:52
  • @R.E.L. Are you getting **Run-time error '13': Type mismatch**? Could [this](https://stackoverflow.com/questions/51272430/type-mismatch-when-passing-string-variable-into-variant-parameter-and-reassign) be the issue? – Zev Spitz Jul 10 '18 at 19:14