1

I have had an issue with an Excel workbook that was giving an external reference warning. Excel gives the option to break these references, but doesn't give any way to identify where the reference is.

I did eventually discover the reference after running the Error Checking utility on every sheet of the workbook. It was hidden inside a table object. The reference was not contained in a data connection, or formula. It is not searchable through the normal Ctrl+F type Find search, nor can I find it contained in any VBA object.

The reference occurs if you create a table object, then add a formula to a column (that has an external reference), then manually replace the formula in each row with something else. Excel maintains the original formula somewhere inside the file. I have tried to access various properties of the ListObject corresponding to the table, the range that contains the column, the ListColumn of the ListObject containing the formula and many others.

The reference is somewhere in the file, it can be shown by clicking the cell's formula error warning and selecting "Restore to Calculated Column Formula".

Is there a way of finding these hidden references systematically with VBA or otherwise?

Edit 1:

I wrote the following script to search various different objects for hidden references to external sources, but haven't found the object that contains this type of reference.

Sub ListLinks()

Dim wb As Workbook
Set wb = Application.ActiveWorkbook

' Identify any links to external workbooks
If Not IsEmpty(wb.LinkSources(xlExcelLinks)) Then
    wb.Sheets.Add
    xIndex = 1
    For Each link In wb.LinkSources(xlExcelLinks)
        Application.ActiveSheet.Cells(xIndex, 1).Value = link
        xIndex = xIndex + 1
    Next link
End If

Dim outSheet As Worksheet
Set outSheet = wb.Worksheets.Add

' Extract all hidden references into a searchable sheet
Dim ws As Worksheet
Dim sh As Shape
With Range("A1:D1")
    .Value = Array("ObjectType", "Parent", "ObjectName", "Reference")
    .Interior.Color = xlColor1
    .Font.Color = xlColor2
End With

' All shape objects can have an action assigned that may be in another workbook
i = 2
For Each ws In Worksheets
    For Each sh In ws.Shapes
        outSheet.Cells(i, 1).Value = "Shape"
        outSheet.Cells(i, 2).Value = ws.Name
        outSheet.Cells(i, 3).Value = sh.Name
        outSheet.Cells(i, 4).Value = "'" & sh.OnAction
        i = i + 1
    Next
Next

' All name references may point to a range or table in another workbook
Dim nm As Name
For Each nm In ActiveWorkbook.Names
    outSheet.Cells(i, 1).Value = "Name"
    outSheet.Cells(i, 3).Value = nm.Name
    outSheet.Cells(i, 4).Value = "'" & nm.RefersTo
    i = i + 1
Next

' All chart series and chart shapes can contain references
Dim ch As Chart
Dim srs As Series
For Each ch In ActiveWorkbook.Charts
    For Each srs In ch.SeriesCollection
        outSheet.Cells(i, 1).Value = "ChartsSeries"
        outSheet.Cells(i, 2).Value = ch.Name
        outSheet.Cells(i, 3).Value = srs.Name
        outSheet.Cells(i, 4).Value = "'" & srs.Formula
        i = i + 1
    For Each sh In ch.Shapes
        outSheet.Cells(i, 1).Value = "ChartsShapes"
        outSheet.Cells(i, 2).Value = ch.Name
        outSheet.Cells(i, 3).Value = sh.Name
        outSheet.Cells(i, 4).Value = "'" & sh.OnAction
        i = i + 1
    Next
    Next
Next

' As above, but for charts in a Worksheet, previous was for Chart Sheets
Dim chOb As ChartObject
For Each ws In Worksheets
    For Each chOb In ws.ChartObjects
        For Each srs In chOb.Chart.SeriesCollection
            outSheet.Cells(i, 1).Value = "ChartsObjectsSeries"
            outSheet.Cells(i, 2).Value = ws.Name & " | " & ch.Name
            outSheet.Cells(i, 3).Value = srs.Name
            outSheet.Cells(i, 4).Value = "'" & srs.Formula
            i = i + 1
        Next
        For Each sh In chOb.Chart.Shapes
            outSheet.Cells(i, 1).Value = "ChartsObjectsShapes"
            outSheet.Cells(i, 2).Value = ws.Name & " | " & ch.Name
            outSheet.Cells(i, 3).Value = sh.Name
            outSheet.Cells(i, 4).Value = "'" & sh.OnAction
            i = i + 1
        Next
    Next
Next

' Query tables can reference external sheets
Dim qryTbl As QueryTable
For Each ws In Worksheets
    For Each qryTbl In ws.QueryTables
        outSheet.Cells(i, 1).Value = "QueryTables"
        outSheet.Cells(i, 2).Value = ws.Name
        outSheet.Cells(i, 3).Value = qryTbl.Name
        outSheet.Cells(i, 4).Value = "'" & qryTbl.Connection
        i = i + 1
    Next
