0

I am looking for a way to return the value from an arbitrary workbook (the workbook will also not be open at the time of running the UDF), defined based on calculations in the UDF.

Pseudo code:

Start by calling =someFunc(currentCell) in any cell

Function someFunc(adr As Range)
region_eval = "C" & Range(adr).Row ' where column C contains string entries, all of which have a corresponding sub-dir (see fileReference).
networkLocation = ActiveWorkbook.Path
networkPath = networkLocation & "\Locations\"
fileReference = networkPath & region_eval & "\ProductList.xlsx"

Workbook.Open fileReference readonly
    Perform index/match call against some sheet in this workbook
someFunc = returned value
Close workbook and end function

This is the desired behavior.

The logic to return the desired values is OK, I have tried it in a simpler formula, and in a UDF that relies on the file being opened manually:

INDEX(locationlist_$A$5000, MATCH(masterlist_A1, locationlist_$B$5000))  

I have, after hours of hair-pulling, discovered that this functionality is not directly available in a UDF designed to work on workbooks that aren't opened manually, and that this is intended from Microsoft's side. But I have also discovered that there is a possible workaround!

Ref:
1. https://stackoverflow.com/a/27844592/4604845
2. http://numbermonger.com/2012/02/11/excel-pull-function-creating-dynamic-links-to-closed-workbooks/

These solutions require hardcoded file paths, which defeats the purpose for my intended usage.

Is there anyone who has insight about how to achieve what is achieved in any of the two above links, but with an arbitrary filepath (as in, contained in a cell neighbouring the cell where the UDF is being called from)?

Note: I tried doing the heavy lifting in a sub, and just call the sub as the first line in the UDF, set the result as a global var, and set the UDF return value to the same var after the sub finished, but either I crashed and burned pretty heavily or Excel saw through my trick and denied it.

EDIT:

Here's the sub/func combo.

Option Explicit

Public networkLocation As String, networkPath As String, fileReference As String, c_formula As String
Public sheet_src As Worksheet, sheet As Worksheet, wb_src As Workbook, wb As Workbook
Public region_eval As String, sheetName_src As String, sheetName As String, regionPath As String, fileName As String

Sub findProductStatus(adr As Range)
    networkLocation = ActiveWorkbook.Path
    networkPath = networkLocation & "\Locations\"
    sheetName_src = "Sheet1"
    sheetName = "Sheet1"

    Set wb_src = ThisWorkbook
    Set sheet_src = wb_src.Sheets(sheetName_src)

    region_eval = Range("I" & adr.Row)
    regionPath = networkPath & region_eval
    'fileReference = regionPath & "\ProductList.xlsx"
    fileName = "ProductList.xlsx"
    ChDir regionPath

    Workbooks.Open fileName:=fileName, ReadOnly:=True

    'Set wb = Workbooks.Open(fileName:=ThisWorkbook.Path & "\Locations\Test\ProductList.xlsx", ReadOnly:=True)
    Set wb = Workbooks("ProductList.xlsx")
    Set sheet = wb.Sheets(sheetName)

    c_formula = Application.WorksheetFunction.Index(sheet.Range("$K$2:$K$5000"), Application.WorksheetFunction.Match(sheet_src.Range("A" & adr.Row), sheet.Range("$A$2:$A$5000"), 0))

End Sub


Function getProductStatus(adr As Range) As String

    Call findCourseStatus(adr)
    getCourseStatus = c_formula
    wb.Close

End Function

I haven't tested the sub/func combo against an open file, but when all of the code was inside the Function and the file in question was opened manually, it worked flawlessly. Stepping through the code and using Debug.Print, I see that even though "Workbooks.Open ..." goes through without any discernible error, the workbook doesn't actually get opened, and thus, when we try to use the workbook object to set the sheet, the function/sub terminates.

Community
  • 1
  • 1
