Add all Outlook folders into an array - arrays

I would like to recursively go through all my outlook folders, add them into an array and return it with a function, so I can call it from multiple places.
Type of object I need to add is Outlook.Folder, so I started with
Dim output() As Outlook.Folder
which provided me with a streak of error #91.
I found I can declare arrays
Dim output() As Variant
which worked in the following sequence:
Dim SubFolderCount As Integer
SubFolderCount = Folder.Folders.Count
Dim output() As Variant
ReDim output(SubFolderCount)
Dim c As Integer
c = -1
'Debug.Print Folder.Name
'GetSubfolders = Folder.Folders.Count
For Each SubFolder In Folder.Folders
c = c + 1
output(c) = SubFolder
'GetSubfolders = GetSubfolders + GetSubfolders(SubFolder)
Next SubFolder
GetSubfolders = output
I found whatever I added to this Variant array is turned to type Variant/String.
Just to be sure, I returned that array from my function, looped through the result and made sure that I cannot use the array contents as Outlook.Folder type, I can only use it as String.
Is it possible, that only primitives can be assigned into an array?
I'm pretty sure I've seen examples where they were adding worksheets.

You missed "set":
set output(c) = SubFolder
That being said, I'd rather store folder entry ids (string) and open the folders on demand using Namespace.GetFolderFromID. Once a folder is processed, you can release it by setting it to Nothing.

This code uses a dictionary to store the local folder name and path
Sub RecurseFolderStructure()
' Requires Reference: Microsoft Scripting Runtime
Dim ThisNamespace As Outlook.NameSpace: Set ThisNamespace = Application.GetNamespace("MAPI")
Dim Inbox As Outlook.MAPIFolder: Set Inbox = ThisNamespace.GetDefaultFolder(olFolderInbox)
'Dim Cal As Outlook.MAPIFolder: Set Cal = ThisNamespace.GetDefaultFolder(olFolderCalendar)
Dim Junk As Outlook.MAPIFolder: Set Junk = ThisNamespace.GetDefaultFolder(olFolderJunk)
Dim BaseFolder As Outlook.MAPIFolder: Set BaseFolder = Inbox '.Folders("SubFolder1\SubFolder2...")
Dim Folders As Scripting.Dictionary: Set Folders = New Scripting.Dictionary
AddSubFolders BaseFolder, Folders
Dim f As Outlook.MAPIFolder
Dim Key As Variant
For Each Key In Folders
'Further Code; for eg.
Set f = Folders(Key)
Debug.Print f.FolderPath
Next Key
Folders.RemoveAll
Set Folders = Nothing
End Sub
Function AddSubFolders(ByRef CurrentFolder As Outlook.MAPIFolder, ByRef dict As Scripting.Dictionary)
Dim Folder As Outlook.MAPIFolder
If Not dict.Exists(CurrentFolder.FolderPath) Then dict.Add CurrentFolder.FolderPath, CurrentFolder
If CurrentFolder.Folders.Count > 0 Then
For Each Folder In CurrentFolder.Folders
AddSubFolders Folder, dict
Next
End If
End Function

Related

Open files from array

I wrote a code where I want a user to select multiple files that code will loop through.
I can't get the loop to work as I am not defining the name right.
Sub RFPDataimporttest() 'select multiple files
Dim Files As Workbook
Dim Fname As Variant
Dim SrcWbk As Workbook
Dim DestWbk As Workbook
Dim i As Integer
Set DestWbk = ThisWorkbook
'choose files and define them as array
Fname = Application.GetOpenFilename(FileFilter:="Excel Files (*.xls*), *.xls*", Title:="Select alla RFP filer från leverantörer", MultiSelect:=True)
If IsArray(Fname) Then
For i = LBound(Fname) To UBound(Fname)
'this where i have a problem. how do i open each file in an array?
Set SrcWbk = Workbooks.Open(Fname)
SrcWbk.Sheets("1. General Information").Range("L8").Copy
With DestWbk.Worksheets("Sheet1").Cells(2, Columns.Count).End(xlToLeft).Offset(-1, 1)
.PasteSpecial Paste:=xlPasteColumnWidths
.PasteSpecial Paste:=xlPasteValues
End With
SrcWbk.Sheets("2. Product information").Range("Z1:AB91").Copy
With DestWbk.Worksheets("Sheet1").Cells(2, Columns.Count).End(xlToLeft).Offset(0, 1)
.PasteSpecial Paste:=xlPasteColumnWidths
.PasteSpecial Paste:=xlPasteValues
End With
SrcWbk.Close False
Next i
End If
End Sub
The result is an array - you are already checking that and looping over it. Simply use i as index:
Set SrcWbk = Workbooks.Open(Fname(i))
(note that the result is even an array when user selects only one file, so code will work for that case also)