Next

Dim lstObj As ListObject
For Each ws In Worksheets
    For Each lstObj In ws.ListObjects
    For Each qryTbl In lstObj.QueryTables
        outSheet.Cells(i, 1).Value = "TableQueryTables"
        outSheet.Cells(i, 2).Value = ws.Name & " | " & lstObj.Name
        outSheet.Cells(i, 3).Value = qryTbl.Name
        outSheet.Cells(i, 4).Value = "'" & qryTbl.Connection
        i = i + 1
    Next
Next

' OLEObjects such as images can point to external sources
Dim oleOb As OLEObject
For Each ws In Worksheets
    For Each oleOb In ws.OLEObjects
        outSheet.Cells(i, 1).Value = "OLEObjects"
        outSheet.Cells(i, 2).Value = ws.Name
        outSheet.Cells(i, 3).Value = oleOb.Name
        outSheet.Cells(i, 4).Value = "'" & oleOb.SourceName
        i = i + 1
    Next
Next

' Hyperlinks can point to external sources
Dim hypLk As Hyperlink
For Each ws In Worksheets
    For Each hypLk In ws.Hyperlinks
        outSheet.Cells(i, 1).Value = "HyperLinks"
        outSheet.Cells(i, 2).Value = ws.Name
        outSheet.Cells(i, 3).Value = hypLk.Name
        outSheet.Cells(i, 4).Value = "'" & hypLk.SubAddress
        i = i + 1
    Next
Next

End Sub

Edit 2:

From Slai's comment I can see the reference inside the XML of the file in /xl/tables/table1.xml, in

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="Table1" displayName="Table1" ref="A1:B4" totalsRowShown="0">
    <autoFilter ref="A1:B4"/>
    <tableColumns count="2">
        <tableColumn id="1" name="a"/>
        <tableColumn id="2" name="b" dataDxfId="0">
            <calculatedColumnFormula>[1]Sheet1!$A2</calculatedColumnFormula>
        </tableColumn>
    </tableColumns>
    <tableStyleInfo name="TableStyleMedium2" showFirstColumn="0" showLastColumn="0" showRowStripes="1" showColumnStripes="0"/>
</table>

Is there any way to access this from inside the VBA object model?

  • is it listed in the Data tab > Edit Links? It could also be on a control, as they are not searchable with Ctrl+F – Slai Jun 20 '17 at 03:52
  • Yes, but from there, I have no way of finding where the reference is. – SuaveIncompetence Jun 20 '17 at 03:56
  • https://support.office.com/en-us/article/Find-links-external-references-in-a-workbook-fcbf4576-3aab-4029-ba25-54313a532ff1 – Slai Jun 20 '17 at 03:58
  • Thanks Slai, I've already looked into that one, the example I gave isn't covered by any of the scenarios listed in that document – SuaveIncompetence Jun 20 '17 at 04:01
  • 1
    then the only thing I can think of is to unzip the .xlsx file and search the .xml files in it – Slai Jun 20 '17 at 04:03
  • Thanks @Slai, I had thought to look at that, but hadn't done it because I had assumed there would be some other way to access the reference without going to that length. – SuaveIncompetence Jun 21 '17 at 03:29

1 Answers1

1

I've realised you can have Excel show the phantom calculation formula by adding a row to a the ListObject. The safest way to add a row without impacting existing data is to copy the table into a temporary sheet and then resize the listObject in the new sheet.

I've added the following to my existing query from the original question to extract all hidden forumla and references:

Dim tmpSht As Worksheet
For Each ws In Worksheets
    For Each lstObj In ws.ListObjects
        Set tmpSht = Sheets.Add
        lstObj.Range.Copy
        tmpSht.Range("A1").PasteSpecial
        tmpSht.ListObjects(1).Resize tmpSht.ListObjects(1).Range.Resize(lstObj.Range.Rows.Count + 1, lstObj.Range.Columns.Count)
        For j = 1 To lstObj.ListColumns.Count
            outSheet.Cells(i, 1).Value = "Table ListObjects - Calculated Formulas"
            outSheet.Cells(i, 2).Value = ws.Name & " | " & lstObj.Name
            outSheet.Cells(i, 3).Value = lstObj.ListColumns(j).Name
            outSheet.Cells(i, 4).Value = "'" & Cells(tmpSht.ListObjects(1).Range.Rows.Count, j).Formula
            i = i + 1
        Next
        Application.DisplayAlerts = False
        tmpSht.Delete
        Application.DisplayAlerts = True
    Next
Next

Subsequently, I've now found the following question with a similar solution: https://stackoverflow.com/a/40734667/2341820