External Link Reporter: See Every Workbook Reference Before You Break Anything
Scans every formula across every sheet and builds a report of every workbook this file links to — which sheets reference each file, how many formulas per sheet, and whether the link is live or broken. Non-destructive. No confirmation dialog.
Table of Contents
TL;DR: Before you strip external links, you need to know what you’re breaking. This macro scans the entire workbook, finds every unique external file reference, and builds a report sheet showing which sheets reference which files — complete with formula counts and link status. No modifications to any formulas. The report sits alongside your data for review before you commit to stripping anything.
The Problem
You open the Henderson engagement file and Excel pops up: “This workbook contains links to other data sources.” You click “Don’t Update” and move on. But three days later, before sending the file to the client, you remember the warning. Now you need to know: what files is this workbook linked to? Which sheets? Are they live or broken? The “Edit Links” dialog shows you a flat list of filenames — no sheet context, no formula counts, no broken/live status at a glance. You’re about to strip all links with the Strip External Links macro, but stripping without auditing is how you flatten a formula that should have stayed.
This macro builds a complete inventory of every external link in the workbook without touching a single formula. The report tells you exactly what you’d break before you break it.
#Prerequisites & Setup
What you’ll need:
- Excel 2016+ (desktop)
- A workbook containing formulas that reference external workbooks (the
[in=[Budget.xlsx]Sheet1!A1) - The workbook should be saved — unsaved workbooks can’t have external links
Limitations:
- This macro is non-destructive. It reads formulas but never modifies them. Safe to run on any workbook.
- Only detects cell-formula links to other Excel workbooks. Does not detect DDE links, OLE links, or add-in references.
- Does not scan named ranges, chart data sources, or data connections. Use Excel’s native
Data → Edit Linksdialog for those. - Broken links (source file moved or deleted) still show up in the report — a broken link is still a link.
- Protected sheets with hidden formulas will show those formulas in the report if
SpecialCellscan access them. If a sheet is fully protected, formulas on that sheet may be skipped.
#The Macro
Option Explicit
Sub ReportExternalLinks()
' ── External Link Reporter ─────────────────────────
' Scans every formula on every sheet for external
' workbook references ([filename.xlsx]). Builds a
' report on a new "External-Links" sheet showing
' every unique external path, which sheets reference
' it, how many formulas, and whether the link is
' live or broken.
'
' Non-destructive — reads formulas, never writes.
' Safe to run on any workbook at any time.
' ────────────────────────────────────────────────────
' ── Configuration ──────────────────────────────────
Const OUT_SHEET As String = "External-Links"
Const MAX_FORMULA_SCAN As Long = 50000 ' Safety limit
' ── State management ───────────────────────────────
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' ── Variables ──────────────────────────────────────
Dim links As Variant
Dim ws As Worksheet
Dim rpt As Worksheet
Dim cell As Range
Dim formulaCells As Range
Dim outRow As Long
Dim i As Long, j As Long
Dim linkPath As String
Dim sheetList As String
Dim totalFormulas As Long
Dim linkStatus As String
Dim sheetsWithLink As New Collection
Dim sheetCount As Long
Dim emptyCount As Long
Dim uniqueLinks As Long
' ── Error handling ─────────────────────────────────
On Error GoTo CleanUp
' ── Step 1: Get all external link sources ──────────
links = ThisWorkbook.LinkSources(xlExcelLinks)
If IsEmpty(links) Then
MsgBox "No external workbook links found in this file.", _
vbInformation, "No Links Found"
GoTo CleanUp
End If
uniqueLinks = UBound(links)
' ── Step 2: Create report sheet ────────────────────
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets(OUT_SHEET).Delete
Application.DisplayAlerts = True
On Error GoTo CleanUp
Set rpt = ThisWorkbook.Worksheets.Add( _
Before:=ThisWorkbook.Worksheets(1))
rpt.Name = OUT_SHEET
' ── Step 3: Write headers ──────────────────────────
rpt.Range("A1:E1").Value = Array( _
"External Path", "Referenced Sheets", _
"Formula Count", "Status", "Notes")
rpt.Range("A1:E1").Font.Bold = True
rpt.Range("A1:E1").Interior.Color = RGB(50, 50, 50)
rpt.Range("A1:E1").Font.Color = vbWhite
' ── Step 4: Scan each link across all sheets ───────
outRow = 2
For i = LBound(links) To UBound(links)
linkPath = links(i)
sheetList = ""
totalFormulas = 0
sheetCount = 0
' Reset collection for this link
Set sheetsWithLink = New Collection
For Each ws In ThisWorkbook.Worksheets
If ws.Name = OUT_SHEET Then GoTo NextSheet
On Error Resume Next
Set formulaCells = ws.Cells.SpecialCells(xlCellTypeFormulas)
On Error GoTo CleanUp
If Not formulaCells Is Nothing Then
Dim localCount As Long
localCount = 0
For Each cell In formulaCells
' Check if this formula contains the link path
If InStr(1, cell.Formula, linkPath, vbTextCompare) > 0 Then
localCount = localCount + 1
End If
' Safety limit — prevent infinite scan on huge sheets
If localCount >= MAX_FORMULA_SCAN Then Exit For
Next cell
If localCount > 0 Then
sheetsWithLink.Add ws.Name & " (" & localCount & ")", _
ws.Name
totalFormulas = totalFormulas + localCount
sheetCount = sheetCount + 1
End If
End If
NextSheet:
Next ws
' ── Build the sheet list string ─────────────────
If sheetCount = 0 Then
sheetList = "(no formulas found)"
Else
For j = 1 To sheetsWithLink.Count
sheetList = sheetList & sheetsWithLink(j)
If j < sheetsWithLink.Count Then sheetList = sheetList & ", "
Next j
End If
' ── Determine link status ───────────────────────
Dim testPath As String
testPath = linkPath
On Error Resume Next
If Dir(testPath) <> "" Then
linkStatus = "LIVE"
Else
linkStatus = "BROKEN"
End If
On Error GoTo CleanUp
' ── Write the row ───────────────────────────────
rpt.Cells(outRow, 1).Value = linkPath
rpt.Cells(outRow, 2).Value = sheetList
rpt.Cells(outRow, 3).Value = totalFormulas
rpt.Cells(outRow, 4).Value = linkStatus
' Status-specific formatting
If linkStatus = "BROKEN" Then
rpt.Cells(outRow, 4).Interior.Color = RGB(254, 202, 202)
rpt.Cells(outRow, 4).Font.Bold = True
rpt.Cells(outRow, 5).Value = _
"Source file missing or moved. Link value is frozen."
Else
rpt.Cells(outRow, 4).Interior.Color = RGB(220, 252, 231)
rpt.Cells(outRow, 4).Font.Bold = True
End If
' ── Notes for specific patterns ─────────────────
If totalFormulas = 0 Then
rpt.Cells(outRow, 5).Value = _
"Link registered but no formulas reference it. " & _
"Check named ranges or chart data sources."
rpt.Cells(outRow, 5).Font.Color = RGB(180, 83, 9)
End If
' Alternate row shading
If outRow Mod 2 = 0 Then
rpt.Range("A" & outRow & ":E" & outRow). _
Interior.Color = RGB(248, 248, 250)
End If
outRow = outRow + 1
Next i
' ── Step 5: Format report ──────────────────────────
With rpt
.Columns("A").ColumnWidth = 55
.Columns("B").ColumnWidth = 50
.Columns("C").ColumnWidth = 16
.Columns("D").ColumnWidth = 10
.Columns("E").ColumnWidth = 48
.Range("A1").Select
ActiveWindow.FreezePanes = True
End With
' ── Step 6: Summary ────────────────────────────────
Dim brokenCount As Long, liveCount As Long
brokenCount = Application.CountIf(rpt.Range("D2:D" & outRow), "BROKEN")
liveCount = Application.CountIf(rpt.Range("D2:D" & outRow), "LIVE")
MsgBox "Found " & uniqueLinks & " unique external link(s)." & vbCrLf & _
" LIVE: " & liveCount & vbCrLf & _
" BROKEN: " & brokenCount & vbCrLf & vbCrLf & _
"Report created on '" & OUT_SHEET & "' sheet.", _
vbInformation, "External Link Report"
CleanUp:
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
If Err.Number <> 0 Then
MsgBox "Error " & Err.Number & ": " & Err.Description, _
vbCritical, "Macro Error"
End If
End Sub
#How It Works
#LinkSources: Excel already knows the answer
Excel maintains an internal registry of every external file this workbook
references. The LinkSources method returns that list directly — no scanning
required to find the filenames:
links = ThisWorkbook.LinkSources(xlExcelLinks)
The xlExcelLinks argument filters to only OLE and DDE links (i.e., formula
references to other Excel workbooks). The result is a one-dimensional array
of full file paths, e.g. \\server\share\2025\Budget.xlsx. If there are no
links, the array is empty — the macro reports zero and exits cleanly.
#Why scan per-link instead of per-cell
The macro could have scanned every cell once and built the report from a dictionary. That’s faster. But building per-link means the report maps directly to the “Edit Links” dialog the user already knows — one row per file, not one row per cell. The preparer who sees “Budget.xlsx: 14 formulas across 3 sheets” can decide whether to strip that file or keep it. A cell-level report would show 14 rows for the same file, making it harder to answer the strategic question: “Should I break this link?”
#Two detection passes
The macro first uses LinkSources to get unique file paths — this is fast and
reliable. Then it loops each sheet’s formula cells to count which formulas
reference which path. The SpecialCells(xlCellTypeFormulas) call narrows the
scan to formula-only cells, skipping blank cells and constants:
Set formulaCells = ws.Cells.SpecialCells(xlCellTypeFormulas)
If a sheet has no formulas (e.g., a data-only import sheet), SpecialCells
returns Nothing and the sheet is skipped entirely. This makes the scan fast
even on large workbooks — blank columns and data entries are never examined.
#Live vs. Broken: Dir() as a quick file test
The Dir() function checks whether the linked file exists at its stored path:
If Dir(testPath) <> "" Then
linkStatus = "LIVE"
Else
linkStatus = "BROKEN"
End If
This is a best-effort check. If the file is on a network share that’s currently
unmapped, Dir() will report it as broken even though it technically exists.
The report flags this as BROKEN but the Notes column doesn’t shame you for it —
broken is broken from Excel’s perspective, regardless of why.
#Why the report goes as the first sheet
Set rpt = ThisWorkbook.Worksheets.Add( _
Before:=ThisWorkbook.Worksheets(1))
The report is inserted as sheet 1 so it’s the first thing anyone sees when opening the file. This is intentional: if you’re handing this workbook to a partner or client, the external-link inventory should be the greeting card. It answers the question “what does this file depend on?” before anyone has to ask it.
#Handling links with no formula matches
Occasionally LinkSources returns a path, but no cell formula contains it.
This happens when the link exists in a named range definition, a chart data
source, or a PivotTable cache. The report flags this with a note:
If totalFormulas = 0 Then
rpt.Cells(outRow, 5).Value = _
"Link registered but no formulas reference it. " & _
"Check named ranges or chart data sources."
The preparer now knows to use Excel’s native Data → Edit Links dialog to
break these, since they’re not cell-formula links the macro can audit.
#Safety limit on large sheets
Const MAX_FORMULA_SCAN As Long = 50000
A sheet with 100,000 formula cells all referencing the same external file would produce a technically correct but meaningless report row. The safety limit caps the scan per file per sheet. In practice, a tax workpaper rarely has more than a few thousand formula cells on a single sheet — this limit exists as a guard, not a governor.
#Adapt It
Get the next macro in your inbox
One copy-paste-ready macro recipe every two weeks. No spam, no VBA theory — just automation that saves you time.
Excel Macro Guy
Excel enthusiast · married to an accountant
I love Excel. My wife is an accountant. Every busy season, I watch her wrestle with workpapers and think "a macro could do that in half a second." So I build them. She tests them on real client data. What survives gets published here.