Vegard
  • 3,587
  • 2
  • 22
  • 40
  • I found [this](https://technet.microsoft.com/en-us/library/ee692882.aspx) suggestion, but I'd rather avoid it if at all possible! – Vegard May 15 '15 at 12:59
  • I'd suggest posting the code for the `sub()` you tried. Someone may spot an error, or at least have something to start working from. – FreeMan May 15 '15 at 13:09
  • reading through your second link, there was a reference to a [Morefunc add-in](http://www.ozgrid.com/Services/ExternalFree.htm#MoreFunc) (secondary hosting, it seems the original is gone). If [Gary's answer](http://stackoverflow.com/a/30260416/2344413) isn't working for you, that may be worth looking into. (I'm bookmarking it for later review...) – FreeMan May 15 '15 at 13:20
  • Question is updated. I also can't use an add-in of that kind, as I am running in a restricted environment. – Vegard May 15 '15 at 13:28
  • It looks to me like your best bet would be to use ADO and SQL to query the source workbook. – Rory May 15 '15 at 14:26
  • 1
    [This approach](http://stackoverflow.com/a/30179303/2165759) allows to get rid of UDF restrictions, and get a value from a closed workbook via UDF. – omegastripes May 15 '15 at 14:33
  • Also you can open another workbook within UDF just using late bound `CreateObject("Excel.Application")` instance. – omegastripes May 15 '15 at 15:50
  • @omegastripes Your suggestions look very promising for my intended usage. I will test if either of these work for me. – Vegard May 18 '15 at 06:32
  • You can find example with late binding [by the link](http://stackoverflow.com/a/23232311/2165759). – omegastripes May 18 '15 at 10:43
  • Thank you for that. Do you think there would be any issue with using this UDF in many cells "at once"? I mean with the opening and closing of the Excel instance(s). – Vegard May 18 '15 at 12:29
  • 1
    IMO you should open a workbook once per sheet calculation event, at most. Consider opening workbook by first UDF call, keeping opened workbook in global or static variable, and close at the end of calculation event. Otherwise you'll probably get slowdown with number of UDF cells. – omegastripes May 18 '15 at 18:21

1 Answers1

1

This can be achieved with a combination of a UDF() and an Event macro.

To retrieve data from a closed workbook, we need four things:

  1. the path
  2. the filename
  3. the sheetname
  4. the cell address

The only thing the UDF will do is to display these items in a very specific format:

Public Function someFunc() As String
    Dim wbPath As String, wbName As String
    Dim wsName As String, CellRef As String
    Dim Ret As String

    wbPath = "C:\TestFolder\"
    wbName = "ABC.xls"
    wsName = "xxx"
    CellRef = "B9"

    someFunc = "'" & wbPath & "[" & wbName & "]" & _
          wsName & "'!" & Range(CellRef).Address(True, True, -4150)
End Function

Take note of the position of the single quotes.

enter image description here

We then use a Calculate event macro to detect the UDF's execution and retrieve the data:

Private Sub Worksheet_Calculate()
  Dim r1 As Range, r2 As Range
  Set r1 = Range("C3")
  Set r2 = Range("C4")
  r2.Value = ExecuteExcel4Macro(r1.Value)
End Sub

The Calculate macro needs to know where the UDF is returning the string (C3) and it also needs to know where to put the retrieved data (C4).

Gary's Student
  • 95,722
  • 10
  • 59
  • 99
  • Very nice effort, but the full path of the file isn't known until the UDF is running. The file path is determined inside the UDF, based on which value its neighbouring cells have. For instance, if you run the UDF in A2, B2 will contain the string "Paris". UDF will concactenate a string so that the path becomes "..\Location\Paris\File.xlsx" But when UDF runs in A3, B3 contains "London", which means the previous file path can't be used. – Vegard May 15 '15 at 13:21
  • I believe that Gary is suggesting that you use the `UDF()` to build the complete path & put it in a known cell, then use the `On_calculate` event to actually go get the data from the closed workbook. Or possibly vice-versa... – FreeMan May 15 '15 at 13:32
  • Actually, Gary's answer looks very similar to [this](http://spreadsheetpage.com/index.php/site/tip/a_vba_function_to_get_a_value_from_a_closed_file/), which was linked from the SO question you linked. I haven't read it completely, but it looks like it's got a more in-depth description of the solution. – FreeMan May 15 '15 at 13:35
  • I have indeed read that article as well, but I don't understand how it's supposed to take the file path dynamically. It looks like you must supply the file path before the function runs? Or is there something about this concept that completely eludes me? – Vegard May 15 '15 at 13:40
  • I believe the goal is to build the file path, possibly with cell concatenation, then use the `on_change` event to fire off your UDF. Maybe Gary will come back to help clarify. I haven't read either the article or his suggestion fully enough to get my head wrapped around it yet... – FreeMan May 15 '15 at 13:47
  • @FreeMan Your analysis is correct.......the only function the *UDF* should perform is to construct the retrieval string based on dynamic requirements.................in an actual application I would not use this approach...............I would open the other workbooks............ – Gary's Student May 15 '15 at 14:04