· Workpaper Management · 14 min read

Phantom Data Connection Remover: Find and Delete Dead ODBC, OLEDB, and Power Query Links

Scans every data connection in the workbook and tells you which ones are orphaned. Removes dead connections with one click while leaving active query links intact.

Share:

TL;DR: You open a workpaper and get three “This workbook contains connections” popups before you can do anything. The prior preparer’s SQL queries, dead ODBC links, and abandoned Power Query connections are still there. This macro builds a report showing every connection, tells you which ones feed live tables and pivots (safe to keep) and which are orphaned (safe to delete), then removes the dead ones in one click.

The Problem

The Kimball Manufacturing file has been passed through four preparers over three years. You open it and immediately get hit with: “This workbook contains connections to external data.” Click OK. “Do you want to refresh?” Click No. “Connection failed — data source not found.” Click OK. You haven’t even seen a cell yet and you’ve already dismissed three popups. You open the Queries & Connections pane and see five connections named things like SqlQuery_2023_v4 and OdbcDSN_TempQB — none of which mean anything to you. Are they powering your trial balance pivot? Or are they from a one-time import the intern did in March 2022?

Excel’s Data → Queries & Connections panel lists everything but doesn’t tell you what’s safe to delete. Remove the wrong connection and your TB pivot breaks. Leave the dead ones and you get popup purgatory every time you open the file. This macro audits every connection, reports which ones have downstream consumers, and removes only the orphans.

#Prerequisites & Setup

What you’ll need:

  • Excel 2016+ (desktop)
  • A workbook with data connections — any file that triggers “This workbook contains connections” or shows entries in Data → Queries & Connections

What the macro does:

  • Iterates every connection in ThisWorkbook.Connections
  • Creates a “Connections-Report” sheet with: name, type, connection string (truncated to 80 characters), refresh-on-open status, and whether any active table or pivot depends on it
  • Counts orphaned connections (zero downstream consumers) and asks to remove them
  • Removes only orphaned connections — live connections stay untouched

What the macro does NOT do:

  • It does NOT remove connections that feed active QueryTables or pivot caches. The report marks these as “In Use” and they are never deleted
  • It does NOT crack passwords or bypass connection credentials. If a connection is locked with a password, its string is displayed but the macro doesn’t attempt to connect
  • It does NOT touch Power Query queries defined in the workbook. These show as connections of type “OLEDB” and are analyzed the same way as any other connection

Limitations:

  • Power Query dependency detection is approximate. If a Power Query query loads to a table and that table name matches a connection’s output, the macro flags the connection as “In Use.” Complex query chains with intermediate steps may not be perfectly detected
  • Connection string display is truncated. Long connection strings (especially ODBC with full driver paths) are cut to 80 characters for readability. The full string is available in Excel’s connection properties dialog
  • OLEDB connections without explicit output table names are flagged as “Unknown” for dependency status. Review these manually before deleting
  • Works on the workbook containing the macro. Store in Personal Macro Workbook to run on any file

#The Macro

Option Explicit

