Excel Mastery Track · Brown-2 belt · ~7–9 hours · Microsoft 365 on Windows assumed
In VBA I you stopped recording and started writing; in VBA II you gave that writing a mind, branches, loops, a Dictionary, your own functions, events, and error handling. Everything so far, though, has lived inside one workbook, driven by code you started by hand and fed by data already sitting on a sheet. This module breaks all three of those walls at once. It gives your macros a proper front door (a UserForm, so a colleague can drive your tool without ever seeing the code). It gives them a reach beyond the file, since the FileSystemObject and the Dir loop let one macro open, read, and consolidate a whole folder of workbooks. They get a hand into other applications too, so a routine can automate Word and Outlook to build a report and mail it. And they get speed, the discipline that makes all of that survivable on real data. That means turning off screen redraws and automatic recalculation, and above all working through in-memory arrays instead of touching cells one at a time.
Advertisement
The through-line is a single promotion: from a macro that you run on this workbook to a tool that other people run on many workbooks. A tool needs an interface a non-programmer can use without breaking it, that is the UserForm, and most of the work is not drawing it but validating what the user types before it ever reaches your data. A tool that consolidates a team's files needs to find and open those files reliably, skip the ones that are already open or the wrong shape, and never leave a half-opened workbook behind if something fails, that is file I/O plus the error-handling discipline from EX6.02, now doing real work. And a tool that runs over thousands of rows must not crawl. The difference between a macro that finishes in eight seconds and the same logic that finishes in eight minutes is almost always the same two habits. Stop redrawing the screen on every write, and read the range into an array, compute in memory, and write it back once.
A word on why this matters for an analyst specifically. The single most valuable macro most analysts ever write is a folder consolidator: point it at a directory of branch files, or broker statements, or monthly extracts, and get one clean master table back. You will build exactly that here. The second most valuable is a one-click report: refresh, format, save as PDF, attach to an email, done. You will meet the pieces of that here and assemble the whole in VBA IV. Neither is exotic; both are just the constructs of VBA II pointed at the file system and made fast. By the end of this module the phrase "can you just combine all of these?" (the request that used to cost you an afternoon of copy-paste) becomes a button.
One version note, stated once. Everything here assumes Microsoft 365 desktop on Windows. UserForms, the FileSystemObject (the Scripting library), Dir, and the Application speed properties are all long-stable, the code runs identically on Excel 2016 through 365. Menu paths are for the Windows ribbon and the Windows Visual Basic Editor (Alt+F11, or Developer ▸ Visual Basic). Two Windows-specific dependencies are called out where they occur: the Scripting.FileSystemObject and early-bound automation of Word/Outlook are Windows components, on Mac VBA the FileSystemObject is unavailable (fall back to Dir/Open statements) and cross-app automation differs. And as always for anything with code: save the tool as .xlsm (macro-enabled) or a plain .xlsx will silently discard every line of it on close.
Learning objectives
By the end you can:
- Design a UserForm with the common controls (
TextBox,ComboBox,ListBox,CheckBox,OptionButton,CommandButton,Label) set their key properties, and show it modally with.Show. - Wire a form's logic through its event handlers (
UserForm_Initialize, a button's_Click,_Change), read control values in code, and unload cleanly withUnload Me/Me.Hide. - Validate every input before you accept it (numeric, in-range, non-empty, from an allowed list, a real date) and keep focus on the offending control with
.SetFocusrather than writing bad data. - Use the
FileSystemObject(FileExists,FolderExists,GetFolder,.Files,CreateTextFile,OpenTextFile) and the classicDirpattern to enumerate and test files and folders. - Read and write text/CSV files line by line, and open, process, and safely close other workbooks with
Workbooks.Open/.Close SaveChanges:=False. - Loop a folder of workbooks, pull a range from each, and consolidate the lot into one master sheet, skipping files that are the wrong shape and never leaving an opened book behind.
- Automate Word and Outlook with late binding (
CreateObject) to generate a document or compose an email with an attachment, and know why late binding is the safe default for portable code. - Make macros fast: toggle
Application.ScreenUpdating,Application.Calculation, andApplication.EnableEventsaround bulk work (restoring them in an error handler), and (the big one) read a range into a Variant array, compute in memory, and write it back in a single assignment. - Structure a growing project: split code across purposeful standard modules, name procedures and modules meaningfully, scope with
Public/Private, and factor shared logic into small reusableSubs andFunctions.
Prerequisites & connections
Builds on. EX6.02 (VBA II. Logic, Functions & Events) is the hard prerequisite, and you'll use it on almost every line here. For Each walks a folder's files. If/Select Case validates inputs and branches on file type. The Scripting.Dictionary accumulates per-key totals while you consolidate, and string handling (InStr, Split, Left/Mid) parses file names and paths. Most of all you need the On Error GoTo / Err pattern. File and cross-application work fails in ways in-sheet code never does: a file is locked, a path is wrong, Outlook isn't installed. A tool that opens files must guarantee it closes them and restores the speed settings even when it crashes. From EX6.01 you keep Option Explicit, fully-qualified objects, With blocks, and the no-.Select reflex. From the wider Excel track you bring the analyst's habit, drilled since EX0.02, of never trusting an input you haven't checked. Form validation is that habit, written as code.
Feeds forward. EX6.04 (VBA IV. Automation Capstones) assembles everything here into four finished tools. Your folder consolidator from the first worked example below becomes Capstone 2 essentially unchanged. The report generator uses the same Word/Outlook automation plus the speed trio to produce a formatted PDF pack. A third tool, the model-checker add-in, scans a workbook using the file and range techniques you'll drill here. Every capstone assumes you work through arrays and switch ScreenUpdating off. Those habits are the difference between a demo and a tool people will actually run. On the analyst track, the same machinery drives the portfolio tracker of EX7.02, which turns many broker files into one dashboard. It also powers any "refresh and print the pack" routine, and the reusable UDF/macro library that a professional modeller (EX7.01) carries from job to job. The first-udf badge came from EX6.02. Now you can build the tools that make VBA worth carrying.
Core teaching content
VBA UserForm
A UserForm is a custom dialog box, your own window with your own controls, that a macro can display to collect input or show output. It turns a macro you run into a tool anyone can run. A UserForm's entire reason to exist is to stop bad data reaching your sheet. Every value a TextBox yields is a String, so "is this a positive number?" is a question you have to answer in code.
5 min read, on its own page
FileSystemObject in VBA
The FileSystemObject (FSO) lets VBA treat files and folders as objects, so a macro can check that a path exists and walk the files in a folder. It also reads and writes text files line by line. It's part of the Microsoft Scripting Runtime, and you create it with late binding, so there's no reference to set. Dir is lighter, but the FSO does more, so it's worth knowing both.
3 min read, on its own page
Combine multiple workbooks with VBA
Combining multiple workbooks with VBA means looping over the files in a folder and copying each file's rows into one master sheet. It's the macro analysts reach for most, for jobs like consolidating a folder of branch workbooks. It follows the same five steps every time, and the error handler matters most: it closes any open file and restores Excel's speed settings if one file fails.
3 min read, on its own page
Controlling other apps: Word & Outlook
VBA can drive any application that exposes an automation model, and for an analyst the two most useful are Word and Outlook. You can bind to them in two ways:
- Early binding means setting a reference (Tools ▸ References ▸ Microsoft Outlook xx.x Object Library), then
Dim olApp As Outlook.Application. You get IntelliSense and named constants. But the code breaks if the target machine has a different Office version or the library is missing. - Late binding is
Dim olApp As Object: Set olApp = CreateObject("Outlook.Application"). There's no reference and no IntelliSense, but the code is portable, because it binds at run time to whatever version is installed. For code you share, late binding is the safe default. Look up the numeric value of any named constant you need and hardcode it with a comment, as in Word'swdFormatPDF = 17.
Here's a minimal "compose an email with an attachment". It doesn't send until the user clicks, which is the courteous default:
Dim olApp As Object, mail As Object
Set olApp = CreateObject("Outlook.Application")
Set mail = olApp.CreateItem(0) ' 0 = olMailItem
With mail
.To = "team@example.com"
.Subject = "Consolidated report " & Format(Date, "yyyy-mm-dd")
.Body = "Attached: this week's consolidation."
.Attachments.Add ThisWorkbook.Path & "\Master.pdf"
.Display ' .Send would fire it immediately
End With
Excel's own ExportAsFixedFormat Type:=xlTypePDF, Filename:=... turns a sheet or range into that PDF first, so a simple pack needs no Word at all. Reach for Word automation when you need real document layout (headings, a narrative, a table of contents). Reach for Outlook when the deliverable is an email. The capstones that come next chain these into the one-click report generator.
Speed up VBA code
Speeding up VBA code means making a macro touch the worksheet less often. Every time code reads or writes a cell, Excel may redraw the screen and recalculate the workbook. The first fix is the speed trio, three Application settings you switch off for the heavy work and restore afterwards. The second is to do the work in an array in memory, with one read from the sheet and one write back.
3 min read, on its own page
Module structure and organisation
As a project grows past one macro, where code lives starts to matter. It has three homes. Standard modules (Module1…) hold your general Subs and Functions, and UDFs must live there. Sheet modules hold that sheet's events, such as Worksheet_Change. ThisWorkbook holds workbook events like Workbook_Open. None of this is glamorous, but it matters:
- Split by purpose, not by size. A
modConsolidatefor the folder loop, amodReportfor the PDF/email chain, amodUtilsfor the shared helpers (aLastRow(ws)function, aSheetExists(name)boolean). One 3,000-line module is a code smell. PublicvsPrivate. Mark aSubPrivateif only its own module calls it, andPublic(the default) if it's an entry point or a shared helper. Fewer public names means fewer accidental collisions and a cleanerAlt+F8macro list, since onlyPublic Subs with no arguments appear there.- Factor out the repeated line. The instant you write
ws.Cells(ws.Rows.Count, "A").End(xlUp).Rowfor the third time, make itFunction LastRow(ws As Worksheet) As Long. Small, named, reused helpers let the folder consolidator and the report generator share the same "find the data" logic without copy-paste. - Name for the reader. Call it
ConsolidateBranches, notMacro3. UsetxtAmount, notTextBox1, andmodReport, notModule2. You pay the cost once, and the benefit comes back every time anyone opens the project.
Is good structure gold-plating? It can look like it. But it's what makes the four capstones that follow buildable, because each of them reuses these modules instead of starting from nothing.
Common mistakes & how experts think differently
A TextBox value is a String, not a number. The most common form bug is total = Me.txtAmount.Value + 100 producing string concatenation or a type error, because .Value is text. Experts test with IsNumeric and convert with CDbl before arithmetic, and they do it every time.
Validating after you have written, or not at all. The novice form writes the row, then maybe checks it. The expert's form writes nothing until every gate passes, returns focus to the bad control, and treats "the user typed garbage" as the normal case. A form that can write bad data is just a faster way to corrupt a sheet.
Leaving files open after a crash. Picture a consolidation loop that opens a workbook and then hits an error on a bad sheet. Without a guard, that workbook stays open and invisible in memory, and it often stays locked for everyone else too. Experts wrap the open in error handling, and the handler's first job is If Not wb Is Nothing Then wb.Close SaveChanges:=False.
Forgetting to restore the speed settings on error. ScreenUpdating = False with no matching restore in the error path leaves the user with a frozen screen and no idea why. An expert uses Resume CleanUp to make the same cleanup code run on success and on failure, so the trio always goes back once and only once.
Cell-by-cell when an array would do. The novice loops For i = 2 To 10001: ws.Cells(i,1) = ws.Cells(i,1) * 1.05: Next and wonders why it takes a minute. The expert reads the range into a Variant array, computes in memory, and writes it back once. That's two boundary crossings instead of twenty thousand. It's the biggest performance lever in all of VBA.
Early binding in code you share. Dim ol As Outlook.Application compiles on your machine and fails to compile on a colleague's with a different Office version or a missing reference. Experts use late binding (CreateObject, As Object) for anything that leaves their own PC, and hardcode the numeric constants with a comment.
Every one of those habits comes from a single mental shift, from macro to tool, from "code I run" to "code other people run on data I have not seen." A tool assumes the input is wrong until validated. It assumes a file in the folder is malformed until it opens cleanly. It assumes it may crash, and guarantees it leaves the workbook in a sane state anyway. And it assumes it'll meet more rows than you tested with, so it works through arrays instead of cells. Look at every "extra" line: the validation ladder, the If Not wb Is Nothing, the Resume CleanUp, the array read. None of it is defensiveness for its own sake. It's the price of a macro that survives contact with a colleague and a real folder.
Worked examples
Worked example 1: India: consolidate a folder of branch workbooks (India, ₹)
Scenario. Four regional branches each send a workbook: North.xlsx, South.xlsx, East.xlsx, West.xlsx, sitting in a branches subfolder next to your tool. Each has one sheet with a header row and then Product / Units / Revenue rows. You must consolidate all of them into a single Master sheet, tagging each row with its branch, and report the row count and grand total.
The data (₹):
| File | Rows (Product: Revenue) | Branch subtotal |
|---|---|---|
| North.xlsx | Alpha ₹12,50,000 · Beta ₹8,75,000 · Gamma ₹9,90,000 | ₹31,15,000 |
| South.xlsx | Alpha ₹11,00,000 · Beta ₹7,50,000 · Gamma ₹6,00,000 | ₹24,50,000 |
| East.xlsx | Alpha ₹9,25,000 · Beta ₹8,00,000 | ₹17,25,000 |
| West.xlsx | Alpha ₹10,50,000 · Beta ₹9,75,000 · Gamma ₹8,00,000 | ₹28,25,000 |
Step 1, expected results, computed by hand first (so the macro has something to be checked against). Same rule as always: know the answer before you trust the code. Before you run anything, do the sums.
- Consolidated rows = 3 + 3 + 2 + 3 = 11 rows.
- Grand total = 31,15,000 + 24,50,000 + 17,25,000 + 28,25,000 = ₹1,01,15,000 (₹10,115,000).
Step 2, the macro is ConsolidateBranches, unchanged from the core section above. It confirms the folder, clears Master, sets the speed trio, and Dir-loops the *.xlsx files. It opens each one read-only, walks its used rows appending Branch/Product/Units/Revenue to Master, then closes it SaveChanges:=False. Finally it restores the settings in CleanUp (and in CleanFail). The branch tag comes from the file name: Left(fname, InStrRev(fname, ".") - 1) turns North.xlsx into North.
Step 3, the check row on Master. After the loop, put the totals where a human, and the lab, can read them: =COUNTA(B2:B100000) for the row count and =SUM(D2:D100000) for the grand total.
=COUNTA(B2:B100000)returns 11.=SUM(D2:D100000)returns 10,115,000, which is ₹1,01,15,000.
Step 4: why the numbers are trustworthy. The by-hand total from Step 1 and the sheet total from Step 3 agree at ₹1,01,15,000, and the row count agrees at 11. Any disagreement would have flagged a bug: a skipped file, a double-counted header row, an off-by-one in the used-range detection. Here's the classic failure this catches. Suppose the used-range end-row calculation grabbed the header. Each file would contribute one phantom row, and the count would read 15 instead of 11. Against the hand figure you'd see that instantly. So always compute the expected total independently before you believe a consolidation macro.
Worked example 2: US: a UserForm that validates an expense, then writes a formatted row (US, $)
Scenario. An expense log on sheet Log has headers Vendor / Category / Amount / Date in A1:D1. You want a form so anyone can add an entry safely: the amount must be a positive number, the date must be real, the category must come from a fixed list, and the vendor must be non-empty. Bad input must never reach the sheet.
Step 1, the form. frmExpense holds three TextBoxes, txtVendor, txtAmount and txtDate, and the date box defaults to today. cboCategory is a ComboBox filled in Initialize with Travel/Meals/Software/Office. btnOK is the Default button and btnCancel the Cancel one. UserForm_Initialize fills the combo and stamps Format(Date,"yyyy-mm-dd") into txtDate.
Step 2, the validation ladder is btnOK_Click as written in the core section. It checks for a vendor first, then IsNumeric(txtAmount), then CDbl(txtAmount) > 0, then cboCategory.ListIndex <> -1, then IsDate(txtDate). Each failure gets MsgBox + SetFocus + Exit Sub. Only when all five pass does it call WriteExpenseRow.
Step 3, the writer appends one formatted row beneath the log's last used row:
Sub WriteExpenseRow(vendor As String, cat As String, amt As Double, dt As Date)
Dim ws As Worksheet, r As Long
Set ws = ThisWorkbook.Worksheets("Log")
r = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
Application.EnableEvents = False ' don't trip a Change handler
With ws
.Cells(r, 1).Value = vendor
.Cells(r, 2).Value = cat
.Cells(r, 3).Value = amt
.Cells(r, 3).NumberFormat = "$#,##0.00"
.Cells(r, 4).Value = dt
.Cells(r, 4).NumberFormat = "yyyy-mm-dd"
End With
Application.EnableEvents = True
End Sub
Step 4, trace a good and a bad entry.
- Good: vendor
"CloudCorp", categorySoftware, amount"2306.40", date"2026-07-18".IsNumeric("2306.40")= True,CDbl= 2306.40 > 0, a category is selected, andIsDate= True. So the row is written, with Amount shown as$2,306.40and Date as2026-07-18. - Bad: amount
"12O0", with a letter O where the zero should be. It's an easy typo to make, and a hard one to spot by eye.IsNumeric("12O0")= False, so the message "Amount must be a number" appears, focus returns totxtAmount, and nothing is written. That's the corruption the form exists to prevent. A stray letter would otherwise land as text in a numeric column and break everySUMbeneath it, with no error to warn you.
Step 5, the running total, as a check. Say the log starts with $1,840.00, $512.75, $2,306.40, $89.99, $1,275.00. Then =SUM(C2:C6) = $6,024.14. You can compute that figure independently (1840 + 512.75 + 2306.40 + 89.99 + 1275 = 6024.14) and use it to confirm the form wrote the amounts as numbers. Text entries would be skipped by SUM, and the total would read low.
Practice set
Work these with Excel and the VBE open. Numeric answers are exact unless a tolerance is stated. Full solutions follow each.
Practice 1 (guided), the validation ladder. A UserForm has txtQty (a TextBox). Write the btnOK_Click gate. It should reject a blank, a non-numeric, or a non-positive quantity, keep focus on txtQty each time, and proceed only if the quantity is a positive whole number.
Solution.