Below is a working code snippet that opens a Microsoft Word document, and goes to a specific index from the Table of Contents. filePath is a filepath, and strTopic is a value that links to the Table of Contents in the Word document.
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
Set docWord = objWord.Documents.Open(fileName:=strPath, ReadOnly:=True)
docWord.Bookmarks(strTopic).Range.Select
I need to bring the Word document to the foreground.
Is there a toFront() type "function" in VBA?
You can achieve what you want using APIS. I am using two APIs SetForegroundWindow and FindWindow
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) _
As Long
Private Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) _
As Long
Sub Sample()
Dim objWord As Object, docWord As Object
Dim strPath As String, FileName As String
Dim hwnd As Long
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
'~~> Change this to the relevant Filename and path
strPath = "C:\Users\Siddharth Rout\Desktop\Sample.docx"
'~~> Put the acutal file name here without the extension
FileName = "Sample"
Set docWord = objWord.Documents.Open(FileName:=strPath, ReadOnly:=True)
hwnd = FindWindow(vbNullString, FileName & " [Read-Only] - Microsoft Word")
If hwnd > 0 Then
SetForegroundWindow (hwnd)
End If
End Sub
NOTE: If you are sure that there is no other Word Application open other than what you opened then you can use this as well :)
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Sub Sample()
Dim objWord As Object, docWord As Object
Dim strPath As String
Dim hwnd As Long
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
'~~> Change this to the relevant Filename and path
strPath = "C:\Users\Siddharth Rout\Desktop\Sample.docx"
Set docWord = objWord.Documents.Open(FileName:=strPath, ReadOnly:=True)
hwnd = FindWindow("OpusApp", vbNullString)
If hwnd > 0 Then
SetForegroundWindow (hwnd)
End If
End Sub
How about,
docWord.Activate
This should bring the file that has been "Set" for the docWord object to foreground.
EDIT: Tested this on Access, quiet unreliable on Excel. Using an API is the best way to go if there are multiple instances of the Word application running.
Once you've opened a document (or added one) you can get a Hwnd to pass to the SetForegroundWindow API function from the ActiveWindow object (e.g. obWord.ActivieWindow.Hwnd). That way you don't need to search for the correct Word instance to bring to front.
This seems to work every time. Word running or not, multiple docs open or not.
OpenAlready:
On Error GoTo 0
With wrdApp
.Selection.Goto What:=1, Which:=2, Name:=PageNumber
.Visible = True
.Activate '<---seems to work well. regardless of number of Word docs open OR not open.
End With
Set wrdDoc = Nothing
Set wrdApp = Nothing
i'm quite new here, and in doing a ~30 min research on this specific case, I think I could bring something to the table here...
I found in this link: http://www.vbaexpress.com/forum/showthread.php?27589-bringing-Word-in-fornt-of-Excel this line of code:
Application.ActivateMicrosoftApp xlMicrosoftWord
It will force Word in front of everything, BUT if there is something opened it will create a new document, so what I found is that, if you want to open something and then bring it to front you use this sintax:
[...]
Application.ActivateMicrosoftApp xlMicrosoftWord
Word.Documents.Open(MyDocument)
[...]
Once in vba debugging the code will always be in front, but when using the code with the excel vba userform it works great! I can see the word changing the words on the fly!!!!
Can also use AppActivate "Microsoft Word"
visit Microsoft help :here
Using AppActivate needs the exact title of the window you want focused. E.g. for a word 2013 template opened as an "add", you would have to use AppActivate "Document1 - Word"
I just use;
FileAndPath = "C:\temp\Mydocument.docx"
ThisWorkbook.FollowHyperlink (FileAndPath)
It even works if the file is already open.
Since ".Activate" is comented various times but at least for me calling it from excel to bring to fornt a word doc.
I have to do a work around minimizing and maximazing the window.
This works well for me until now:
Set wdApp = CreateObject("Word.Application")
wdApp.Visible = True
wdApp.ScreenUpdating = True
Set wdDoc = wdApp.Documents.Open(ThisWorkbook.Path & "\" & stWordDocument) 'update your path
wdDoc.Activate
ActiveDocument.ActiveWindow _
.WindowState = wdWindowStateMinimize
ActiveDocument.ActiveWindow _
.WindowState = wdWindowStateMaximize
found!
ActiveDocument.Activate 'might not be necessary
ActiveDocument.Windows.Application.WindowState = wdWindowStateMaximize
works flawlessly. I already had an "activedocument" I was working on.
http://www.access-programmers.co.uk/forums/showthread.php?t=173871
Related
Suppose I have two hyperlinks (on excel sheet) referring to two documents:
e.g ( A.doc and B.doc ) on my local intranet.
I will open the first document "A.doc" then I will open the second one "B.doc"
The problem:
If there is already an opened word document and then I clicked hyperlink (Word Document on my local intranet),
The later file is not opened automatically and I have to click on the flashing taskbar button to open the cited second file.
This issue occurs only with Microsoft word documents found on my local intranet.
If there is no open document and I clicked on any word hyperlink, It opens normally without any issue.
Please watch this short video to understand my problem.
I need to utilize FollowHyperlink event in excel or any other method to:
bring the previous opened window A.doc to front and then bring the second one B.doc to front.
you may find it a strange question! But I have to do it manually each time to show and bring the second one to front.
I have used this API code (in a Word document) on Normal-ThisDocument:
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Dim LHwnd As Long
Private Sub Document_Open()
If Application.Documents.Count > 1 Then
LHwnd = FindWindow("rctrl_renwnd32", Application.ActiveWindow.Caption)
SetForegroundWindow (LHwnd)
End If
End Sub
And used that code on my excel sheet itself:
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
On Error Resume Next
Dim objWd As Object
Set objWd = GetObject(, "Word.Application")
AppActivate objWd.ActiveWindow.Caption
Set objWd = Nothing
End Sub
Finally, I found this helpful page Bring an external application window to the foreground But I could not adapted it to my need.
Please, try the next BeforeDoubleClick event. If the problem is related only to hyperlinks, it should work...
Option Explicit
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Target.column = 1 And Target.Value <> "" Then 'limit this behavior to the first column
If LCase(left(Target.Value, 5)) = "http:" Then
Cancel = True
Dim objWd As Object, d As Object, arrD: arrD = Split(Target.Value, ".")
If LCase(left(arrD(UBound(arrD)), 3)) <> "doc" Then Exit Sub
On Error Resume Next
Set objWd = GetObject(, "Word.Application") 'find the Word open session, if any
On Error GoTo 0
If objWd Is Nothing Then
Set objWd = CreateObject("Word.Application")
End If
With objWd
.Visible = True
Set d = .Documents.Open(Target.Value)
End With
'force somehow the new open document window expose its handler...
Dim i As Long
Do Until objWd.ActiveWindow.Caption = d.name Or _
objWd.ActiveWindow.Caption = left(d.name, InstRev(d.name, ".")-1) & " [Read-Only] [Compatibility Mode]"
i = i + 1: Debug.Print objWd.ActiveWindow.Caption, left(d.name, InstRev(d.name, ".")-1) & " [Read-Only] [Compatibility Mode]"
DoEvents: If i >= 10 Then Exit Do 'just in case, if something unexpected happens...
Loop
SetForegroundWindow CLngPtr(objWd.ActiveWindow.hWnd)
End If
End If
End Sub
It should work in 64 bit, but it is easy to be adapted for both cases, supposing that it works as you need.
I've searched high and low and the following code is the closest I've come to my objective.
This is what I'm working on:
I wrote some code (OK, honestly, mostly copied bits and pieces and pasted into what is probably jumbled code that works) to email documents to my students. If a doc is open, I get and error, which allows me to manually save and close the doc (thx to Debug), and continue on. I would like to automate this, but Word seems to make things a tad difficult by opening each doc in a separate instance. I can get one instance and its doc, but if it is not the one I need, I cannot save and close it. I found how to get the other instances, but I have not found how to check each instance to see if the doc which it opened is the one I want.
I used ZeroKelvin's UDF in (Check if Word instance is running), which I modified a little bit...
Dim WMG As Object, Proc As Object
Set WMG = GetObject("winmgmts:")
For Each Proc In WMG.InstancesOf("win32_process")
If UCase(Trim(Proc.Name)) = "WINWORD.EXE" Then
*'Beginning of my code...*
*'This is what I need and have no idea how to go about*
Dim WdApp as Word.Application, WdDoc as Object
*' is it better to have WdDoc as Document?*
set WdDoc = ' ### I do not know what goes here ...
If WdDoc.Name = Doc2Send Or WdDoc.Name = Doc2SendFullName Then
*' ### ... or how to properly save and close*
WdApp.Documents(Doc2Send).Close (wdPromptToSaveChanges)
Exit For
End If
*'... end of my code*
Exit For
End If
Next 'Proc
Set WMG = Nothing
Thank you for your time and effort.
Cheers
You may like to consider controlling the number of instances of the Word application that are created. The function below, called from Excel, will return an existing instance of Word or create a new one only if none existed.
Private Function GetWord(ByRef WdApp As Word.Application) As Boolean
' 256
' return True if a new instance of Word was created
Const AppName As String = "Word.Application"
On Error Resume Next
Set WdApp = GetObject(, AppName)
If Err Then
Set WdApp = CreateObject(AppName, "")
End If
WdApp.Visible = True
GetWord = CBool(Err)
Err.Clear
End Function
The function is designed for early binding, meaning you need to add a reference to the Microsoft Word Object Library. During development it's better to work that way. You can change to late binding after your code has been fully developed and tested.
Please take note of the line WdApp.Visible = True. I added it to demonstrate that the object can be modified. A modification done within the If Err bracket would apply only to a newly created instance. Where I placed it it will apply regardless of how WdApp was created.
The next procedure demonstrates how the function might be used in your project. (You can run it as it is.)
Sub Test_GetWord()
' 256
Dim WdApp As Word.Application
Dim NewWord As Boolean
Dim MyDoc As Word.Document
NewWord = GetWord(WdApp)
If NewWord Then
Set MyDoc = WdApp.Documents.Add
MsgBox "A new instance of Word was created and" & vbCr & _
"a document added named " & MyDoc.Name
Else
MsgBox "Word is running and has " & WdApp.Documents.Count & " document open."
End If
End Sub
As you see, the variable WdApp is declared here and passed to the function. The function assigns an object to it and returns information whether that object previously existed or not. I use this info to close the instance if it was created or leave it open if the user had it open before the macro was run.
The two message boxes are for demonstration only. You can use the logical spaces they occupy to do other things. And, yes, I would prefer to assign each document in an instance I'm looking at to an object variable. While using early binding you will get the added benefit of Intellisense.
EDIT
Your procedure enumerates processes. I wasn't able to find a way to determine convert the process into an instance of the application. In other words, you can enumerate the processes and find how many instances of Word are running but I can't convert any of these instances into a particular, functioning instance of the application so as to access the documents open in it. Therefore I decided to enumerate the windows instead and work from there back to the document. The function below specifically omits documents opened invisibly.
Option Explicit
Private Declare PtrSafe Function apiGetClassName Lib "user32" Alias _
"GetClassNameA" (ByVal Hwnd As Long, _
ByVal lpClassname As String, _
ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function apiGetDesktopWindow Lib "user32" Alias _
"GetDesktopWindow" () As Long
Private Declare PtrSafe Function apiGetWindow Lib "user32" Alias _
"GetWindow" (ByVal Hwnd As Long, _
ByVal wCmd As Long) As Long
Private Declare PtrSafe Function apiGetWindowLong Lib "user32" Alias _
"GetWindowLongA" (ByVal Hwnd As Long, ByVal _
nIndex As Long) As Long
Private Declare PtrSafe Function apiGetWindowText Lib "user32" Alias _
"GetWindowTextA" (ByVal Hwnd As Long, ByVal _
lpString As String, ByVal aint As Long) As Long
Private Const mcGWCHILD = 5
Private Const mcGWHWNDNEXT = 2
Private Const mcGWLSTYLE = (-16)
Private Const mcWSVISIBLE = &H10000000
Private Const mconMAXLEN = 255
Sub ListName()
' 256
' adapted from
' https://www.extendoffice.com/documents/excel/4789-excel-vba-list-all-open-applications.html
Dim xStr As String
Dim xStrLen As Long
Dim xHandle As Long
Dim xHandleStr As String
Dim xHandleLen As Long
Dim xHandleStyle As Long
Dim WdDoc As Word.Document
Dim Sp() As String
On Error Resume Next
xHandle = apiGetWindow(apiGetDesktopWindow(), mcGWCHILD)
Do While xHandle <> 0
xStr = String$(mconMAXLEN - 1, 0)
xStrLen = apiGetWindowText(xHandle, xStr, mconMAXLEN)
If xStrLen > 0 Then
xStr = Left$(xStr, xStrLen)
xHandleStyle = apiGetWindowLong(xHandle, mcGWLSTYLE)
If xHandleStyle And mcWSVISIBLE Then
Sp = Split(xStr, "-")
If Trim(Sp(UBound(Sp))) = "Word" Then
ReDim Preserve Sp(UBound(Sp) - 1)
xStr = Trim(Join(Sp, "-"))
Set WdDoc = Word.Application.Documents(xStr)
' this applies if the document was not saved:-
If WdDoc.Name <> xStr Then Set WdDoc = GetObject(xStr)
Debug.Print xStr,
Debug.Print WdDoc.Name
End If
End If
End If
xHandle = apiGetWindow(xHandle, mcGWHWNDNEXT)
Loop
End Sub
Note that it's important to have the API functions at the top of the module - no code above them. Your question doesn't extend to what you want to do with the files but you wanted them listed, and that is accomplished.
I'm trying to create a VBA script on an instance A for copying basic stuff on an instance B of Word generated by a tier program with a temporary and unpredictable name , so I'm not able to use the GetObject(Path,) to get this instance with the Path because I don't have it.
My temporary solution is a PowerShell running this command from the Instance A to get the name of all Windows with "Word" in the Title... and store it in a VBA variable to detect if the name is from an other Instance than Instance A :
Get-Process |Where-Object {$_.mainWindowTitle -like "*Word*"} |format-table mainwindowtitle
It works but I can't believe there is no way to detect all running instances of an Application directly from VBA even with an unknown path.
I tried ugly stuff like this in VBA to cross over different Instances without success:
Sub GetAllInstance()
Dim WordApp As Word.Application, wordInstance As Object
Set WordApp = GetObject(, "Word.Application")
For Each wordInstance In WordApp
MsgBox (wordInstance)
Next wordInstance
End Sub
And the Immediate Command show me that the GetObject only have information about my Instance A, resulting only 1 documents even if 3 are opened on separates instance:
?WordApp.Documents.Count
1
EDIT 20/02:
With the good advices of Cindy, I changed my approch trying to work with process, I successfully detected differents PID of my running instances with the code below:
Sub IsProcessRunning()
Dim process As String
Dim objList As Object
Dim xprocess As Variant
Dim wdApp As Word.Application
process = "Word.exe"
Set objList = GetObject("winmgmts:") _
.ExecQuery("select ProcessID from win32_process where name='" & process & "'")
For Each xprocess In objList
Debug.Print xprocess.ProcessID
AppActivate (xprocess.ProcessID)
Set wdApp = GetObject(, "Word.Application")
Debug.Print wdApp.Workbooks(1).Name
Next xprocess
End Sub
Unfortunatly, activate an application do not clear the ROT, I'm now trying to find a way to clear it and refresh it to register the new activated application in the ROT and use the GetObject with the good instance.
Finally found a solution !
With the code below, because I know where my third-software generate the temporary file with the new instance, I search the name of the file using HWND and the GetWindowText from user32 lib. It permit to me to assign the GetObject using the full path and make interaction between my two documents from two separated instances. Thanks to Cindy and Mathieu for their help:
' API declaration
Const GW_HWNDNEXT = 2
Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Sub CommandButton1_Click()
Dim openDoc As Document, sourceDoc As Document, targetDoc As Object
Dim hWndThis As Long
Dim sTitle As String
Dim xTable As Table
''' INITIALIZATION '''
'Assign the source Document
Set sourceDoc = ActiveDocument
'Detect each instance by Window Name, then assign it to different object
hWndThis = FindWindow(vbNullString, vbNullString)
While hWndThis
sTitle = Space$(255)
sTitle = Left$(sTitle, GetWindowText(hWndThis, sTitle, Len(sTitle)))
If sTitle Like "*tmp*.DOC*" Then
FileToOpen = Left(sTitle, Len(sTitle) - 8)
Set targetDoc = GetObject("C:\Users\xxxxx\AppData\Local\Temp" & "\" & FileToOpen)
GoTo EndLoop:
End If
hWndThis = GetWindow(hWndThis, GW_HWNDNEXT)
Wend
EndLoop:
End Sub
Cell A3 contains folder path. Cells below contain file names with extensions. Upon selecting a cell below, my Excel macro opens that file's location in File Explorer and out of multiple files in that folder selects this particular one, which can be seen in Preview. When next cell containing another file name is selected on the spreadsheet, another File Explorer window opens, even though it's the same path from A3. Looking for a line of code to add which will first close the first File Explorer window, before opening a new one. The code needs to be closing that specific File Explorer window from cell A3, out of multiple open File Explorer windows. Code I have so far
UPDATE: Running below codes, but it does not close the existing opened folder, just opens yet another:
If Target.Column = 1 And Target.Row > 5 Then
Call CloseWindow
Shell "C:\Windows\explorer.exe /select," & Range("A3") & ActiveCell(1, 1).Value, vbNormalFocus 'this works, but opens NEW folder every time
and in separate Module:
'BELOW GOES WITH Public Sub CloseWindow() FROM: https://stackoverflow.com/questions/49649663/close-folder-opened-through-explorer-exe
Option Explicit
''for 64-bit Excel use
'Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
' (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Long) As LongPtr
''for 32-bit Excel use
'Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
' (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
'To make it compatible with both 64 and 32 bit Excel you can use
#If VBA7 Then
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Long) As LongPtr
#Else
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
#End If
'Note that one of these will be marked in red as compile error but the code will still run.
Const WM_SYSCOMMAND = &H112
Const SC_CLOSE = &HF060
Public Sub CloseWindow()
Dim sh As Object
Set sh = CreateObject("shell.application")
Dim w As Variant
For Each w In sh.Windows
'print all locations in the intermediate window
Debug.Print w.LocationURL
' select correct shell window by LocationURL
' If w.LocationURL = "file://sharepoint.com#SSL/DavWWWRoot/sites/folder" Then
'If w.LocationURL = "Range("M1").value" Then
If w.LocationURL = "file://K:/ppp/xx/yy/1 - zzz" Then
SendMessage w.hWnd, WM_SYSCOMMAND, SC_CLOSE, 0
End If
Next w
End Sub
UPDATE 2:
I am now thinking however, that probably the best solution would actually be not to close the file explorer and then open it, but rather for the code to identify that there is already an open file explorer window with path from cell A3 and neither close it nor open a new one, but rather just select the new file corresponding to the new cell being clicked on in already opened file explorer window with path from cell A3. Can anybody think of a way to do that?
I found an solution (not my own) that implements a WMI query against a 'Win32_Process' Class. The code here closes any explorer.exe instances. While I don't fully understand it, I did test and found it works.
Sub CloseWindow()
Dim objWMIcimv2 As Object, objProcess As Object, objList As Object
Dim intError As Integer
Set objWMIcimv2 = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set objList = objWMIcimv2.ExecQuery("select * from win32_process where name='explorer.exe'")
For Each objProcess In objList
intError = objProcess.Terminate
If intError <> 0 Then Exit For
Next
Set objWMIcimv2 = Nothing
Set objList = Nothing
Set objProcess = Nothing
End Sub
This will do the job for you. If the folder is not open it will open it, otherwise it will activate it and will bring it to the front.
In case you want to select a file in the folder, you should modify this a bit and use oWinOpen.Quit to close the window and then re-open it. Shell's behavior when opening a folder simply is different from when selecting a file in the folder too.
Sub OpenFolder(strPath As String)
Dim bFolderIsOpen As Boolean
Dim oShell As Object
Dim oWinOpen As Object
Dim Wnd As Object
Set oShell = CreateObject("Shell.Application")
bFolderIsOpen = FALSE
For Each Wnd In oShell.Windows
If Wnd.Document.Folder.Self.Path = strPath Then
Set oWinOpen = Wnd
bFolderIsOpen = TRUE
End If
Next Wnd
If bFolderIsOpen = FALSE Then 'open it for the first time
Call Shell("explorer.exe" & " " & """" & strPath & """", vbNormalFocus)
Else
oWinOpen.Visible = FALSE
oWinOpen.Visible = TRUE
End If
I have seen a lot of suggestions for this problem, and I have tried them all, but none seem to work. The VBA code is in a non-Microsoft product (SAP Business Objects, which might be the problem). I create an Excel object:
Set oExcel = CreateObject("Excel.Application")
Load the contents from column 1 of one of the WorkSheets in a particular workbook, then close Excel. Each time, it leaves a process in memory, taking up 5+ mb of memory.
I tried making the oExcel object visible, so that at least I could kill it without resorting to the Task Manager, but when I call Quit, the UI quits, and still leaves the process.
Every time I run the code, it creates a new process. So I tried to reuse any existing Excel processes by calling
Set m_oExcel = GetObject(, "Excel.Application")
and only creating it if that call returns nothing,
That did not proliferate the processes, but the single process grew by 5+ mb each time, so essentially the same problem.
In each case, I close the workbook I opened and set DisplayAlerts to False before quitting:
m_oBook.Close SaveChanges:=False
m_oExcel.DisplayAlerts = False
m_oExcel.Quit
This bit of code has been in use for at least five years, but this problem did not crop up until we moved to Windows 7.
Here is the full code in case it helps. Note all the Excel objects are module level variables ("m_" prefix) per one suggestion, and I have used the "one-dot" rule per another suggestion. I also tried using generic objects (i.e. late bound) but that did not resolve the problem either:
Private Function GetVariablesFromXLS(ByVal sFile As String) As Boolean
On Error GoTo SubError
If Dir(sFile) = "" Then
MsgBox "File '" & sFile & "' does not exist. " & _
"The Agent and Account lists have not been updated."
Else
Set m_oExcel = CreateObject("Excel.Application")
Set m_oBooks = m_oExcel.Workbooks
Set m_oBook = m_oBooks.Open(sFile)
ThisDocument.Variables("Agent(s)").Value = DelimitedList("Agents")
ThisDocument.Variables("Account(s)").Value = DelimitedList("Accounts")
End If
GetVariablesFromXLS = True
SubExit:
On Error GoTo ResumeNext
m_oBook.Close SaveChanges:=False
Set m_oBook = Nothing
Set m_oBooks = Nothing
m_oExcel.DisplayAlerts = False
m_oExcel.Quit
Set m_oExcel = Nothing
Exit Function
SubError:
MsgBox Err.Description
GetVariablesFromXLS = False
Resume SubExit
ResumeNext:
MsgBox Err.Description
GetVariablesFromXLS = False
Resume Next
End Function
Most times this happens because Excel is keeping a COM Add-in open. Try using the link below for help on removing the COM Add-in.
Add or remove add-ins
I find particular comfort in the note:
Note This removes the add-in from memory but keeps its name in the list of available add-ins. It does not delete the add-in from your computer.
Adding an answer based on David Zemens comment. Works for me.
m_oExcel.Quit '<- Still in Task Manager after this line
Set m_oExcel = Nothing '<- Gone after this line
This question has already been answered by Acantud in response to a subsequent post:
https://stackoverflow.com/questions/25147242
Fully qualify your references to objects within the Excel workbook you open to avoid creating orphaned processes in the task manager. In this case, the solution is to prefix DelimitedList with m_oBook, such as
ThisDocument.Variables("Agent(s)").Value = m_oBook.DelimitedList("Agents")
Though this isn't supposed to happen, you could send excel a "WindowClose" message in order to force close.
You'll be needing these API functions
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function TerminateProcess Lib "kernel32" (ByVal hProcess As Long, ByVal uExitCode As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
And it should look something like this:
// First, get the handle
hWindow = FindWindow(vbNullString, "Excel")
//Get proccess ID
GetWindowThreadProcessId(hWindow, ProcessValueID)
//Kill the process
ProcessValue = OpenProcess(PROCESS_ALL_ACCESS, CLng(0), ProcessValueID)
TerminateProcess(ProcessValue, CLng(0))
CloseHandle ProcessValueID
Need to use only:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Excel.Application.Quit
End Sub