This document captures practical lessons from building a non-trivial .xlsm with generated VBA modules, Excel COM automation, and iterative debugging.
It is meant for:
- Expert Excel/VBA developers starting with agentic coding workflows
- Prompt engineers directing coding agents on Excel/VBA tasks
It focuses especially on how to handle compilation errors fast.
The "Zellij" project mentioned throughout was a small example used to explore the workflow.
The session produced an Excel macro workbook (ZellijImageGenerator.xlsm) that:
- exposes generator options on a front sheet (
Generator) - has buttons to generate output / create new phrase
- renders generated images into a worksheet grid as cell colorings (
Image)
Implementation used:
- VBA modules generated/imported from
.basfiles - PowerShell COM automation to create workbook and wire buttons/macros
- headless macro execution for testing
- Keep VBA source in plain-text
.basfiles under version control. - Use a script (
CreateZellijXlsm.ps1) to:- create workbook
- create/configure sheets
- import all modules
- save as
.xlsm
- Validate by running macros through COM in a scripted loop.
- If compile/runtime UI blocks COM, use UI automation to read dialogs and selected code line.
- Patch
.basfiles, rebuild.xlsm, rerun tests.
This is significantly more reliable than manual edits directly in VBE.
VBA parser behavior can be brittle in ways modern languages are not.
Practical rules:
- Keep procedure signatures and long calls short and readable.
- Use line continuation (
_) aggressively for long declarations/calls. - Avoid “too clever” one-liners in declarations.
- Keep naming consistent and explicit.
In this session, some procedures existed in text but were not recognized by compilation/introspection until signatures were restructured and shortened.
Good split used here:
modZellijCore: RNG, seed hashing, palette typesmodZellijRaster: low-level rasterization primitivesmodZellijGenerator: pattern generation logicmodZellijExcel: sheet IO, rendering, button entry points
This makes compile/debug localization much faster.
Fixed-size vs dynamic array declarations can cause surprises in ByRef calls between procedures.
Practical rule:
- Standardize on dynamic arrays (
Dim a() As Double,ReDim ...) for array parameters passed between procedures.
This reduced ambiguity and removed compile-path instability.
Coloring cell-by-cell is very slow.
Used optimizations:
- pre-clear background in one range operation
- set row/column sizing in bulk
- color runs of same color per row (run-length style range writes)
For large outputs, this is the difference between usable and unusable.
Long is signed 32-bit. Intermediate conversions (CLng) overflow quickly.
Session example:
- runtime overflow in SHA/byte writing path (
WriteBe32) - fixed by avoiding unsafe
CLngconversions on large unsigned-like values - extracted bytes via staged subtraction/division to stay in safe ranges
Rule: when emulating unsigned 32-bit arithmetic, assume overflow risk everywhere.
This is the most important part for agentic workflows.
When Excel.Application.Run("MacroName") hits compile errors:
- Excel/VBE opens compile dialog
- VBE highlights a token/line
- COM automation call often hangs or fails with RPC errors (
0x800706BE)
If you cannot read the dialog and highlighted line, iteration is slow.
Use UI Automation to read:
- compile dialog text (e.g., “Sub or Function not defined”)
- selected token/line from code pane document (
TextPattern.GetSelection())
Then patch based on exact failing symbol.
The session used System.Windows.Automation from PowerShell (pwsh) with this sequence:
- Load UIA assemblies.
- Find top-level windows (
TreeScope.Children) and pick target window by title:- VBE compile state window:
Microsoft Visual Basic for Applications - [<Module> (Code)] - Runtime error message window (Excel): workbook title, e.g.
Zellij
- VBE compile state window:
- Extract dialog text:
- Search descendants for
ControlType.Text - Read
.Current.Namefrom each text element
- Search descendants for
- Extract highlighted failing code:
- Find
ControlType.Documentin VBE code pane - Get
TextPattern - Read
GetSelection()to capture highlighted token - Expand selection to line with
ExpandToEnclosingUnit(3)(line unit) to capture full call line
- Find
- Dismiss blocking dialogs:
- Find descendant button with name
OK - Invoke via
InvokePattern - Resume COM macro loop
- Find descendant button with name
Minimal pattern:
Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes
$root = [System.Windows.Automation.AutomationElement]::RootElement
# 1) Find VBE window
$wins = $root.FindAll([System.Windows.Automation.TreeScope]::Children, [System.Windows.Automation.Condition]::TrueCondition)
$vbe = $null
for ($i=0; $i -lt $wins.Count; $i++) {
$w = $wins.Item($i)
if ($w.Current.Name -like 'Microsoft Visual Basic for Applications*') { $vbe = $w; break }
}
# 2) Compile dialog text
$textCond = New-Object System.Windows.Automation.PropertyCondition(
[System.Windows.Automation.AutomationElement]::ControlTypeProperty,
[System.Windows.Automation.ControlType]::Text
)
$texts = $vbe.FindAll([System.Windows.Automation.TreeScope]::Descendants, $textCond)
for ($j=0; $j -lt $texts.Count; $j++) {
$name = $texts.Item($j).Current.Name
if ($name) { $name }
}
# 3) Highlighted code token/line
$docCond = New-Object System.Windows.Automation.PropertyCondition(
[System.Windows.Automation.AutomationElement]::ControlTypeProperty,
[System.Windows.Automation.ControlType]::Document
)
$docs = $vbe.FindAll([System.Windows.Automation.TreeScope]::Descendants, $docCond)
$doc = $docs.Item(0)
$tp = $doc.GetCurrentPattern([System.Windows.Automation.TextPattern]::Pattern)
$sel = $tp.GetSelection()
$line = $sel[0].Clone()
$line.ExpandToEnclosingUnit(3) | Out-Null
$line.GetText(2000)Practical notes:
- In PowerShell,
$PIDis reserved; do not use$pidfor your own process-id variable. - Expect both titled and untitled Excel processes/windows during COM runs.
- If COM call fails with RPC after compile popup, read/click UI first, then continue automation.
- UIA selectors should prefer control type + short title pattern; exact titles vary by locale/version.
- Compile dialog text
- Highlighted token (
DrawEightPointStarPattern) - Highlighted call line in
modZellijGenerator - Runtime error message shown in Excel window (
Generate failed: Overflow ...)
Using VBIDE CodeModule via COM:
ProcStartLineProcOfLineFind- dumping module text from workbook
If ProcStartLine("SomeProc") fails while text contains it, parser likely did not register the procedure properly (often syntax/line-shape issue nearby).
- Run macro headless via COM.
- If hang/RPC: inspect UI (dialog + selected line).
- Capture exact missing symbol and call site.
- Validate called procedure declaration text in imported workbook module.
- Simplify declaration/call shape (split lines, shorten names, reduce complexity).
- Rebuild workbook from source modules.
- Repeat.
When compile is clean but execution fails:
- Add lightweight stage markers (e.g., global
gZellijStage) - Update marker at critical checkpoints
- Include marker in error message surface (
MsgBox ... at <stage>)
This gave a precise location for overflow in hashing byte-output.
Keep this instrumentation temporary or behind a debug flag.
Programmatic module import requires Trust Center VBOM access.
Session used registry key:
HKCU\Software\Microsoft\Office\16.0\Excel\Security\AccessVBOM = 1
When opening workbook for execution:
- enable macros for runtime test (
AutomationSecurity = 1in this session)
When opening for static inspection:
- disable macros (
AutomationSecurity = 3) to avoid side effects
Always close workbook and quit Excel in finally and release COM objects.
If a dialog is open, COM may still break; UI-close dialog first, then cleanup.
Use prompts that force an evidence-driven loop.
“Build <feature> in .xlsm via source-controlled .bas modules and a reproducible build script.
After each build, run headless macro tests.
If compile/runtime error occurs, capture exact UI dialog text and highlighted code line before patching.
Do not guess. Report the root cause and fix.”
- “Keep VBA procedure signatures short and multiline.”
- “Prefer dynamic arrays for
ByRefarray parameters.” - “Instrument runtime errors with stage markers.”
- “Use direct
pwshcommands; avoid nested shell indirection.” - “After fix, rerun macro and provide proof of success.”
.basmodule files- workbook build script
- test runner script/snippets
- known limitations section
Observed behavior strongly suggested parser registration issues from line/procedure shape, but exact VBA internal rule was not fully proven.
Improvement:
- add automated line-length and declaration-shape lint checks before import.
UIAutomation worked here, but may vary by:
- Office version
- UI language/localization
- window focus/security contexts
Improvement:
- create a reusable UIA helper script with fallback selectors and localized message patterns.
RowHeight/ColumnWidth to pixel equivalence is approximate and font/display dependent.
Improvement:
- add a calibration routine (measure target range width/height and tune).
A direct “compile project now and return diagnostics” API is limited in native COM workflow.
Improvement:
- investigate robust compile command invocation through VBE command bars or external VBA tooling integration.
- Source-first: all VBA in text modules under git.
- Reproducible build: one script generates
.xlsm. - Automated smoke test: run entry macro headlessly.
- UI capture on failure: always collect compile/runtime dialog + highlighted line.
- Evidence-based patching: no blind guesses.
- Short declarations: avoid parser edge cases.
- Numeric safety review: especially unsigned-like arithmetic.
- Close-loop proof: rerun test and report success evidence.
That process reduced debugging time substantially and turned opaque VBA failures into a manageable engineering loop.