Sub RemovePhantomConnections()
    ' ── Phantom Data Connection Remover ────────────────
    ' Lists every ODBC, OLEDB, and Power Query data
    ' connection in the workbook. Flags orphaned
    ' connections (no downstream tables or pivots). On
    ' confirm, removes only the orphans.
    '
    ' Zero input. Report-first-then-act pattern.
    ' ────────────────────────────────────────────────────

    ' ── Configuration ──────────────────────────────────
    Const OUT_SHEET As String = "Connections-Report"
    Const MAX_STRING_LEN As Long = 80

    ' ── State management ───────────────────────────────
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    ' ── Variables ──────────────────────────────────────
    Dim conn As WorkbookConnection
    Dim rpt As Worksheet
    Dim row As Long, orphanCount As Long, totalCount As Long
    Dim connType As String, connStr As String, refreshStr As String
    Dim consumerCount As Long, statusText As String
    Dim orphanNames As String
    Dim qt As QueryTable, pt As PivotTable, pc As PivotCache
    Dim ws As Worksheet

    ' ── Error handling ─────────────────────────────────
    On Error GoTo CleanUp

    ' ── Check for connections ──────────────────────────
    totalCount = ThisWorkbook.Connections.Count
    If totalCount = 0 Then
        MsgBox "No data connections found in this workbook.", _
               vbInformation, "All Clear"
        GoTo CleanUp
    End If

    ' ── Create report sheet ────────────────────────────
    On Error Resume Next
    Application.DisplayAlerts = False
    ThisWorkbook.Sheets(OUT_SHEET).Delete
    Application.DisplayAlerts = True
    On Error GoTo CleanUp

    Set rpt = ThisWorkbook.Sheets.Add( _
        Before:=ThisWorkbook.Sheets(1))
    rpt.Name = OUT_SHEET

    ' ── Report headers ─────────────────────────────────
    rpt.Cells(1, 1) = "Connection Name"
    rpt.Cells(1, 2) = "Type"
    rpt.Cells(1, 3) = "Connection String (truncated)"
    rpt.Cells(1, 4) = "Refresh on Open"
    rpt.Cells(1, 5) = "Status"
    rpt.Range("A1:E1").Font.Bold = True

    ' ── Analyze each connection ────────────────────────
    row = 2
    orphanCount = 0
    orphanNames = ""

    For Each conn In ThisWorkbook.Connections
        ' ── Determine connection type ──────────────────
        connType = "Unknown"
        On Error Resume Next
        connType = conn.Type
        On Error GoTo CleanUp

        ' ── Get connection string (truncated) ──────────
        connStr = ""
        On Error Resume Next
        If conn.OLEDBConnection Is Not Nothing Then
            connStr = conn.OLEDBConnection.Connection
            connType = "OLEDB"
        ElseIf conn.ODBCConnection Is Not Nothing Then
            connStr = conn.ODBCConnection.Connection
            connType = "ODBC"
        End If
        On Error GoTo CleanUp

        If connStr = "" Then connStr = "(not accessible)"

        ' Truncate long strings
        If Len(connStr) > MAX_STRING_LEN Then
            connStr = Left(connStr, MAX_STRING_LEN - 3) & "..."
        End If

        ' ── Refresh on open? ───────────────────────────
        refreshStr = "No"
        On Error Resume Next
        If conn.OLEDBConnection Is Not Nothing Then
            If conn.OLEDBConnection.RefreshOnFileOpen Then
                refreshStr = "Yes"
            End If
        ElseIf conn.ODBCConnection Is Not Nothing Then
            If conn.ODBCConnection.RefreshOnFileOpen Then
                refreshStr = "Yes"
            End If
        End If
        On Error GoTo CleanUp

        ' ── Count downstream consumers ─────────────────
        consumerCount = 0

        ' Check QueryTables
        For Each ws In ThisWorkbook.Worksheets
            On Error Resume Next
            For Each qt In ws.QueryTables
                If qt.Connection Like "*" & conn.Name & "*" Then
                    consumerCount = consumerCount + 1
                End If
            Next qt
            On Error GoTo CleanUp
        Next ws

        ' Check PivotCaches
        On Error Resume Next
        For Each pc In ThisWorkbook.PivotCaches
            If pc.Connection Like "*" & conn.Name & "*" Then
                ' Count pivot tables using this cache
                For Each ws In ThisWorkbook.Worksheets
                    For Each pt In ws.PivotTables
                        If pt.CacheIndex = pc.Index Then
                            consumerCount = consumerCount + 1
                        End If
                    Next pt
                Next ws
            End If
        Next pc
        On Error GoTo CleanUp

        ' ── Determine status ───────────────────────────
        If consumerCount > 0 Then
            statusText = "In Use (" & consumerCount & _
                         " consumer(s))"
        Else
            statusText = "ORPHANED - safe to remove"
            orphanCount = orphanCount + 1
            orphanNames = orphanNames & "  • " & conn.Name & vbCrLf
        End If

        ' ── Write to report ────────────────────────────
        rpt.Cells(row, 1) = conn.Name
        rpt.Cells(row, 2) = connType
        rpt.Cells(row, 3) = connStr
        rpt.Cells(row, 4) = refreshStr
        rpt.Cells(row, 5) = statusText

        ' Color-code orphaned rows
        If consumerCount = 0 Then
            rpt.Range("A" & row & ":E" & row).Interior.Color = _
                RGB(255, 230, 230)
        End If

        row = row + 1
    Next conn

    ' ── Format report ──────────────────────────────────
    rpt.Columns("A:E").AutoFit
    If rpt.Range("A1").CurrentRegion.Rows.Count > 1 Then
        rpt.ListObjects.Add(xlSrcRange, _
            rpt.Range("A1").CurrentRegion, , xlYes).Name = _
            "ConnTbl"
    End If
    rpt.Activate

    ' ── Report findings ────────────────────────────────
    If orphanCount = 0 Then
        MsgBox "Found " & totalCount & " connection(s). " & _
               "All are in use — nothing to remove." & vbCrLf & _
               vbCrLf & "Report on '" & OUT_SHEET & "' sheet.", _
               vbInformation, "All Clear"
        GoTo CleanUp
    End If

    ' ── Confirm and remove orphans ─────────────────────
    Dim msg As String
    msg = "Found " & totalCount & " connection(s). " & _
          orphanCount & " are orphaned:" & vbCrLf & vbCrLf & _
          orphanNames & vbCrLf & _
          "Remove only the orphaned connections? " & _
          "Active connections will NOT be affected."

    If MsgBox(msg, vbExclamation + vbYesNo, _
              "Confirm Removal") = vbNo Then
        MsgBox "No connections were removed. " & vbCrLf & _
               "Report on '" & OUT_SHEET & "' sheet.", _
               vbInformation, "Cancelled"
        GoTo CleanUp
    End If

    ' ── Remove orphaned connections ────────────────────
    Dim connIndex As Long
    connIndex = ThisWorkbook.Connections.Count

    Do While connIndex >= 1
        Set conn = ThisWorkbook.Connections(connIndex)
        consumerCount = 0

        ' Re-check consumers (safety check before deleting)
        For Each ws In ThisWorkbook.Worksheets
            On Error Resume Next
            For Each qt In ws.QueryTables
                If qt.Connection Like "*" & conn.Name & "*" Then
                    consumerCount = consumerCount + 1
                End If
            Next qt
            On Error GoTo CleanUp
        Next ws

        On Error Resume Next
        For Each pc In ThisWorkbook.PivotCaches
            If pc.Connection Like "*" & conn.Name & "*" Then
                For Each ws In ThisWorkbook.Worksheets
                    For Each pt In ws.PivotTables
                        If pt.CacheIndex = pc.Index Then
                            consumerCount = consumerCount + 1
                        End If
                    Next pt
                Next ws
            End If
        Next pc
        On Error GoTo CleanUp

        If consumerCount = 0 Then
            On Error Resume Next
            conn.Delete
            On Error GoTo CleanUp
        End If

        connIndex = connIndex - 1
    Loop

    ' ── Final report ───────────────────────────────────
    MsgBox "Removed " & orphanCount & _
           " orphaned connection(s). " & vbCrLf & _
           (totalCount - orphanCount) & _
           " active connection(s) remain." & vbCrLf & _
           vbCrLf & "Full report on '" & OUT_SHEET & _
           "' sheet.", vbInformation, "Done"

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