VBA Copy/paste static ranges to static ranges in dynamically built array of worksheets

I am trying to copy the cell values of 4 static ranges from one worksheet and paste those values into the cells within 4 static ranges for each worksheet that is included in my dynamically built list.
Here is my code:
Sub retpsh()
Dim Home As Worksheet: Set Home = Worksheets("Home")
Dim s3 As Worksheet: Set s3 = Worksheets("Sheet3")
Dim s9 As Worksheet: Set s9 = Worksheets("Sheet9")
Dim s5 As Worksheet: Set s5 = Worksheets("Sheet5")
Dim s7 As Worksheet: Set s7 = Worksheets("Sheet7")
Dim Back As Worksheet: Set Back = Worksheets("Home Backstage")
Dim wsarray As Variant
Dim message As String: message = Back.Cells(18, 13)
Dim ws As Variant
If Back.Cells(18, 19) = 0 Then
If MsgBox("Nothing selected!", vbOKOnly) = vbOK Then Exit Sub
Else
If MsgBox(message + " Do you wish to continue?", vbYesNo) = vbNo Then Exit Sub
wsarray = Array(Back.Cells(18, 19)) 'Doesn't work properly
End If
For Each ws In wsarray
ws.Range("C2:C5", "C8:C11", "C13", "B18:C22") = Worksheets("Home Backstage").Range("B1:B4", "B6:B9", "B11", "B18:C22").Value '''''450 error with or without "Set" before line
Next ws
End Sub
First, wsarray = Array(Back.Cells(18, 19)) does not work as it is not parsing that cell value, it takes the whole thing as a single value (i.e. "s3","s5","s7","s9" or whatever the cell value is). Back.Cells(18, 19) has a formula that builds the list based on the 4 options selected on the "Home" page. The formula builds the list to be any of the 16 combinations of: s3, s5, s7, or s9. The final cell value looks like: "s3" or like "s3","s7","s9". Using just Dim wsarray() or wsarray() = doesn't change the behavior. Any time I use wsarray() without Array(...) I get a '13' Type mismatch error.
Is this a matter of dynamically defining the dimensions of the array
first?
If not, is there a way to parse the cell value into the array?
If not, how would I build the array list dynamically in VBA?
Second, even bypassing the above issues by specifying the array manually, I still get a '450' Wrong number of arguments or invalid property assignment error. I know setting one range with multiple nonconsecutive cells = another range setup the same way works fine (e.g. Range("K15:C18","C29") = Range("C1:C4","C15")), so:
Why is that syntax not working within the For loop?
Third, the statement within the For loop needs the fully qualified name Worksheets("Home Backstage") and did not accept the alias Back.
Why does the For not accept the worksheet alias Back?
Does the For loop act outside of the Dim's that are setup prior to it thus needing the Dim within the loop?
I know I could get around all of this with a bunch of If statements and referencing the state of each of the 4 options on the "Home" page to determine which worksheets to copy to, but I don't like that idea. That doesn't seem to be the right way to go about this, having a bunch of duplicated code with slight changes to predicates hence my desire to use an array. Nevertheless, my questions are more "why" than "how", but I appreciate any guidance or explanations all the same!
Here's another option, very similar to Scott's, but making use of a few more arrays to handle your ranges:
Sub retpsh()
Dim Back As Worksheet: Set Back = Worksheets("Home Backstage")
Dim wsarray As Variant
Dim fromRangeArray As Variant
Dim toRangeArray As Variant
Dim message As String: message = Back.Cells(18, 13)
Dim ws As Variant
If Back.Cells(18, 19) = 0 Then
If MsgBox("Nothing selected!", vbOKOnly) = vbOK Then Exit Sub
Else
If MsgBox(message + " Do you wish to continue?", vbYesNo) = vbNo Then Exit Sub
wsarray = Split(Back.Cells(18, 19).Value, ",")
End If
'Could do this into a single multidimensional array if you are a sadist
fromRangeArray = Array("B1:B4", "B6:B9", "B11", "B18:C22")
toRangeArray = Array("C2:C5", "C8:C11", "C13", "B18:C22")
'loop through sheet names
For Each ws In wsarray
For rngIndex = 0 To UBound(fromRangeArray)
Sheets(ws).Range(toRangeArray(rngIndex)).Value = Back.Range(fromRangeArray(rngIndex)).Value
Next rngIndex
Next ws
End Sub
You will need to enter the actual sheet names in the cell:
sheet9,sheet7
equating a string or variable with a variable name does not work, so you will need to loop through the array created by Split and make sure the sheet exists, then use it.
You cannot use range with more than two cell references. Range expects a start and a finish.
Sub retpsh()
Dim Home As Worksheet: Set Home = Worksheets("Home")
Dim Back As Worksheet: Set Back = Worksheets("Home Backstage")
Dim wsarray() As String
Dim message As String: message = Back.Cells(18, 13)
Dim i As Long
If Back.Cells(18, 19) = 0 Then
If MsgBox("Nothing selected!", vbOKOnly) = vbOK Then Exit Sub
Else
If MsgBox(message + " Do you wish to continue?", vbYesNo) = vbNo Then Exit Sub
wsarray = Split(Back.Cells(18, 19).Value, ",")
End If
For i = LBound(wsarray) To UBound(wsarray)
If Not IsError(Application.Evaluate("'" & wsarray(i) & "'!A1")) Then
With Worksheets(wsarray(i))
.Range("C2:C5").Value = Back.Range("B1:B4").Value
.Range("C8:C11").Value = Back.Range("B6:B9").Value
.Range("C13").Value = Back.Range("B11").Value
.Range("B18:C22").Value = Back.Range("B18:C22").Value
End With
End If
Next i
End Sub

Want to check if there is a worksheet with the same name as the file

Want to check if there is a worksheet with the same name as the file.
At the moment I have filnam opening the files as an array but want it to cycle through the code and see if there is a worksheet with the same name.
I have used a split to remove the path name and the extension but cant get it to check.
I apologise for the messiness of the code. Been trying to get it sorted then Ill tidy it up. There is more code but that isnt required for this as I want it to run that code if there isnt a match.
Please can you help?
Sub sort_it_out()
Dim wb1 As Workbook
Dim wb2 As Workbook
Dim Sheet As Worksheet
Dim filnam As Variant
On Error GoTo errorhandler
Set wb1 = ActiveWorkbook
ChDir Application.ActiveWorkbook.path
'get files
filnam = Application.GetOpenFilename(FileFilter:="2D Table Formats (*.htm;*.xlsm;*.html),*.htm;*.xlsm;*.html", Title:="Select 2D Table", MultiSelect:=True)
'set the array
If IsArray(filnam) Then 'if at least one file is selected, this will be an Array
'define j as the array
For j = LBound(filnam) To UBound(filnam)
'remove path and extension
Dim s As String, a() As String, p As String
s = filnam(j)
a() = Split(s, "\")
p = Split(a(UBound(a)), ".")(0)
MsgBox "p " & p
'check if worksheet exists
For Each ws_check In ThisWorkbook.Worksheets()
If ws_check.Name = p Then
MsgBox "Its there"
Exit Sub
Else
End If
Next ws_check
'continue code from here
This then runs the code... but its not looping the array for some reason. Only one file at at a time. Can you please help?
It is a little hard to follow your code but does this do what you are trying to do?
I think you were storing the file name in the p variable so my code below would check each worksheet in the workbook to see if they have the same name as the p variable.
Public Sub CompareWorksheetNamesToFiles()
Dim file_name As String
file_name = ActiveWorkbook.Name
Dim ws_check As Worksheet
For Each ws_check In ThisWorkbook.Worksheets()
If ws_check.Name = p Then
Debug.Print ("Do Something")
End If
Next ws_check
End Sub
I have worked it all out now.
This will now open file locations, shorten their paths to just the filename minus the extension, then ws check checks the sheets against the filename and then looper jumps to the next.
Thank you to alwaysdata for helping me out.
Sub sort_it_out()
Dim filnam As Variant
'open file locations
filnam = Application.GetOpenFilename(FileFilter:="2D Table Formats (*.htm;*.xlsm;*.html),*.htm;*.xlsm;*.html", Title:="Select 2D Table", MultiSelect:=True)
'if at least one file is selected, this will be an Array
If IsArray(filnam) Then
For j = LBound(filnam) To UBound(filnam)
'remove pathway and extension from entire filename and path. ie C:\open.txt becomes open.
Dim s As String, a() As String, p As String
s = filnam(j)
a() = Split(s, "\")
p = Split(a(UBound(a)), ".")(0)
'check if worksheet exists against p ... ie if theres a worksheet called open it will goto the next option if not it will continue through code
For Each ws_check In ThisWorkbook.Worksheets()
If ws_check.Name = p Then
MsgBox p & " has already been transfered across. ", vbExclamation 'lets the user know this is already there.
GoTo looper
Else
End If
Next ws_check
'do something here with the code if not found. IE MSGBOX " NOT FOUND "
'jump to this point if there is a match.
looper:
Next
Else
Exit Sub
End If
End Sub

How Can I Make Sure I Only Open Text Files When Working With FSO - VBA

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.

Appending text files in a loop on array only prints 1 letter of first file

I have a folder C:\test\ that has multiple .txt files which I need to append to one output text file. Using FSO and TextStream I can write the files explicitly with no problem in this manner:
Public Sub test()
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
Const Path As String = "C:\test\"
Dim helloWorld As Object
Set helloWorld = FSO.CreateTextFile(FileName:=(Path & "helloworld.txt"), OverWrite:=True, Unicode:=False)
helloWorld.WriteLine FSO.GetFile("C:\test\Product_ID_update.txt").OpenAsTextStream(ForReading).ReadAll
helloWorld.WriteLine FSO.GetFile("C:\test\RPT-4475.txt").OpenAsTextStream(ForReading).ReadAll
helloWorld.Close
End Sub
It works perfectly, but I have hundreds of files to append so it would be crazy to type them all out, so I wrote some code to put all the file names into an array, then loop over each index to generate the file path. Here is the code:
Sub Combine_Text_Files2()
Dim InputDirPath As String
InputDirPath = "C:\test\"
Dim InputFileType As String
InputFileType = "*.txt"
Dim OutputDirPath As String
OutputDirPath = "C:\test\"
Dim OutputFileName As String
OutputFileName = "_CombinedOutput.txt"
Dim InputFileName As String
InputFileName = Dir$(InputDirPath & InputFileType)
Dim FileArray() As String
Dim i As Integer: i = 0
Do Until InputFileName = vbNullString
ReDim Preserve FileArray(0 To i)
FileArray(i) = InputFileName
InputFileName = Dir$
i = i + 1
Loop
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
Dim Stream As Object
Set Stream = FSO.CreateTextFile((OutputDirPath & OutputFileName), OverWrite:=True, Unicode:=False)
Dim FileNameAndPath As String
For i = LBound(FileArray) To UBound(FileArray)
FileNameAndPath = (InputDirPath & FileArray(i))
Debug.Print ("Processing: " & FileNameAndPath)
Dim fileToCopy As File
Set fileToCopy = FSO.GetFile(FileNameAndPath)
Dim streamToCopy As TextStream
Set streamToCopy = fileToCopy.OpenAsTextStream(ForReading)
Dim text As String
text = streamToCopy.ReadAll
Stream.WriteLine FSO.GetFile(FileNameAndPath).OpenAsTextStream(ForReading).ReadAll
Debug.Print ("Appended to " & OutputFileName & ": " & FileNameAndPath)
Next i
Stream.Close
End Sub
The FileNameAndPath value gets updated correctly, and as it goes through the first Stream.WriteLine iteration, it appends only the first letter of the first file to the output file, then moves on to the next iteration, and on the next Stream.WriteLine it fails due to Invalid procedure call or argument.
I've been trying to debug this for quite a while but not certain what is causing this. Only thing I can think of that might be causing it is the array, because it's really the only thing that is different AFAIK... Any help would be greatly appreciated!
Additional details
If I comment out the WriteLine call it goes through the entire array, printing all file paths to immediate. As you can see I broke down the original one-liner into multiple steps for debugging.
Replicating it is easy:
Create a C:\test\ directory
Create two or more text files and add text content to each of them
Run the code in the VBE
I found the problem. The problem was not the code, it works fine (though I feel sure could be improved, I'll take it over to Code Review).
The problem was that some of the source files were actually originally Excel documents that became converted to .txt and apparently carried over some meta-data that Notepad ignored, but the VBA compiler did not know what to do with trying to put it into a String.
Lesson learned, perform a sanity check of your source data.

Resources