Importing Spreedsheet file from SAP GUI using Macro - VBA error 9 subscript out of range while using Workbooks.Close - excel

I'm newbie into macros and VBA - but because specific of my work requires to learn about VBA and macros.
So I've recorded in SAP GUI code below.
File EXPORT.XLSX saves at destination and new EXPORT.XLSX opens itself - But I want to just save it at the destination - without opening it straight away (for that I have different code) - how to do it? Or how to close the EXPORT.XLSX - Workbooks("filenamewithpath").close doesn't work - I'm receiving Error Code 9 :/
Run-time error '9': Subscript out of range
Sub Pobierz_zle()
If Not IsObject(SApplication) Then
Set SapGuiAuto = GetObject("SAPGUI")
Set SApplication = SapGuiAuto.GetScriptingEngine
End If
If Not IsObject(Connection) Then
Set Connection = SApplication.Children(0)
End If
If Not IsObject(session) Then
Set session = Connection.Children(0)
End If
If IsObject(WScript) Then
WScript.ConnectObject session, "on"
WScript.ConnectObject SApplication, "on"
End If
session.findById("wnd[0]").maximize
session.findById("wnd[0]/tbar[0]/okcd").Text = "/n zle_stocklist"
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/usr/ctxt[0]").Text = "3901"
session.findById("wnd[0]/usr/ctxt[0]").caretPosition = 4
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/usr/ctxt[1]").Text = "3182"
session.findById("wnd[0]/usr/ctxt[1]").caretPosition = 4
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/usr/ctxt[3]").Text = "3500"
session.findById("wnd[0]/usr/ctxt[3]").SetFocus
session.findById("wnd[0]/usr/ctxt[3]").caretPosition = 4
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/usr/chk[0]").Selected = False
session.findById("wnd[0]/usr/chk[0]").SetFocus
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]").sendVKey 8
session.findById("wnd[0]/usr/cntlCONTAINER_1/shellcont/shell").pressToolbarContextButton "&MB_EXPORT"
session.findById("wnd[0]/usr/cntlCONTAINER_1/shellcont/shell").selectContextMenuItem "&XXL"
session.findById("wnd[1]/usr/cmb").SetFocus
session.findById("wnd[1]/tbar[0]/btn[0]").press
session.findById("wnd[1]/usr/ctxt[0]").Text = "T:\DEPT-TSCW\WL\Inwentaryzacja\TEMP\"
session.findById("wnd[1]/usr/ctxt[1]").Text = "EXPORT.XLSX"
session.findById("wnd[1]/usr/ctxt[1]").caretPosition = 11
session.findById("wnd[1]").sendVKey 0
Application.Wait (Now + TimeValue("0:00:10"))
ActiveWorkbook.Close (False)
End Sub