#Why a report sheet instead of just a message box

A message box can only show so much — maybe three connection names before it becomes unreadable. When a workbook has 8 connections with different types, connection strings, and dependency counts, you need something you can scan, sort, and reference.

The report sheet gives you permanent documentation. After the macro runs, the “Connections-Report” sheet stays in the workbook. You can see which connections were removed, which remain, and why. Six months later when someone asks “why doesn’t the inventory pivot refresh anymore?” you can look at the report and see that connection was flagged as “In Use” and left alone — the problem is something else entirely.

The report is formatted as an Excel Table (ListObjects.Add) which gives you free sorting and filtering. Click the “Status” column dropdown and filter to “ORPHANED” to see just the connections that were removed.

#The two-pass safety check

This macro checks dependencies twice — once during the reporting pass, and once again during the deletion pass:

' Pass 1: Reporting
For Each conn In ThisWorkbook.Connections
    ' ... count consumers, build report ...
Next conn

' --- confirmation message box ---

' Pass 2: Deletion (re-checks consumers)
Do While connIndex >= 1
    ' ... re-count consumers ...
    If consumerCount = 0 Then conn.Delete
    connIndex = connIndex - 1
Loop

Why the double-check? Between pass 1 and pass 2, the user reads the confirm message box. If the macro only checked once, the connection states are frozen in time. Re-checking right before deletion is an extra safety net — it ensures that no pivot or query table was somehow re-associated with a connection by the time the deletion happens.

