Excel UDF: retrieving value from arbitrary, closed, external workbook - vba

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.

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:
the path
the filename
the sheetname
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.
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).

Related

vba run other excel files macros

I have following code:
Sub MacroRunner()
Dim Nomefile As String, Nomefolder As String
Nomefolder = ActiveWorkbook.Path
Nomefile = Dir(Nomefolder & "\*.xlsb")
Workbooks.Open (Nomefolder & "\" & Nomefile)
ActiveWorkbook.Worksheets(2).Select
Application.Run "Nomefile!listaIdprodotto" '<-------- "nomefile" variable not returned
Application.DisplayAlerts = False
ActiveWindow.Close
End Sub
The issue is in line marked by a left arrow; Excel doesn't return variable value making itself unable to find asked Macro to be executed.
Thanks for any help.
You should refer a bit differently (in case that Nomefile.xlsb is the file):
Application.Run "'Nomefile.xlsb'!listaIdprodotto"
Or even (in case that Nomefile is a variable):
Application.Run "'" & Nomefile & "'!listaIdprodotto"
Source
It would be better to be more specific about what files and sheets you want to act on.
Nomefolder = ActiveWorkbook.Path
This isn't necessarily the workbook containing the code. If you create a new workbook immediately before running the code then this will equal an empty string - it's whichever workbook is currently on top (active).
Nomefile = Dir(Nomefolder & "\*.xlsb")
This will return the first file in the folder that has an xlsb extension. If todays file wasn't created it will return the previous file and run yesterdays update again.
If it's a file that's generated each day then look for the file name with the correct date.
ActiveWorkbook.Worksheets(2).Select
Again - same problem with ActiveWorkbook. This is also looking at the second sheet in the tab order which may not be the sheet you're after if someone moved it. Reference the sheet by name (which could still be changed). It would be better to reference by sheet CodeName which can't be changed by the user, but that opens a different kettle of worms as the sheet you're referencing isn't in the workbook containing the code.
Application.Run "Nomefile!listaIdprodotto"
As you've enclosed Nomefile within the double quotes it's not seeing it as a variable but as a file called Nomefile. To see it as a variable it needs to be written as Application.Run Nomefile & "!listaIdprodotto". If the file name contains spaces then it needs to be written as Vityata has written: Application.Run "'" & Nomefile & "'!listaIdprodotto". This encloses the file name in single quotes.
I'd rewrite the code as:
Public Sub Test()
Dim Nomefile As String, Nomefolder As String
Dim wrkBk As Workbook
'Nomefolder = ActiveWorkbook.Path
Nomefolder = ThisWorkbook.Path
'Nomefile = Dir(Nomefolder & "\*.xlsb")
Nomefile = Nomefolder & "\WorkbookWithCode.xlsb"
'Open the workbook, run the code and close the workbook.
Set wrkBk = Workbooks.Open(Nomefile)
Application.Run "'" & wrkBk.Name & "'!listaIdprodotto"
wrkBk.Close
End Sub
The main difference here is that I set the whole workbook to a variable - Set wrkBk = ..... From there on I can always reference the correct workbook and don't have to worry whether it's Active or not.

Option Explicit and Macros in a function [duplicate]

This question already has answers here:
running excel macro from another workbook
(4 answers)
Closed 5 years ago.
I have the following code:
Option Explicit
Public Function LastWeekOfMonth() As Boolean
'finds the current date
Dim CurrentDate As Date
CurrentDate = CDate(ActiveSheet.Cells(FIRST_DATA_ROW, 1))
'find filepath and filename
Dim filepath As String
Dim filename As String
filepath = "C:\target\file\path\here\"
filename = Cells(3, 4) & ".m_d.xlsm"
MsgBox (filepath & filename)
'if it is the last week of the month, write a monthly report, and return true to continue with the face to face paperwork
If (31 - Day(CurrentDate)) <= 7 Then
'write a monthly report
Dim app As New Excel.Application
Dim wsheet As New Worksheet
Dim book As Excel.Workbook
app.Visible = False 'Visible is False by default, so this isn't necessary
Set book = app.Workbooks.Open(filepath & filename)
Set wsheet = book.Worksheets("Monthly")
'Application.Run ("WriteMonthly")
app.book.wsheet.Run ("WriteMonthly")
book.Close SaveChanges:=True
app.Quit
Set app = Nothing
LastWeekOfMonth = True
'if it is not, simply continue with the face to face paperwork
Else
LastWeekOfMonth = False
End If
End Function
WriteMonthly is a macro that is currently contained in the target spreadsheet. Documentation online says that macros can be run through application.run(macro name, arg1,arg2,arg3, ...)
I want to pull information from the current spreadsheet to call up a specific spreadsheet and run a macro on that spreadsheet, with the code for that macro being on that spreadsheet, and eventually any data that is generated to be stored on that spreadsheet.
This is part of a form which looks at if the last days of the month have been reached; if they have, write a specific "monthly" report.
The problem is this:
app.book.wsheet.Run ("WriteMonthly")
doesn't work and neither does:
Application.run("!WriteMonthly")
The syntax is Application.Run "'WbkName'!ProcedureName"
However because the procedure you are calling resides in an Object Module (a worksheet in this case) the syntax has to include the name of the object as well, such as this: Application.Run "'WbkName'!ObjectName.ProcedureName"
Application.Run "'" & filename & "'!ObjectName.WriteMonthly"
Replace ObjectName with the name of the VbModule of the worksheet Monthly.
Try: Application.Run "'" & filename & "'!" & wsheet.CodeName & ".WriteMonthly"
Also ensure that procedure WriteMonthly is not declared as Private in the `Monthly' Module

Trouble Saving Newly Created VBA Workbook

I'm starting a new project and having trouble right at the start =[. So often I need to pull out specific data from a very large excel sheet and create a new excel sheet for just that data. At the moment I am currently trying to create a new workbook and save it to a file path. I am getting the error on the SaveAs execution line. Any idea why this might be happening? The error is:
"Method 'Save As' of object' _Workbook' failed.
Dim Path As String
Dim dat As String
Dim Client As String
Path = "C:\Back\Test\"
ThisWorkbook.Sheets("Control Panel").Activate
dat = Range("F42")
Client = Range("F43")
Workbooks.Add
ActiveWorkbook.SaveAs Filename:=Path & Date & "-" & Client & ".xls", FileFormat:=xlNormal
newWBName = ActiveWorkbook.Name
I will propose my access to your need.
Here is sub which should do what you need. So first i recomend to Dim all of your variables and do not use activate. Instead use sheet variable and also acces single values via cells not via range.
But your main issue maybe is that you try to use reserved word Date. Let me know if something isnt clear to you.
Sub save()
Dim filePath As String
Dim dateFromSheet As String
Dim clientName As String
Dim controlPanelSheet As Worksheet
Dim newWorkbookName As String
Set controlPanelSheet = Sheets("Control Panel")
filePath = "c:\Users\sukl\Documents\"
With controlPanelSheet
dateFromSheet = .Cells(42, "F").Value
clientName = .Cells(43, "F").Value
End With
ThisWorkbook.SaveAs Filename:=filePath & dateFromSheet & "-" & clientName & ".xls", FileFormat:=xlNormal
newWorkbookName = ThisWorkbook.Name
End Sub

Excel/VBA: Can't open workbook

Function getProductStatus(adr As Range) As String
Dim networkLocation As String, networkPath As String, fileReference As String, srcSheetRef As String, c_formula As String
Dim sheet_src As Worksheet, sheet As Worksheet, wb_src As Workbook, wb As Workbook
Dim sheetName_src As String, sheetName As String
networkLocation = ActiveWorkbook.Path
networkPath = networkLocation & "\Locations\"
sheetName_src = "Sheet1"
sheetName = "Sheet1"
Set wb_src = ThisWorkbook
Set sheet_src = wb_src.Sheets(sheetName_src)
fileReference = networkPath & region_eval & "\ProductList.xlsx"
Workbooks.Open Filename:=fileReference, 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))
getProductStatus = c_formula
wb.Close
End Function
The following formula works if entered directly in a cell:
=INDEX('\\..somePath\Locations\[ProductList.xlsx]Sheet1'!$K$2:$K$5000;MATCH('Sheet1'!A2;'\\..somePath\Locations\[ProductList.xlsx]Sheet1'!$A$2:$A$5000;0))
EDIT:
The question was previously about a #NAME error, but that has been resolved.. only to evolve into a worse one.
This function did, at some point, work as intended (after the #NAME was resolved). Between then and now, nothing has changed except closing the file being asked for. And now, opening the file before running the function does nothing.
How can I debug this? Why did Excel stop opening the file?
EDIT #2: The files opened in different instances, apparently, because the function does work if both files are open in the same instance. So the true question is now, why doesn't my code succeed in opening the file "on it's own"?

Copy data from another Workbook through VBA

I want to collect data from different files and insert it into a workbook doing something like this.
Do While THAT_DIFFERENT_FILE_SOMEWHERE_ON_MY_HDD.Cells(Rand, 1).Value <> "" And Rand < 65536
then 'I will search if the last row in my main worksheet is in this file...
End Loop
If the last row from my main worksheet is in the file, I'll quit the While Loop. If not, I'll copy everything. I'm having trouble finding the right algorithm for this.
My problem is that I don't know how to access different workbooks.
The best (and easiest) way to copy data from a workbook to another is to use the object model of Excel.
Option Explicit
Sub test()
Dim wb As Workbook, wb2 As Workbook
Dim ws As Worksheet
Dim vFile As Variant
'Set source workbook
Set wb = ActiveWorkbook
'Open the target workbook
vFile = Application.GetOpenFilename("Excel-files,*.xls", _
1, "Select One File To Open", , False)
'if the user didn't select a file, exit sub
If TypeName(vFile) = "Boolean" Then Exit Sub
Workbooks.Open vFile
'Set targetworkbook
Set wb2 = ActiveWorkbook
'For instance, copy data from a range in the first workbook to another range in the other workbook
wb2.Worksheets("Sheet2").Range("C3:D4").Value = wb.Worksheets("Sheet1").Range("A1:B2").Value
End Sub
You might like the function GetInfoFromClosedFile()
Edit: Since the above link does not seem to work anymore, I am adding alternate link 1 and alternate link 2 + code:
Private Function GetInfoFromClosedFile(ByVal wbPath As String, _
wbName As String, wsName As String, cellRef As String) As Variant
Dim arg As String
GetInfoFromClosedFile = ""
If Right(wbPath, 1) <> "" Then wbPath = wbPath & ""
If Dir(wbPath & "" & wbName) = "" Then Exit Function
arg = "'" & wbPath & "[" & wbName & "]" & _
wsName & "'!" & Range(cellRef).Address(True, True, xlR1C1)
On Error Resume Next
GetInfoFromClosedFile = ExecuteExcel4Macro(arg)
End Function
Are you looking for the syntax to open them:
Dim wkbk As Workbook
Set wkbk = Workbooks.Open("C:\MyDirectory\mysheet.xlsx")
Then, you can use wkbk.Sheets(1).Range("3:3") (or whatever you need)
There's very little reason not to open multiple workbooks in Excel. Key lines of code are:
Application.EnableEvents = False
Application.ScreenUpdating = False
...then you won't see anything whilst the code runs, and no code will run that is associated with the opening of the second workbook. Then there are...
Application.DisplayAlerts = False
Application.Calculation = xlManual
...so as to stop you getting pop-up messages associated with the content of the second file, and to avoid any slow re-calculations. Ensure you set back to True/xlAutomatic at end of your programming
If opening the second workbook is not going to cause performance issues, you may as well do it. In fact, having the second workbook open will make it very beneficial when attempting to debug your code if some of the secondary files do not conform to the expected format
Here is some expert guidance on using multiple Excel files that gives an overview of the different methods available for referencing data
An extension question would be how to cycle through multiple files contained in the same folder. You can use the Windows folder picker using:
With Application.FileDialog(msoFileDialogFolderPicker)
.Show
If .Selected.Items.Count = 1 the InputFolder = .SelectedItems(1)
End With
FName = VBA.Dir(InputFolder)
Do While FName <> ""
'''Do function here
FName = VBA.Dir()
Loop
Hopefully some of the above will be of use
I had the same question but applying the provided solutions changed the file to write in. Once I selected the new excel file, I was also writing in that file and not in my original file. My solution for this issue is below:
Sub GetData()
Dim excelapp As Application
Dim source As Workbook
Dim srcSH1 As Worksheet
Dim sh As Worksheet
Dim path As String
Dim nmr As Long
Dim i As Long
nmr = 20
Set excelapp = New Application
With Application.FileDialog(msoFileDialogOpen)
.AllowMultiSelect = False
.Filters.Add "Excel Files", "*.xlsx; *.xlsm; *.xls; *.xlsb", 1
.Show
path = .SelectedItems.Item(1)
End With
Set source = excelapp.Workbooks.Open(path)
Set srcSH1 = source.Worksheets("Sheet1")
Set sh = Sheets("Sheet1")
For i = 1 To nmr
sh.Cells(i, "A").Value = srcSH1.Cells(i, "A").Value
Next i
End Sub
With excelapp a new application will be called. The with block sets the path for the external file. Finally, I set the external Workbook with source and srcSH1 as a Worksheet within the external sheet.