Based on this entry you could try the following
Add the following declarations to a module
Option Explicit
' https://stackoverflow.com/a/68004477/6600940
#If VBA7 Then
Private Declare PtrSafe Function FindWindowEx Lib "User32" Alias _
"FindWindowExA" (ByVal hWnd1 As LongPtr, _
ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As LongPtr
Private Declare PtrSafe Function IIDFromString Lib "ole32" _
(ByVal lpsz As LongPtr, ByRef lpiid As GUID) As LongPtr
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hWnd As LongPtr, ByVal dwId As LongPtr, ByRef riid As GUID, _
ByRef ppvObject As Object) As LongPtr
#Else
Private Declare Function FindWindowEx Lib "User32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
Private Declare Function IIDFromString Lib "ole32" _
(ByVal lpsz As Long, ByRef lpiid As GUID) As Long
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As GUID, _
ByRef ppvObject As Object) As Long
#End If
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Const S_OK As Long = &H0
Private Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}"
Private Const OBJID_NATIVEOM As Long = &HFFFFFFF0
As you have the name of the workbook you would like to close you can run the following code. It will counter-times look for that file and try to close it. It might be not the perfect solution but for me it works all the time. Whenever I donwload a file from SAP it will close the automatically opened file regardless wheter it was opened in the same instance or in another one
Sub runCloseWorkbook(wkbName As String, counter As Long)
Const MAX_TRIES As Long = 10
If closeWorkbook(wkbName) Then
Exit Sub
Else
counter = counter + 1
If counter <= MAX_TRIES Then
Application.OnTime Now + TimeValue("00:00:05"), "'runCloseWorkbook """ & wkbName & """,""" & counter & "'"
'Debug.Print "closeWorkbook", wkbName, counter
End If
End If
End Sub
For getting this to work you also need
Private Function closeWorkbook(wkbName As String) As Boolean
Dim hWinXL As Long
hWinXL = FindWindowEx(0&, 0&, "XLMAIN", vbNullString)
Dim xlApp As Excel.Application
Dim wb As Excel.Workbook
Do While hWinXL > 0
If getXLApp(hWinXL, xlApp) Then
For Each wb In xlApp.Workbooks
If wb.Name = wkbName Then
wb.Close False
closeWorkbook = True
' xlApp.Quit
End If
Next
End If
hWinXL = FindWindowEx(0, hWinXL, "XLMAIN", vbNullString)
Loop
End Function
' Private Function getXLApp(hWinXL As Long, xlApp As Excel.Application) As Boolean
Function getXLApp(hWinXL As Long, xlApp As Object) As Boolean
Dim hWinDesk As Long, hWin7 As Long
Dim obj As Object
Dim iid As GUID
Call IIDFromString(StrPtr(IID_IDispatch), iid)
hWinDesk = FindWindowEx(hWinXL, 0&, "XLDESK", vbNullString)
hWin7 = FindWindowEx(hWinDesk, 0&, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj) = S_OK Then
Set xlApp = obj.Application
getXLApp = True
End If
End Function
In your case you could call it like that
runcloseworkbook "T:\DEPT-TSCW\WL\Inwentaryzacja\TEMP\EXPORT.XLSX", 5

Related

Look for an open file before running code

