I want to make use of dynamic arrays in VBA in order to save the found strings and use the FileSystemObject to find the files.
The code I currently use is this one
Private Sub cmdStartSearch_Click()
Dim resultList(0) As String
Call FindFile(resultList, ".png", "Q:\", True)
End Sub
Private Sub FindFile(ByRef resultList() As String, target As String, ByVal aPath As String, useSubfolders As Boolean)
Dim myFileSystemObject As FileSystemObject, curFolder As folder, folder As folder
Dim folColl As Folders, file As file, fileColl As Files
Set myFileSystemObject = New FileSystemObject
Set curFolder = myFileSystemObject.GetFolder(aPath)
Set folderList = curFolder.SubFolders
Set fileList = curFolder.Files
For Each file In fileList
ReDim Preserve resultList(1 To UBound(resultList) + 1) As String
If InStr(file.Name, target) > 0 Then
resultList(UBound(resultList)) = file.Name
Debug.Print file.Name
End If
Next
If useSubfolders Then
For Each folder In folderList
DoEvents 'Yield execution so other events may be processed
If Not foundTarget Then
FindFile resultList, target, folder.Path, useSubfolders
End If
Next
End If
Set myFileSystemObject = Nothing
Set curFolder = Nothing
Set folderList = Nothing
Set fileList = Nothing
End Sub
This fails however with Array is fixed or or temporarily locked.
How can I get around this problem, or solve the original one?
I feel this has a far better/easier solution. What you do is loop through the file list as you did but then return a long string with the file names and then finally break it using the Split function which will yield you a String array anyway.
Your code could simply change as,
Private Sub cmdStartSearch_Click()
Dim resultList() As String
resultList = Split(FindFile(".png", "Q:\", True), ";")
End Sub
Private Function FindFile(target As String, ByVal aPath As String, useSubfolders As Boolean) As String
Dim retStr As String
Dim myFileSystemObject As FileSystemObject, curFolder As folder, folder As folder
Dim folColl As Folders, file As file, fileColl As Files
Set myFileSystemObject = New FileSystemObject
Set curFolder = myFileSystemObject.GetFolder(aPath)
Set folderList = curFolder.SubFolders
Set fileList = curFolder.Files
For Each file In fileList
If InStr(file.Name, target) > 0 Then
retStr = retStr & ";" & file.Name
Debug.Print file.Name
End If
Next
If useSubfolders Then
For Each folder In folderList
DoEvents 'Yield execution so other events may be processed
If Not foundTarget Then
retStr = retStr & ";" & FindFile(target, folder.Path, useSubfolders)
End If
Next
End If
Set myFileSystemObject = Nothing
Set curFolder = Nothing
Set folderList = Nothing
Set fileList = Nothing
If Len(retStr) > 0 Then retStr = Right(retStr, Len(retStr)-1)
FindFile = retStr
End Function
Why are the variables folderList, fileList, foundTarget not declared in the context?
Related
I currently have this code that finds all files and folders and writes it to a table. The problem is is that is it sometimes slow.
The code below is modified so that it writes to an array but I am having issues passing the array on when the code loops.
Ultimately, I would like the array to pass on to the first sub so that I can transpose it into the table at once.
Sub FileAndFolder()
Dim FSOLibrary As Object
Dim FSOFolder As Object
Dim FolderName As String
Dim FilesTbl As ListObject
Set FilesTbl = Range("FilesTbl").ListObject
'Set the folder name to a variable
FolderName = Left$(ActiveWorkbook.Path, InStrRev(ActiveWorkbook.Path, "\"))
'Set the reference to the FSO Library
Set FSOLibrary = CreateObject("Scripting.FileSystemObject")
'Another Macro must call LoopAllSubFolders Macro to start
LoopAllFolders FSOLibrary.GetFolder(FolderName)
'return TempArray here and paste into table
'Range(FilesTbl.ListColumns("File Name").DataBodyRange(1)) = TempArray
End Sub
Sub LoopAllFolders(FSOFolder As Object)
'Don’t run the following macro, it will be called from the macro above
Dim FSOSubFolder As Object
Dim FSOFile As Object
Dim FolderPath As String
Dim FileName As String
Dim TempArray() As String
'For each subfolder call the macro
For Each FSOSubFolder In FSOFolder.SubFolders
LoopAllFolders FSOSubFolder
Next
'For each file, print the name
For Each FSOFile In FSOFolder.Files
'Insert the actions to be performed on each file
FileName = FSOFile.Name
FolderPath = FSOFile.ParentFolder
If Left(FileName, 2) = "~$" Then GoTo NEXTINLOOP
ReDim Preserve TempArray(0 To 3, 0 To i)
TempArray(0, i) = FileName
TempArray(1, i) = FolderPath & "\" & FileName 'file
TempArray(2, i) = FolderPath 'folder
TempArray(3, i) = FolderPath & "\" & FileName 'showpath
i = i + 1
NEXTINLOOP:
Next
End Sub 'TempArray and i clears here
Thanks.
You either need to declare a variable at the module level so that the list of folder information is available to all methods in the module, or change 'LoopAllFolders' to a Function so that you can return the information you have collated.
The function below will return a Variant which contains an array of arrays (normally called a jagged array). You access the jagged array using this nomenclature
Varname(x)(y)
You will need a variable in the calling method to receive the jagged array
e.g.
Dim myFileInfo as Variant
MyFileInfo = LoopAllFolders(FSOLibrary.GetFolder(FolderName))
Here is the updated function
Public Function LoopAllFolders(FSOFolder As Scripting.FileSystemObject) As Variant
'Don’t run the following macro, it will be called from the macro above
Dim FileInfo As Scripting.Dictionary: Set myFileInfo = New Scripting.Dictionary
'For each subfolder call the macro
Dim FSOSubFolder As Scripting.Folder
For Each FSOSubFolder In FSOFolder.SubFolders
LoopAllFolders FSOSubFolder
Next
'For each file, print the name
Dim FSOFile As Scripting.File
For Each FSOFile In FSOFolder.Files
'Insert the actions to be performed on each file
Dim FileName As String
FileName = FSOFile.Name
Dim FolderPath As String
FolderPath = FSOFile.ParentFolder
If Not Left(FileName, 2) = "~$" Then
myFileInfo.Add Array(FileName, FolderPath & "\" & FileName, FolderPath, FolderPath & "\" & FileName)
End If
Next
LoopAllFolders = myFileInfo.Items
End Function
The above code may not be perfect but at least it points you in the right direction.
Based on your question, you might do well by working through a VBA tutorial as functions are fairly fundamental, and if you are unaware of them......
To help you on your journey I'd also recommend installing the fantastic and free RubberDuck addin.
Create a List of Files all subfolders of a Folder
I do not understand what you're doing with FilesTbl, so I modified your solution to create a new workbook with the result. Surely you will figure out how to apply it to the table.
A Quick Fix
Option Explicit
Sub FileAndFolder()
Dim FSOLibrary As Object
Dim FSOFolder As Object
Dim FolderName As String
Dim FilesTbl As ListObject
'Set FilesTbl = Range("FilesTbl").ListObject
'Set the folder name to a variable
FolderName = Left$(ActiveWorkbook.Path, InStrRev(ActiveWorkbook.Path, "\"))
'Set the reference to the FSO Library
Set FSOLibrary = CreateObject("Scripting.FileSystemObject")
Dim TempArray() As Variant ' ByRef
'Another Macro must call LoopAllSubFolders Macro to start
LoopAllFolders FSOLibrary.GetFolder(FolderName), TempArray
'return TempArray here and paste into table
With Workbooks.Add
With ActiveSheet.Range("A1").Resize(UBound(TempArray, 2), UBound(TempArray))
.Value = Application.Transpose(TempArray)
End With
.Saved = True
End With
'Range(FilesTbl.ListColumns("File Name").DataBodyRange(1)) = TempArray
End Sub
Sub LoopAllFolders(FSOFolder As Object, ByRef TempArray As Variant)
'Don’t run the following macro, it will be called from the macro above
Dim FSOSubFolder As Object
Dim FSOFile As Object
Dim FolderPath As String
Dim FileName As String
Dim i As Long
'Dim TempArray() As String
'For each subfolder call the macro
For Each FSOSubFolder In FSOFolder.SubFolders
LoopAllFolders FSOSubFolder, TempArray
Next
'For each file, print the name
For Each FSOFile In FSOFolder.Files
'Insert the actions to be performed on each file
FileName = FSOFile.Name
FolderPath = FSOFile.ParentFolder
If Left(FileName, 2) = "~$" Then GoTo NEXTINLOOP
i = i + 1
ReDim Preserve TempArray(1 To 4, 1 To i)
TempArray(1, i) = FileName
TempArray(2, i) = FolderPath & "\" & FileName 'file
TempArray(3, i) = FolderPath 'folder
TempArray(4, i) = FolderPath & "\" & FileName 'showpath
NEXTINLOOP:
Next
End Sub 'TempArray and i clears here
Simplified Excel Code:
Public MyFile As String
Public varSheetA As Variant
Public SelRangeA As Range
Public wsCopy As Excel.Worksheet
Sub SelectFile_Click()
MyFile = Application.GetOpenFilename() 'Aquire filepath from user
If (MyFile <> "False") Then
Range("B1").Value = "File Found"
End If
End Sub
Sub LoadFile_Click()
Dim WbOne As Workbook
Dim strRangeToCheck As String
strRangeToCheck = "A1:T2000"
Set WbOne = Workbooks.Open(MyFile) 'Open that file
Set wsCopy = WbOne.Worksheets(1) 'Try to copy
Set varSheetA = wsCopy.Range(strRangeToCheck) 'Try to copy
Set SelRangeA = wsCopy.Range(strRangeToCheck) 'Try to copy
WbOne.Close 'This is where we lose the references & values
End Sub
Sub DisplayFile_Click()
Range("A4").Value = varSheetA(1, 1)
End Sub
The end result of this program is to have the values from WorkSheet(1) in a Range or Variant Array so that I can edit and display them as needed and eventually copy the values back into the original file. However when I run this code, all the Public variables I initialize are empty when LoadFile_Click exits (more specifically when WbOne closes.
Previously my code looked like varSheetA = WbOne.Worksheet(1).Range(strRangeToCheck) although I'm currently in the process of testing different methods because that way didn't seem to work.
Anyone see any fundamental problems with what I'm trying to do? Thanks!
Option Explicit
Public MyFile As String
Public varSheetA As Variant
Sub SelectFile_Click()
MyFile = Application.GetOpenFilename() 'Aquire filepath from user
If (MyFile <> "False") Then
Range("B1").Value = "File Found"
End If
End Sub
Sub LoadFile_Click()
Const strRangeToCheck As String = "A1:T2000"
With Workbooks.Open(MyFile)
varSheetA = .Worksheets(1).Range(strRangeToCheck).Value
.Close False
End With
End Sub
Sub DisplayFile_Click()
Range("A4").Value = varSheetA(1, 1)
End Sub
Currently have a working script that uses FSO, but it also opens .xlsm files within my working directory. I would like to to only open .txt files.
I found this code that should work, however I can't figure out how to apply it to my situation:
Sub test()
' Loop thru all files in the folder
folder = ActiveWorkbook.path
path = folder & "\*.txt"
Filename = Dir(path)
Do While Filename <> ""
'insert other functions here
Loop
End Sub
My Code (Works, but also opens .xlsm files, which I don't want it to do):
Option Explicit
Sub Initialize_barcode_lookup_Array_test()
Dim fso As FileSystemObject
Dim folder As String, path As String, count_txt_files As Long, Filename As String
Dim folder2 As folder
Dim file As file
Dim FileText As TextStream
Dim TextLine As String
Dim Items() As String
Dim ShippingPlanArray() As String
Dim i As Long, j As Long, k As Long
Dim cl As Range
Dim fName
Dim row As Long, column As Long
Dim shipping_plan As Long 'Number of shipping plans text files imported
Dim barcode_Lookup() As String
Dim lastRow As Long
Dim longest_lastRow As Long
Dim counter As Long
Dim FNSKU_Input As String
'<<<< Creating FSO Object >>>>>
'Define longest_lastRow
longest_lastRow = 0
'Define i (References the text file open)
i = 0
' Get a FileSystem object
Set fso = New FileSystemObject
' get the directory you want
Set folder2 = fso.GetFolder(ActiveWorkbook.path)
' Loop only while the files being opened are .txt files:
For Each file In folder2.Files
row = 0
column = 0
Set FileText = file.OpenAsTextStream(ForReading)
Do Until FileText.AtEndOfStream
fName = FileText.ReadLine
'Parse data by tabs (text-tab delimited) into Items() array
Items() = Split(fName, vbTab)
' Redimension Preserve the ShippingPlanArray()
' NOTE: You can only Redimension preserve the last dimension of a multi-dimensional array
' (In this case: row)
ReDim Preserve ShippingPlanArray(9, row)
'Read Data into an Array Variable
For column = LBound(Items) To UBound(Items)
'MsgBox Items(column)
ShippingPlanArray(column, row) = Items(column)
Next column
row = row + 1
Loop
Next file
End Sub
I don't know if fso support an overloaded method for GetFolder where you can specify the pattern. If it does, use that i.e. GetFolder(Path, "*.txt"). If it doesn't, can you not just add a simple condition to check the file extension in your 'for each' loop and only process the ones that ends in '.txt'.
Update:
Try this:
For Each file In folder2.Files
Dim extension As String
extension = LCase(Mid$(file, InStrRev(file, ".")))
If extension = ".txt" Then
Debug.Print "TEST"
End If
Next
I've tested it and it works as expected.
I have multiple "datasheet" text files that are used with a program at work and need to harvest values from them and combine it all into a spreadsheet.
The text files are formatted as such:
[File]
DescText = "1756-IF16H 16 Channel Hart Analog Input Module";
CreateDate = 04-07-10;
CreateTime = 10:29;
Revision = 1.1;
HomeURL = "http://www.ab.com/networks/eds/XX/0001000A00A30100.eds";
[Device]
VendCode = 1;
VendName = "Allen-Bradley";
ProdType = 10;
ProdTypeStr = "Multi-Channel Analog I/O with HART";
ProdCode = 163;
MajRev = 1;
MinRev = 1;
ProdName = "1756-IF16H/A";
Catalog = "1756-IF16H/A";
Icon = "io_brown.ico";
The Tags are consistent through all the files and each lines ends with a semicolon [ ; ] so I'm assuming this should be pretty easy. I need to pull "DescText","VendCode","ProdType","MajRev","MinRev",and"ProdName" into separate columns.
There are about 100 individual data files, each with a nonsensical filename, so I'm looking to have the macro just go through and open each one in the folder.
Thanks for the help, here is the solution I came up with for this specific problem
Sub OpenFiles()
Dim MyFolder As String
Dim MyFile As String
MyFolder = "[directory of files]"
MyFile = Dir(MyFolder & "\*.txt")
Dim filename As String
Dim currentrow As Integer: currentrow = 2
Do While Myfile <> "" 'This will go through all files in the directory, "Dir() returns an empty string at the end of the list
'For i = 1 To 500 'this was my debug loop to only go through the first 500 files at first
filename = MyFolder & "\" & MyFile 'concatinates directory and filename
Open filename For Input As #1
Do Until EOF(1) 'reads the file Line by line
Line Input #1, textline
'Text = Text & textline
If textline = "" Then 'error handler, if line was empty, ignore
Else
Dim splitline() As String
splitline() = Split(textline, "=", -1, vbTextCompare)
'because of how my specific text was formatted, this splits the line into 2 strings. The Tag is in the first element, the data in the second
If IsError(splitline(0)) Then
splitline(0) = ""
End If
Select Case Trim(splitline(0)) 'removes whitespace
Case "DescText"
currentrow = currentrow + 1
'files that didn't have a description row, resulted in empty rows in the spreadsheet.
ActiveSheet.Range("A" & currentrow).Cells(1, 1).Value = splitline(1)
Case "Revision"
ActiveSheet.Range("B" & currentrow).Cells(1, 1).Value = splitline(1)
Case "ProdCode"
ActiveSheet.Range("C" & currentrow).Cells(1, 1).Value = splitline(1)
Case "ProdType"
ActiveSheet.Range("D" & currentrow).Cells(1, 1).Value = splitline(1)
'...etc. etc... so on for each "tag"
End Select
End If
Loop
Close #1
MyFile = Dir() 'reads filename of next file in directory
'currentrow = currentrow + 1
'Next i
Loop
End Sub
here how I would solve the complete task:
Private Sub importFiles(ByVal pFolder As String)
' create FSO
Dim oFSO As Object
Set oFSO = CreateObject("Scripting.FileSystemObject")
' create folder
Dim oFolder As Object
Set oFolder = oFSO.getFolder(pFolder)
' go thru the folder
Dim oFile As Object
For Each oFile In oFolder.Files
' check if is a text file
If UCase(Right(oFile.Name, 4)) = ".TXT" Then
Debug.Print "process file: " & oFolder.Path & "\" & oFile.Name
readFile oFolder.Path & "\" & oFile.Name
End If
Next
' clean up
Set oFolder = Nothing
Set oFSO = Nothing
End Sub
Private Sub readFile(ByVal pFile As String)
' get new file handle
Dim hnd As Integer
hnd = FreeFile
' open file
Open pFile For Input As hnd
Dim sContent As String
Dim sLine As String
' read file
Do Until EOF(hnd)
Line Input #hnd, sLine
sContent = sContent & sLine
Loop
' close file
Close hnd
' extract requiered data
Debug.Print getValue(sContent, "ProdName")
Debug.Print getValue(sContent, "DescText")
End Sub
Private Function getValue(ByVal pContent As String, ByVal pValueName As String) As String
Dim sRet As String
sRet = ""
If InStr(pContent, pValueName) Then
pContent = Mid(pContent, InStr(pContent, pValueName) + Len(pValueName) + 2)
sRet = Left(pContent, InStr(pContent, ";") - 1)
sRet = Trim(sRet)
End If
getValue = sRet
End Function
Overall the solution contains 3 different procedures:
importFiles reads the content of a given directory (which has to be handed over as parameter) and if it finds a .txt file it calls readFile() and passes the full path of the file to it
readFile() opens the text file and stores the content in a string variable. After this is done it calles getValue for each value you are interessted in.
getValue analyses the given content and extractes the given value.
Simply adjust the calls of getValue() so that you get all values you are interessted in and store them instead of showing via debug.print and call the first procedure with the right directory like importFiles "C:\Temp"
I have a VBS file that I am trying to use to determine what folders and files are in a certain directory. I believe I have the code written correctly, but whenever I try to write out the file or current directory I get a blank text document with nothing but the root directory written out. Any advice would be greatly appreciated.
Dim NewFile
Function GetFolders (strFolderPath)
Dim objCurrentFolder, colSubfolders, objFolder, files
Set objCurrentFolder = objFSO.GetFolder(strFolderPath)
Set colSubfolders = objCurrentFolder.SubFolders
For Each objFolder In colSubfolders
NewFile.WriteLine(" - " & objFolder.Path)
Set files = folder.Files
For each folderIdx In files
NewFile.WriteLine(" - "& folderIdx.Name)
Next
Call GetFolders (objFolder.Path)
Next
End Function
Dim fso, sFolder
Set fso = CreateObject("Scripting.FileSystemObject")
sFolder = Wscript.Arguments.Item(0)
If sFolder = "" Then
Wscript.Echo "No Folder parameter was passed"
Wscript.Quit
End If
Set NewFile = fso.CreateTextFile(sFolder&"\FileList.txt", True)
NewFile.WriteLine(sFolder)
Call GetFolders(sFolder)
NewFile.Close
You haven't payed sufficient attention to your variable naming. Your script is a good example of the reason why all VBScripts should start with the line:-
Option Explicit
This would highlight all the variables that haven't been declared which in turn will point out typos and inconsistencies in variable naming. Here is how I would write it:-
Option Explicit
Dim msFolder : msFolder = Wscript.Arguments.Item(0)
If msFolder = "" Then
Wscript.Echo "No Folder parameter was passed"
Wscript.Quit
End If
Dim mfso : Set mfso = CreateObject("Scripting.FileSystemObject")
Dim moTextStream : Set moTextStream = mfso.CreateTextFile(msFolder & "\FileList.txt", True)
moTextStream.WriteLine(msFolder)
WriteFolders mfso.GetFolder(msFolder)
moTextStream.Close
Sub WriteFolders(oParentFolder)
Dim oFolder
For Each oFolder In oParentFolder.SubFolders
moTextStream.WriteLine(" - " & oFolder.Path)
Dim oFile
For Each oFile In oFolder.Files
moTextStream.WriteLine(" - " & oFile.Name)
Next
WriteFolders oFolder
Next
End Sub