In practice, this almost never changes anything. But when you’re deleting data connections from a production workpaper, a 2-millisecond re-check is cheap insurance.

#Detecting OLEDB vs. ODBC connections

Excel’s WorkbookConnection object doesn’t have a clean .Type property that returns a readable string. Instead, you check which sub-object exists:

If conn.OLEDBConnection Is Not Nothing Then
    connStr = conn.OLEDBConnection.Connection
    connType = "OLEDB"
ElseIf conn.ODBCConnection Is Not Nothing Then
    connStr = conn.ODBCConnection.Connection
    connType = "ODBC"
End If

A connection is OLEDB if the .OLEDBConnection object exists, and ODBC if .ODBCConnection exists. If neither exists, it might be a Power Query connection (which uses ODBC under the hood in some Excel versions) — the On Error Resume Next guards prevent crashes, and the type defaults to “Unknown” if detection fails.

#Counting consumers — QueryTables and PivotCaches

A connection is “in use” if any QueryTable or PivotCache references it. The macro checks both:

' Check QueryTables on every sheet
For Each ws In ThisWorkbook.Worksheets
    For Each qt In ws.QueryTables
        If qt.Connection Like "*" & conn.Name & "*" Then
            consumerCount = consumerCount + 1
        End If
    Next qt
Next ws

' Check PivotCaches
For Each pc In ThisWorkbook.PivotCaches
    If pc.Connection Like "*" & conn.Name & "*" Then
        ' Count pivot tables using this cache
        For Each ws In ThisWorkbook.Worksheets
            For Each pt In ws.PivotTables
                If pt.CacheIndex = pc.Index Then
                    consumerCount = consumerCount + 1
                End If
            Next pt
        Next ws
    End If
Next pc

The Like "*" & conn.Name & "*" check matches if the connection name appears anywhere in the QueryTable or PivotCache connection string. This is deliberately loose — it’s better to flag a connection as “In Use” when it might not be than to flag it as orphaned when it actually is.

PivotCaches get extra scrutiny: if a cache is flagged as matching, the macro counts every PivotTable that uses that cache. A single connection with 3 pivot tables feeding from it shows “In Use (3 consumers)” — which tells you this connection is load-bearing and should not be touched.

#Reverse-order deletion to avoid index shifts

The deletion loop runs backwards:

Dim connIndex As Long
connIndex = ThisWorkbook.Connections.Count

Do While connIndex >= 1
    Set conn = ThisWorkbook.Connections(connIndex)
    ' ... check and delete ...
    connIndex = connIndex - 1
Loop

Deleting connection #3 shifts connections #4, #5, #6… down by one index. If you looped forward (1, 2, 3…), deleting connection #3 means the old #4 becomes the new #3 — and your loop counter already passed #3, so you skip it. Looping backward avoids this entirely. Delete #5, and #1 through #4 are unaffected.

#Why orphaned rows are highlighted red

If consumerCount = 0 Then
    rpt.Range("A" & row & ":E" & row).Interior.Color = _
        RGB(255, 230, 230)
End If

The light red fill on orphaned rows makes the report scannable at a glance. You open the Connections-Report sheet and immediately see: two rows are red, three are white. The red rows are the ones about to be deleted. If a connection you expected to be active is red, you know something’s wrong — cancel the deletion and investigate.

#The confirm message box lists orphan names

Before asking “delete or cancel?” the macro shows exactly which connections are orphaned:

orphanNames = orphanNames & "  • " & conn.Name & vbCrLf

The message box shows the full list. If you see a connection name you recognize — maybe PowerQuery_TB_Import is orphaned because the TB pivot was deleted but you planned to rebuild it — click No. No harm done. The report sheet is already created, so you have documentation either way.

#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.

One macro recipe every two weeks. Unsubscribe anytime.

E

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.

More about me →