Office Macros skill for Codex
VBA and Office Scripts that finish in seconds. Codex decides between VBA and Office Scripts from where the workbook lives and how it must run, then writes macros that read and write whole ranges as arrays, never Select, and always restore calculation and events on error. It works from exported .bas and .ts source, reads existing macros with olevba, and leaves Outlook sends for you to confirm.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- office-macros Installs to ~/.agents/skills/office-macros/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install office-macros The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project office-macros Installs to .agents/skills/office-macros/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/office-macros.zipunzip -o office-macros.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) office-macros PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $office-macros, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Writes and fixes Excel, Word and Outlook VBA macros and Excel Office Scripts (TypeScript) for Excel on the web and Power Automate - bulk array reads instead of cell loops, error handlers that restore application state, and macro-security-aware delivery. Use when the user asks for a VBA macro, a slow or broken .xlsm, an Office Script, a button that automates a workbook, or a Power Automate flow that runs a script.
Use it when
- A workbook needs a macro or a button that automates a manual routine
- An existing .xlsm macro is slow, fragile or throws errors
- Excel on the web or Power Automate needs an Office Script
- Macros stopped running after a file was emailed or downloaded
Use something else when
- A formula or LAMBDA would solve it: use excel-formulas
- You are building the workbook from code outside Excel: use xlsx-spreadsheets
- The data lives in Google Sheets: use google-sheets-automation
How to check the result
Run on a copy with production-sized data, compare row counts and a checksum column with a manual run, then force an error and confirm Excel's calculation and events are restored.
The mistake it prevents
Looping cell by cell with Select and ActiveSheet, which is slow and silently edits whichever workbook happens to be active.
Done well means: the macro runs in seconds on real data sizes, leaves Excel in the state it found it (screen updating, calculation, events) even after an error, and the user can read, rerun and trust it. Choose the platform first; most macro pain comes from picking the wrong one.
Choose VBA or Office Scripts
| Situation | Choose |
|---|---|
| Desktop Excel/Word/Outlook only, needs file system, other apps, UserForms | VBA |
| Workbook lives in OneDrive/SharePoint, used in Excel on the web | Office Scripts |
| Must run unattended on a schedule or from a form/email trigger | Office Scripts + Power Automate |
| Shared with Mac users | Office Scripts (VBA on Mac lacks Windows APIs and ActiveX) |
| Pure data transformation that could live outside Excel | Python: $spreadsheet-automation |
| A formula could do it | Write the formula: $excel-formulas |
Workflow
- [ ] 1. Get a copy of the workbook and a description of one manual run, step by step
- [ ] 2. Inspect structure: sheet names, tables (ListObjects), named ranges, data sizes
- [ ] 3. Write the macro against a copy, never the user's only file
- [ ] 4. Run on the copy; compare output with one manual run
- [ ] 5. Add the error handler, logging and a confirmation before destructive steps
- [ ] 6. Deliver as .bas/.ts source plus instructions, not only inside a binary file
Codex cannot open the VBA editor. Work with exported modules (.bas, .cls, .frm) as
text. To read macros already inside an .xlsm or .docm, olevba workbook.xlsm from the
oletools package prints them (pip install needs network). An .xlsm is a zip whose code
sits in a binary vbaProject.bin; never edit that blob directly.
VBA patterns
Skeleton with state restore
Option Explicit
Public Sub BuildMonthlySummary()
Dim calcMode As XlCalculation
calcMode = Application.Calculation
On Error GoTo Fail
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
If lastRow < 2 Then Err.Raise vbObjectError + 1, , "No data rows on sheet Data"
Dim src As Variant
src = ws.Range("A2:D" & lastRow).Value2 ' one read: 2-D array, 1-based
Dim totals As Object
Set totals = CreateObject("Scripting.Dictionary")
Dim r As Long
For r = 1 To UBound(src, 1)
totals(src(r, 2)) = totals(src(r, 2)) + src(r, 4)
Next r
Dim out() As Variant, k As Variant, i As Long
ReDim out(1 To totals.Count, 1 To 2)
For Each k In totals.Keys
i = i + 1
out(i, 1) = k: out(i, 2) = totals(k)
Next k
With ThisWorkbook.Worksheets("Summary")
.Range("A2:B" & .Rows.Count).ClearContents
.Range("A2").Resize(UBound(out, 1), 2).Value2 = out ' one write
End With
Cleanup:
Application.Calculation = calcMode
Application.EnableEvents = True
Application.ScreenUpdating = True
Exit Sub
Fail:
MsgBox "BuildMonthlySummary failed: " & Err.Description, vbExclamation
Resume Cleanup
End Sub
Rules that make VBA fast and predictable:
Option Explicitin every module; declareLong, notInteger(overflows at 32,767).- Never
SelectorActivate; reference ranges through a worksheet variable. - Read and write whole ranges as arrays. A cell-by-cell loop over 50k rows takes minutes; the array version takes well under a second.
- Fully qualify:
ThisWorkbook.Worksheets("Data"), notActiveSheetorSheets("Data")(the latter picks whichever workbook is active). - Prefer ListObjects (
ws.ListObjects("tblSales").DataBodyRange) over hard-coded ranges. Value2skips Currency/Date coercion and is faster; format dates on output.- Find the last row with
End(xlUp)on a column that is always filled, notUsedRange.
Word and Outlook
- Word: edit through
ActiveDocument.Content.Findwith.Execute Replace:=wdReplaceAlland ranges; avoid moving theSelection. - Outlook: create mail with
Application.CreateItem(olMailItem), call.Displayso the user reviews it. Use.Sendonly after the user explicitly confirms bulk sending, and expect the Outlook object-model guard prompt on unmanaged machines.
Office Scripts (Excel on the web)
function main(workbook: ExcelScript.Workbook, minAmount: number = 0): string {
const data = workbook.getWorksheet("Data");
if (!data) throw new Error("Sheet 'Data' not found");
const used = data.getUsedRange();
if (!used) return "Sheet is empty";
const rows = used.getValues(); // one read
const header = rows.shift() as string[];
const amountCol = header.indexOf("Amount");
if (amountCol < 0) throw new Error("No 'Amount' column");
const kept = rows.filter(r => Number(r[amountCol]) >= minAmount);
let out = workbook.getWorksheet("Filtered");
if (out) out.delete();
out = workbook.addWorksheet("Filtered");
out.getRangeByIndexes(0, 0, kept.length + 1, header.length)
.setValues([header, ...kept]); // one write
return `${kept.length} rows kept`;
}
mainparameters afterworkbookbecome inputs in Power Automate; the return value becomes the action’s output.- Keep
get*/set*calls out of loops: each one is a round trip to the workbook. Read once, transform plain arrays, write once. - Explicit
anyis not allowed; type values as(string | number | boolean)[][]. getWorksheet,getTableandgetUsedRangereturnundefinedwhen missing; check.- Scripts cannot reach the file system or other workbooks directly; Power Automate passes
data in and out. External
fetchworks only when the script runs from Excel, not from Power Automate. - Power Automate runs have a synchronous timeout and a daily call cap per user; check Microsoft’s current “Office Scripts platform limits” page before designing a large job, and split work into batches of rows.
Delivery and macro security
- Since 2022, Office blocks macros in files carrying the Mark of the Web (downloaded or emailed). The user unblocks via file Properties > Unblock, or IT adds a Trusted Location. Never advise lowering macro security to “Enable all macros”.
- Ship the source:
.basexports in the repo, plus a README with import steps (Alt+F11 > File > Import File) and the button or shortcut to bind. - For reusable VBA across workbooks, suggest an
.xlamadd-in instead of copying modules. - Sign macros when the organisation requires it; self-signed certificates only satisfy the machine that created them.
Gotchas
On Error Resume Nextleft on hides every later failure. Scope it to one line and turn it off withOn Error GoTo 0or the handler label.- Deleting rows in a forward loop skips rows. Loop backwards, or build a union range or filter and delete once.
- Regional settings:
Range.Formulatakes English function names and commas;FormulaLocaltakes the user’s locale. WriteFormula2for dynamic arrays. Workbook_Openand worksheet events fire during your macro unlessEnableEventsis off.- Office Scripts recorded by the Action Recorder use hard-coded addresses; rewrite against tables or used ranges before reusing them.
Verification
- Run against a copy with production-sized data and time it (
Timerin VBA). - Compare output with a manual run on the same input: row counts and one checksum column.
- Force an error (rename the source sheet) and confirm the handler restores calculation, events and screen updating.
- For Office Scripts, run once from Excel, then once from the Power Automate test run, and read the returned string.
Handover
**Macro:** BuildMonthlySummary (Module modSummary.bas), Excel desktop, Windows and Mac
**Input:** sheet Data, columns A:D, header row 1
**Output:** sheet Summary A:B, cleared and rewritten each run
**Run:** button on Summary sheet, or Alt+F8 > BuildMonthlySummary
**Tested:** 48,210 rows in 0.6 s; totals match the manual pivot for September What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Office Macros"
short_description: "VBA and Office Scripts that finish in seconds"
default_prompt: "$office-macros This VBA macro takes ten minutes on 40k rows, make it fast" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.