I want to look for the needed reference file before running code.
My code will error out with some of the code running if the file is not found.
something like...
Sub TestByWorkbookName()
Dim wb As Workbook
For Each wb In Workbooks
If wb.Name = "file name" Then
'run code...
End If
Next
MsgBox "File not found"
End Sub
I want to run code if the workbook is open and end sub if not open.
Quick example for checking if any wb has been found:
for each wb in workbooks
if instr(wb.name, "file name")>0 then
check = 1
`do stuff
exit sub
else
check = 0
end if
next wb
if check = 0 then msgbox "File not found."
If I got the OP right he would like to check if a certain workbook is already open in Excel. And in case it is open some code can run then.
My suggestion would be to do it like that
Sub RunCode()
Dim wkbName As String
wkbName = "myWorkbook.xlsx" ' only the workbook name is needed, not the full path
If isWorkbookOpen(wkbName) Then ' code for this function is below
' Run the code you want to run in case the workbook with the name wkbName is open
Debug.Print wkbName & " is open"
Else
' Do not do anything in case the workbook with the name wkbName is not open
Debug.Print wkbName & " is not open"
End If
End Sub
The following code will be needed for the code above
Option Explicit
Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
Private Declare PtrSafe Function IIDFromString Lib "ole32" _
(ByVal lpsz As Long, ByRef lpiid As GUID) As Long
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hWnd As LongPtr, ByVal dwId As Long, ByRef riid As GUID, _
ByRef ppvObject As Object) As LongPtr
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Const S_OK As Long = &H0
Private Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}"
Private Const OBJID_NATIVEOM As Long = &HFFFFFFF0
Function getXLApp(hWinXL As LongPtr, xlApp As Excel.Application) As Boolean
Dim hWinDesk As LongPtr, hWin7 As LongPtr
Dim obj As Object
Dim iid As GUID
Call IIDFromString(StrPtr(IID_IDispatch), iid)
hWinDesk = FindWindowEx(hWinXL, 0&, "XLDESK", vbNullString)
hWin7 = FindWindowEx(hWinDesk, 0&, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj) = S_OK Then
Set xlApp = obj.Application
getXLApp = True
End If
End Function
Function isWorkbookOpen(wkbName As String) As Boolean
Dim hWinXL As LongPtr
hWinXL = FindWindowEx(0&, 0&, "XLMAIN", vbNullString)
Dim xlApp As Excel.Application
Dim wb As Excel.Workbook
Do While hWinXL > 0
If getXLApp(hWinXL, xlApp) Then
For Each wb In xlApp.Workbooks
If wb.Name = wkbName Then
isWorkbookOpen = True
End If
Next
End If
hWinXL = FindWindowEx(0, hWinXL, "XLMAIN", vbNullString)
Loop
End Function

How to collect Excel.Application objects opened in a system?

I found the following code which is based on GetRunningObjectTable - WinAPI function.It collects all excel application objects into var array.
The problem that i am having in this code is that when i press run, it says : "CollectROT.dll" cannot be found.Also I could not find the library for ROT(Running Object Table).
Public Declare PtrSafe Function GetRunningExcelApps Lib "CollectROT.dll" (ByRef result As Variant) As Long
Public Const dllname As String = "CollectROT.dll"
Public glbApp As Application
Public Function GetExcelAppCollection() As Variant
Dim var As Variant
Dim appcalc As Long
Dim app As Application
ChDir (ThisWorkbook.Path)
appcalc = GetRunningExcelApps(var)
If appcalc > 0 Then
GetExcelAppCollection = var
Else
GetExcelAppCollection = Empty
End If
Exit Function
End Function
Please, copy the next code on top of a standard module (in the declarations area). It is designed to work in both (32 - 64 bit) versions:
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Function FindWindowEx Lib "USER32" Alias _
"FindWindowExA" (ByVal hWnd1 As LongPtr, _
ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As LongPtr
Private Declare PtrSafe Function IIDFromString Lib "ole32" _
(ByVal lpsz As LongPtr, ByRef lpiid As GUID) As LongPtr
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hWnd As LongPtr, ByVal dwId As LongPtr, ByRef riid As GUID, _
ByRef ppvObject As Object) As LongPtr
#Else
Private Declare Function FindWindowEx Lib "User32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
Private Declare Function IIDFromString Lib "ole32" _
(ByVal lpsz As Long, ByRef lpiid As GUID) As Long
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As GUID, _
ByRef ppvObject As Object) As Long
#End If
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Const S_OK As Long = &H0
Private Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}"
Private Const OBJID_NATIVEOM As Long = &HFFFFFFF0
Please, copy the next code in the same standard module. This should be the function doing what (I understood) you need:
Function GetExcelAppCollection() As Variant
Dim dict As Object, i As Long
#If VBA7 Then
Dim hWinXL As LongPtr
#Else
Dim hWinXL As Long
#End If
Dim xlApp As Object 'Excel.Application
Set dict = CreateObject("scripting.dictionary")
hWinXL = FindWindowEx(0&, 0&, "XLMAIN", vbNullString)
While hWinXL > 0
i = i + 1
If GetXLapp(hWinXL, xlApp) Then
If Not dict.Exists(xlApp.hWnd) Then
dict.Add xlApp.hWnd, xlApp
End If
End If
hWinXL = FindWindowEx(0, hWinXL, "XLMAIN", vbNullString)
Wend
GetExcelAppCollection = dict.Items
End Function
Copy the next function (called by the above one) in the same module:
#If VBA7 Then
Function GetXLapp(hWinXL As LongPtr, xlApp As Object) As Boolean
Dim hWinDesk As LongPtr, hWin7 As LongPtr
#Else
Function GetXLapp(hWinXL As Long, xlApp As Object) As Boolean
Dim hWinDesk As Long, hWin7 As Long
#End If
Dim obj As Object
Dim iid As GUID
Call IIDFromString(StrPtr(IID_IDispatch), iid)
hWinDesk = FindWindowEx(hWinXL, 0&, "XLDESK", vbNullString)
hWin7 = FindWindowEx(hWinDesk, 0&, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj) = S_OK Then
Set xlApp = obj.Application
GetXLapp = True
End If
End Function
It can be tested using the next test Sub:
Sub testGetExAppColl()
Dim arr As Variant
arr = GetExcelAppCollection
Debug.Print arr(0).Workbooks(1).Name, arr(UBound(arr)).Workbooks.count
End Sub
Please, test it and send some feedback. The code is not so complicated as it can be considered at the first glance... I can simplify it for being used only in 64 bit environment, but I do not think that this can really be an issue.
If something not clear enough, do not hesitate to ask, please.
Edited:
The next function returns all open workbooks (as objects) in a collection. It is similar to the one returning Excel application objects, but it dig a little deeper, extracting all open documents:
Function GetAllWorkbooks() As Collection
Dim i As Long
#If VBA7 Then
Dim hWinXL As LongPtr
#Else
Dim hWinXL As Long
#End If
Dim xlApp As Object 'Excel.Application
Dim wb As Object ' Excel.Workbook
Dim dict, k, Col As New Collection
Set dict = CreateObject("scripting.dictionary")
hWinXL = FindWindowEx(0&, 0&, "XLMAIN", vbNullString)
While hWinXL > 0
i = i + 1
If GetXLapp(hWinXL, xlApp) Then
If Not dict.Exists(xlApp.hWnd) Then
dict.Add xlApp.hWnd, xlApp
For Each wb In xlApp.Workbooks
Col.Add wb
Next
End If
End If
hWinXL = FindWindowEx(0, hWinXL, "XLMAIN", vbNullString)
Wend
Set GetAllWorkbooks = Col
End Function
In order to test it, you can use the next Sub:
Sub EnumAllOpenWorkbooks()
Dim Col As Collection, wb As Workbook
Set Col = GetAllWorkbooks()
For Each wb In Col
'you can find the one you need
Debug.Print wb.Name & ":" & _
IIf(wb.Application.hWnd = Application.hWnd, _
"In this instance", "In another instance")
Next wb
End Sub
.Net has his own "api-dll" to manage proccess.
Try that and tell me if is useful for you.
For Each p As Process In Process.GetProcesses
If String.compare(p.ProcessName, "excel",true) = 0 Then
'Do stuff
msgbox("hi im a excel process")
End If
Next

How to activate workbook exported from SAP?

I have a macro to process data from SAP.
When the workbook from SAP appears I am not able to activate it via VBA code below.
Even Workbooks("export.xlsx").activate does not work.
session.findById("wnd[0]/tbar[0]/okcd").Text = "/nFBL3N"
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/usr/btn%_SD_SAKNR_%_APP_%-VALU_PUSH").press
session.findById("wnd[1]/usr/tabsTAB_STRIP/tabpSIVA/ssubSCREEN_HEADER:SAPLALDB:3010/tblSAPLALDBSINGLE/ctxtRSCSEL_255-SLOW_I[1,0]").Text = "179811"
session.findById("wnd[1]/usr/tabsTAB_STRIP/tabpSIVA/ssubSCREEN_HEADER:SAPLALDB:3010/tblSAPLALDBSINGLE/ctxtRSCSEL_255-SLOW_I[1,1]").Text = "431311"
session.findById("wnd[1]/usr/tabsTAB_STRIP/tabpSIVA/ssubSCREEN_HEADER:SAPLALDB:3010/tblSAPLALDBSINGLE/ctxtRSCSEL_255-SLOW_I[1,1]").SetFocus
session.findById("wnd[1]/usr/tabsTAB_STRIP/tabpSIVA/ssubSCREEN_HEADER:SAPLALDB:3010/tblSAPLALDBSINGLE/ctxtRSCSEL_255-SLOW_I[1,1]").caretPosition = 6
session.findById("wnd[1]/tbar[0]/btn[0]").press
session.findById("wnd[1]/tbar[0]/btn[8]").press
session.findById("wnd[0]/usr/radX_AISEL").Select
session.findById("wnd[0]/usr/ctxtSO_BUDAT-LOW").Text = "01.03.2020"
session.findById("wnd[0]/usr/ctxtSO_BUDAT-HIGH").Text = "31.03.2020"
session.findById("wnd[0]/usr/ctxtSO_BUDAT-HIGH").SetFocus
session.findById("wnd[0]/usr/ctxtSO_BUDAT-HIGH").caretPosition = 10
session.findById("wnd[0]/tbar[1]/btn[16]").press
session.findById("wnd[0]/usr/ssub%_SUBSCREEN_%_SUB%_CONTAINER:SAPLSSEL:2001/ssubSUBSCREEN_CONTAINER2:SAPLSSEL:2000/ssubSUBSCREEN_CONTAINER:SAPLSSEL:1106/ctxt%%DYN009-LOW").Text = "L6"
session.findById("wnd[0]/usr/ssub%_SUBSCREEN_%_SUB%_CONTAINER:SAPLSSEL:2001/ssubSUBSCREEN_CONTAINER2:SAPLSSEL:2000/ssubSUBSCREEN_CONTAINER:SAPLSSEL:1106/ctxt%%DYN009-LOW").SetFocus
session.findById("wnd[0]/usr/ssub%_SUBSCREEN_%_SUB%_CONTAINER:SAPLSSEL:2001/ssubSUBSCREEN_CONTAINER2:SAPLSSEL:2000/ssubSUBSCREEN_CONTAINER:SAPLSSEL:1106/ctxt%%DYN009-LOW").caretPosition = 2
session.findById("wnd[0]/tbar[1]/btn[8]").press
session.findById("wnd[0]/mbar/menu[0]/menu[3]/menu[1]").Select
session.findById("wnd[1]/tbar[0]/btn[11]").press
Set session = Nothing
Set Connection = Nothing
Set SapGuiAuto = Nothing
Set WScript = Nothing
Set Application = Nothing
Dim wb As Workbook
Windows("export.XLSX").Activate
In order to check the existence of the workbook in all open Excel sessions, please use the next code, as I will explain
Firstly copy the next code on top of your module (in the declarations part):
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" ( _
ByVal hWnd As LongPtr, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long
Private Declare PtrSafe Function FindWindowExA Lib "user32" ( _
ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, _
ByVal lpszClass As String, ByVal lpszWindow As String) As LongPtr
#Else
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" ( _
ByVal hwnd As Long, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long
Private Declare Function FindWindowExA Lib "user32" ( _
ByVal hwndParent As Long, ByVal hwndChildAfter As Long, _
ByVal lpszClass As String, ByVal lpszWindow As String) As Long
#End If
Then, use the next function to be called from your existing code (instead of Windows("export.XLSX").Activate):
Private Function GetExcelSes() As Collection
Dim g&(0 To 3), ppv As Object, hWnd, hWnd2, hWnd3
g(0) = &H20400: g(1) = &H0
g(2) = &HC0: g(3) = &H46000000
Dim AlreadyThere As Boolean, Xl As Application
Set GetExcelSes = New Collection
Do
hWnd = FindWindowExA(0, hWnd, "XLMAIN", vbNullString)
If hWnd = 0 Then Exit Do
hWnd2 = FindWindowExA(hWnd, 0, "XLDESK", vbNullString)
hWnd3 = FindWindowExA(hWnd2, 0, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hWnd3, &HFFFFFFF0, g(0), ppv) = 0 Then
AlreadyThere = False
For Each Xl In GetExcelSes
If Xl Is ppv.Application Then
AlreadyThere = True
Exit For
End If
Next
If Not AlreadyThere Then
GetExcelSes.aDD ppv.Application
End If
End If
Loop
End Function
The above function can be called as in the following example (use it like it is instead of your last code line). The declarations are important:
Sub TestSaveAWbFromAllSess()
Dim Ex As Collection, El As Variant
Dim wb As Workbook, expWb As Workbook
Set Ex = GetExcelSes
For Each El In Ex
For Each wb In El.Workbooks
Debug.Print wb.Name 'just to see all open wb names, confirming that the function works...
If UCase(wb.Name) = "EXPORT.XLSX" Then
Set expWb = wb
'Do here whatever you need with the found workbook. For instance:
expWb.SaveCopyAs fileName:=ThisWorkbook.path & "\TestSAPExport.XLSX"
End If
Next
Next
End Sub

Close specific Excel file opened by user from Access-VBA

I see version of how to do this with
Dim ran as Excel.Applcation
but the version of Access I am using doesn't have Excel.Application as an option.
I have written the following code that runs, but does not close the file
Dim Path1 as String
Dim objXL As Object
Dim xlWB As Object
Path1 = "C:/....."
Set objXL = CreateObject("Excel.Application")
Set xlWB = objXL.Workbooks.Open(Path1)
xlWB.Close False
Set xlWB = Nothing
objXL.Quit
Set objXL = Nothing
You can use the following code to close all Excel files (already posted here):
Public Sub CloseAllExcel()
Dim obj As Object
On Error GoTo ExitSub
Dim i As Integer
'There shouldn't be more than 10000 running Excel applications
'Can use While True too, but small risk of infinite loop
For i = 0 To 10000
Set obj = GetObject(, "Excel.Application")
obj.Quit
Next i
ExitSub:
End Sub
But if we're going to close one specific one, we need some Win32 magic that I can't do, but hey, if you can't do something, you can find it on StackOverflow.
Most code by Florent B found here
First, declare our Win32 functions
#If VBA7 Then
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" ( _
ByVal hwnd As LongPtr, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long
Private Declare PtrSafe Function FindWindowExA Lib "user32" ( _
ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, _
ByVal lpszClass As String, ByVal lpszWindow As String) As LongPtr
#Else
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" ( _
ByVal hwnd As Long, ByVal dwId As Long, riid As Any, ppvObject As Object) As Long
Private Declare Function FindWindowExA Lib "user32" ( _
ByVal hwndParent As Long, ByVal hwndChildAfter As Long, _
ByVal lpszClass As String, ByVal lpszWindow As String) As Long
#End If
Then use them to get all running Excel application objects
Public Function GetExcelInstances() As Collection
Dim guid&(0 To 3), acc As Object, hwnd, hwnd2, hwnd3
guid(0) = &H20400
guid(1) = &H0
guid(2) = &HC0
guid(3) = &H46000000
Set GetExcelInstances = New Collection
Do
hwnd = FindWindowExA(0, hwnd, "XLMAIN", vbNullString)
If hwnd = 0 Then Exit Do
hwnd2 = FindWindowExA(hwnd, 0, "XLDESK", vbNullString)
hwnd3 = FindWindowExA(hwnd2, 0, "EXCEL7", vbNullString)
If AccessibleObjectFromWindow(hwnd3, &HFFFFFFF0, guid(0), acc) = 0 Then
GetExcelInstances.Add acc.Application
End If
Loop
End Function
And then use that collection so we can check which one has the workbook open, and close it
Public Sub closeWorkbook(workbookPath As String)
Dim excelInstances As Collection
Set excelInstances = GetExcelInstances
Dim excelApp As Object
Dim excelWorkbook As Object
For Each excelApp In excelInstances
For Each excelWorkbook In excelApp.Workbooks
If excelWorkbook.FullName = workbookPath Then
excelWorkbook.Close False
End If
Next excelWorkbook
If excelApp.Workbooks.Count = 0 Then
excelApp.Quit
End If
Next excelApp
End Sub
And then, implement that close function
Dim Path1 as String
Path1 = "C:/....."
closeWorkbook Path1

Migrating VBA from 32 to 64 bit office fails on "AccessibleObjectFromWindow" in oleacc

I've just moved from office 32 bit to office 64 bit. I have a lot of Outlook macros and Outlook helpfully points out all your VBA code that needs changing, and I've been able to fix most of it. The bit I'm struggling with is the code that I had help writing on one of my last stackexchange posts:
Open attachment in excel window and copy to open workbook
The code is supposed to find an excel window with my spreadsheet in it so I can mess about with it in the remainder of the code. Just to recap, the below works in 32 bit:
Option Explicit
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
Private Declare Function GetDesktopWindow Lib "user32" () As Long
Private Declare Function AccessibleObjectFromWindow& Lib "oleacc" _
(ByVal hwnd&, ByVal dwId&, riid As GUID, xlWB As Object)
Private Const OBJID_NATIVEOM = &HFFFFFFF0
Private Type GUID
lData1 As Long
iData2 As Integer
iData3 As Integer
aBData4(0 To 7) As Byte
End Type
Sub Sample()
Dim Ret
Dim oXLApp As Object, wb As Object
Dim sPath As String, sFileName As String, SFile As String, filewithoutExt As String
Dim IDispatch As GUID
sPath = "C:\Users\Chris\Desktop\"
sFileName = "Data.xlsx": filewithoutExt = "Data"
SFile = sPath & sFileName
Ret = IsWorkBookOpen(SFile)
'~~> If file is open
If Ret = True Then
Dim dsktpHwnd As Long, hwnd As Long, mWnd As Long, cWnd As Long
SetIDispatch IDispatch
dsktpHwnd = GetDesktopWindow
hwnd = FindWindowEx(dsktpHwnd, 0&, "XLMAIN", vbNullString)
mWnd = FindWindowEx(hwnd, 0&, "XLDESK", vbNullString)
While mWnd <> 0 And cWnd = 0
cWnd = FindWindowEx(mWnd, 0&, "EXCEL7", filewithoutExt)
hwnd = FindWindowEx(dsktpHwnd, hwnd, "XLMAIN", vbNullString)
mWnd = FindWindowEx(hwnd, 0&, "XLDESK", vbNullString)
Wend
'~~> We got the handle of the Excel instance which has the file
If cWnd > 0 Then
'~~> Bind with the Instance
Call AccessibleObjectFromWindow(cWnd, OBJID_NATIVEOM, IDispatch, wb)
'~~> Work with the file
With wb.Application.Workbooks(sFileName)
'
'~~> Rest of the code
'
End With
End If
'~~> If file is not open
Else
On Error Resume Next
Set oXLApp = GetObject(, "Excel.Application")
'~~> If not found then create new instance
If Err.Number <> 0 Then
Set oXLApp = CreateObject("Excel.Application")
End If
Err.Clear
On Error GoTo 0
Set wb = oXLApp.Workbooks.Open(SFile)
'
'~~> Rest of the code
'
End If
End Sub
Private Sub SetIDispatch(ByRef ID As GUID)
With ID
.lData1 = &H20400
.iData2 = &H0
.iData3 = &H0
.aBData4(0) = &HC0
.aBData4(1) = &H0
.aBData4(2) = &H0
.aBData4(3) = &H0
.aBData4(4) = &H0
.aBData4(5) = &H0
.aBData4(6) = &H0
.aBData4(7) = &H46
End With
End Sub
'~~> Function to check if file is open
Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False
Case 70: IsWorkBookOpen = True
Case Else: Error ErrNo
End Select
End Function
There seems to be one line failing in the below, which is only invoked when the excel file you're looking is open, and in the 32 bit version it sets 'wb' to the application that's got it open.
I've commented the broken line:
Option Explicit
Private Declare PtrSafe Function FindWindowEx Lib "user32" _
Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr
Public Declare PtrSafe Function GetDesktopWindow Lib "user32" () As LongPtr
Private Declare Function AccessibleObjectFromWindow& Lib "oleacc" _
(ByVal hwnd&, ByVal dwId&, riid As GUID, xlWB As Object)
Private Const OBJID_NATIVEOM = &HFFFFFFF0
Private Type GUID
lData1 As LongPtr
iData2 As Integer
iData3 As Integer
aBData4(0 To 7) As Byte
End Type
Sub Sample()
Dim Ret
Dim oXLApp As Object, wb As Object
Dim sPath As String, sFileName As String, SFile As String, filewithoutExt As String
Dim IDispatch As GUID
sPath = "C:\Users\Chris\Desktop\"
sFileName = "Data.xlsx": filewithoutExt = "Data"
SFile = sPath & sFileName
Ret = IsWorkBookOpen(SFile)
'~~> If file is open
If Ret = True Then
Dim dsktpHwnd As LongPtr, hwnd As LongPtr, mWnd As LongPtr, cWnd As LongPtr
SetIDispatch IDispatch
dsktpHwnd = GetDesktopWindow
hwnd = FindWindowEx(dsktpHwnd, 0&, "XLMAIN", vbNullString)
mWnd = FindWindowEx(hwnd, 0&, "XLDESK", vbNullString)
While mWnd <> 0 And cWnd = 0
cWnd = FindWindowEx(mWnd, 0&, "EXCEL7", filewithoutExt)
hwnd = FindWindowEx(dsktpHwnd, hwnd, "XLMAIN", vbNullString)
mWnd = FindWindowEx(hwnd, 0&, "XLDESK", vbNullString)
Wend
'~~> We got the handle of the Excel instance which has the file
If cWnd > 0 Then
'~~> Bind with the Instance
'!!!!!!!!!!this next line does nothing!!!!!!!
Call AccessibleObjectFromWindow(cWnd, OBJID_NATIVEOM, IDispatch, wb)
'~~> Work with the file
With wb.Application.Workbooks(sFileName)
'
'~~> Rest of the code
'
End With
End If
'~~> If file is not open
Else
On Error Resume Next
Set oXLApp = GetObject(, "Excel.Application")
'~~> If not found then create new instance
If Err.Number <> 0 Then
Set oXLApp = CreateObject("Excel.Application")
End If
Err.Clear
On Error GoTo 0
Set wb = oXLApp.Workbooks.Open(SFile)
'
'~~> Rest of the code
'
End If
End Sub
Private Sub SetIDispatch(ByRef ID As GUID)
With ID
.lData1 = &H20400
.iData2 = &H0
.iData3 = &H0
.aBData4(0) = &HC0
.aBData4(1) = &H0
.aBData4(2) = &H0
.aBData4(3) = &H0
.aBData4(4) = &H0
.aBData4(5) = &H0
.aBData4(6) = &H0
.aBData4(7) = &H46
End With
End Sub
'~~> Function to check if file is open
Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False
Case 70: IsWorkBookOpen = True
Case Else: Error ErrNo
End Select
End Function
You've declared AccessibleObjectFromWindow() using shorthand notation which uses 32bit data types, try changing to:
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal hwnd As LongPtr, ByVal dwId As LongPtr, riid As GUID, xlWB As Object) As LongPtr
Finally found an answer here:
https://stackoverflow.com/a/28504836/317958
So I made two mistakes, first I was over-enthusiastic with changes:
Private Type GUID
lData1 As LongPtr
iData2 As Integer
iData3 As Integer
aBData4(0 To 7) As Byte
End Type
Should remain:
Private Type GUID
lData1 As Long
iData2 As Integer
iData3 As Integer
aBData4(0 To 7) As Byte
End Type
And the declaration should be:
Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" (ByVal Hwnd As LongPtr, ByVal dwId As LongPtr, ByRef riid As GUID, ByRef ppvObject As Object) As LongPtr

Resources