Detect if an object has been disconnected from its clients - vba

I am having an issue with automating an Excel file. The VBA script within Excel first opens a Word application and Word document:
Dim wordApp As Object
Set wordApp = CreateObject("Word.Application")
vPath = Application.ActiveWorkbook.Path
Set wordDoc = wordApp.Documents.Open(vPath & "\test.doc")
And then I call a subroutine within the Word document passing some data from the Excel file:
Call wordApp.Run("StartWithData", variable1, variable2)
If Excel detects that an error occurs in that subroutine, I close the Word document and Word application from Excel in a label I call Err1:
On Error Goto Err1
'all the code from above
Exit Sub
Err1:
wordDoc.Close wdCloseWithoutSaving
wordApp.Quit SaveChanges:=wdDoNotSaveChanges
Set wordDoc = Nothing
Set wordApp = Nothing
This works perfectly fine under normal circumstances; however, if the Word document or application are closed before the Err1 label executes (such as the user manually closing the document), I get the following error:
Run-time error '-2147417848 (80010108)':
Automation error The object invoked has disconnected from its clients.
which makes perfect sense because the wordApp and/or wordDoc variables still reference the Application and Document objects and those objects do not exist anymore (yet are also not considered to be Nothing).
So here is my inquiry: Is there a way to check if an object has been disconnected from its client before the run-time error occurs so as to avoid having to rely on on error resume next?
Such as:
If Not isDisconnected(wordDoc) Then
wordDoc.Close wdCloseWithoutSaving
End If
If Not isDisconnected(wordApp) Then
wordApp.Quit SaveChanges:=wdDoNotSaveChanges
End If
Update 1:
After looking at omegastripes' answer, I realized that the error given above only occurs when the document (wordDoc) was the object that got disconnected. If the Word application (wordApp) is what got disconnected, I get the following error:
Run-time error '462':
The remote server machine does not exist or is unavailable

Consider the below example:
Sub Test()
Dim wordApp As Object
Dim wordWnd As Object
Dim wordDoc As Object
Set wordApp = CreateObject("Word.Application")
Set wordWnd = wordApp.Windows ' choose any object property as indicator
wordApp.Visible = True ' debug
Set wordDoc = wordApp.Documents.Open(Application.ActiveWorkbook.Path & "\test.doc")
MsgBox IsObjectDisconnected(wordWnd) ' False with opened document
wordDoc.Close
MsgBox IsObjectDisconnected(wordWnd) ' False with closed document
wordApp.Quit ' disconnection
MsgBox IsObjectDisconnected(wordWnd) ' True with quited application
End Sub
Function IsObjectDisconnected(objSample As Object) As Boolean
On Error Resume Next
Do
IsObjectDisconnected = TypeName(objSample) = "Object"
If Err = 0 Then Exit Function
DoEvents
Err.Clear
Loop
End Function
Seems any type detection of the variable, which references to the intrinsic Word objects, like .Documents, .Windows, .RecentFiles, etc., made immediately after document close or application quit commands have been invoked, may throw the error 14: Out of string space, while Word application processing the command. The same detection on the Applicationobject , may also hang Excel application.
In the example TypeName() call is wrapped into OERN loop, that should skip irrelevant results to get explicit disconnection feedback, relying on the type name, but not on the error number. To avoid hanging, .Windows property is being checked instead of Application.

Related

Assign a Word variable through Outlook

I have a database management tool that runs on multiple Office apps.
I use Outlook to receive variables from a userform which is then sent to a Word template file, creating a new Word document.
When Outlook calls Word and creates a document, I need to assign a value to a Boolean variable stored in a Word module. Either true or false, depending on the user input.
The Boolean is used to decide which lines of code run on a Word userform the user can later open, but not in the document itself (i.e. inserted into one of the fields in the document).
So, if I correctly understood your question, your Word application has a variable "stored in a Word module". Let us say that this variable will be:
Public boolTest As Boolean
If you will have a Sub in that specific module (in 'Normal.dotm' or in a docm document), let us say:
Sub testBooleanChange(boolT As Boolean)
boolTest = boolT
MsgBox boolTest
End Sub
If Outlook will call the above Sub as:
objWord.Run "testBooleanChange", True
Then your boolTest variable will take the sent Boolean Value
In fact, a real code dealing with the above suggestion will look like that:
Sub testCallWordProc()
Dim W As Word.Application
On Error Resume Next
Set W = GetObject(, "Word.Application")
If Err.Number <> 0 Then
Err.Clear: On Error GoTo 0
Set W = CreateObject("Word.Application")
End If
W.Visible = True
'If the Sub in discussion exists in a document, un-comment the next line and use your real document full name:
'W.Documents.Open ("Your doc keeping the sub.docm")
'If the Sub is inside the 'Normal' you can simply use:
W.RUN "testBooleanChange", True
End Sub

Object error when calling application

I'm trying to open an excel document when a user clicks a button. There are multiple buttons that open the same document but I want it to change the worksheet if the document is already opened and not another instance of the document
Public objExcel As Object
Sub Main()
Set objExcel = CreateObject("Excel.Application")
End Sub
Public Sub QE1_Click()
Call Main
If objExcel Is Nothing Then
objExcel.Visible = True
objExcel.Workbooks.Open "H:\My Documents\Flowchart to Word\Quality and Environmental management system flowchart.xlsm"
objExcel.Worksheets("Project enquiry").Activate
Else
objExcel.Worksheets("Project enquiry").Activate
End If
End Sub
Public Sub QE2_Click()
Call Main
If objExcel Is Nothing Then
objExcel.Visible = True
objExcel.Workbooks.Open "H:\My Documents\Flowchart to Word\Quality and Environmental management system flowchart.xlsm"
objExcel.Worksheets("Order and project release").Activate
Else
objExcel.Worksheets("Order and project release").Activate
End If
End Sub
Running the code gives me the error: Application-defined or object-defined error
Can anyone point out what's causing the error?
This code here:
Set objExcel = CreateObject("Excel.Application")
Is creating a new Excel Application. Then this one here:
objExcel.Worksheets("Project enquiry").Activate
already assumes that the new application is having a worksheet called Project enquiry, which cannot be true. Thus, you are getting the 1004 error. Refine your business logic and it should work.
In general, try to delete this condition If objExcel Is Nothing Then because objExcel will never be Nothing, you are calling Main which assigns object to it. Then the code may work.
A little upgardes to get your code to work more efficiently:
Option Explicit
Public objExcel As Object
Sub Main()
' don't need to open another instance of Excel, can use the same instance
On Error Resume Next
Set objExcel = GetObject(, "Excel.Application") ' check if there is an open instance of Excel running
On Error GoTo 0
If objExcel Is Nothing Then
Set objExcel = CreateObject("Excel.Application")
End If
End Sub
'==================================================================
Public Sub QE1_Click()
Dim wb As Workbook
Dim sht As Worksheet
If objExcel Is Nothing Then
Main ' call sub that initializes an Excel application object
End If
objExcel.Visible = True
Set wb = objExcel.Workbooks.Open("H:\My Documents\Flowchart to Word\Quality and Environmental management system flowchart.xlsm")
On Error Resume Next
Set sht = wb.Worksheets("Project enquiry")
On Error GoTo 0
If sht Is Nothing Then ' sheet doesn't exist >> raise an error
MsgBox "Workbook doesn't have a sheet named 'Project enquiry'", vbCritical, "Sheet critical error"
Else ' sheet object created successfully
sht.Activate ' <-- NOT SURE why you need to use Activate ?
End If
End Sub
Note: same modifications should be applied to Sub QE2_Click().
If you want to control an Office application from within a different one - Excel from within Word, for example, you first need to decide whether you want to write your code using Intellisense and what's called "early-binding" or whether you want to use "late-binding", which does not have Intellisense but has the advantage that you don't need to rely on a link to the other (Excel) VBA code library.
In order to use early-binding you must go to Tools/References in the VBA editor and activate the checkbox next to the entry for the other application (Excel). Only then can you use something like Dim wb As Workbook.
If you don't want to use early-binding, then you must declare things as Dim wb as Object, same as you've done for the Excel.Application.
In order have code decide whether it needs to use an running instance of the other application (Excel) or start a new instance, use the method GetObject. This can be used to pick up any running instance, or to check for a specific file.
Set ojbExcel = GetObject(,"Excel.Application")
vs
Set objExcel = GetObject("H:\My Documents\Flowchart to Word\Quality and Environmental management system flowchart.xlsm")
If what you're looking for with GetObject isn't currently available, you'll get an error which you can check and subsequently use CreateObject in order to start the application.
Option Explicit
Public objExcel As Object
Sub Main()
''' Try to re-use an existing instance
' If that instance does not exist, an error will be generated
' So temporarily turn off error messages
On Error Resume Next
' check if there is an open instance of Excel running
Set objExcel = GetObject(, "Excel.Application")
' Turn error messages back on
On Error GoTo 0
If objExcel Is Nothing Then
Set objExcel = CreateObject("Excel.Application")
End If
End Sub
Note that you should usually not turn off error-messaging - you need a really good reason to do so and you should turn it on again as soon as possible.
If all your buttons are essentially the same - in the two procedures you show everything is the same except the Else step - then you can cut down on the duplicate code. (That will also make maintenance simpler if you don't have to make changes in all the procedures).
Also, since the Else action is the same as the last action in the If you can simply put that after End If.
Private Sub ActivateWorksheet(wsName as String)
If objExcel Is Nothing Then
objExcel.Visible = True
objExcel.Workbooks.Open "H:\My Documents\Flowchart to Word\Quality and Environmental management system flowchart.xlsm"
End If
objExcel.Worksheets(wsName).Activate
End Sub
Public Sub QE1_Click()
Call Main
ActivateWorksheet "Project enquiry"
End Sub
Public Sub QE2_Click()
Call Main
ActivateWorksheet "Order and project release"
End Sub

Word vba document.readonly status incorrectly returns false

I have an excel project that checks word documents for a changed modify date and, if changed, it opens that document and imports the text from the word form fields into excel.
The routine in excel that opens and imports the word documents is as follows:
Sub CopyFromWord(pFile as String,aFile as string)
Dim wdApp As Object
On Error Resume Next
Set wdApp = GetObject(, "Word.Application")
If Err.Number <> 0 Then
Set wdApp = CreateObject("Word.Application")
Else 'word is already running
End If
On Error Goto 0
Set wdDoc = wdApp.Documents.Open(Filename:="" & pFile & "", ReadOnly:=True)
wdApp.Visible = False
For Each c In wdDoc.bookmarks
'removed code that copies values from word doc fields to excel sheet
Next c
wdApp.Activedocument.Close SaveChanges:=False
End Sub
The word documents all started out as copies of the same file. There are a few thousand copies of the file, but each located in their own folder with a unique name.
The user finds the folder they need and opens the word document within it. It bring up a userform and then populates formfields in the document with the input to the userform. A command button then saves and exits the form.
Because the welcome message/userform loads automatically upon the document opening, I added the following code into the open event for the document:
Sub Document_Open()
If ThisDocument.ReadOnly = True then Exit Sub
msgbox "Welcome " & Environ$("Username") & ". Click OK to begin."
Userform1.show
End sub
This ensures when the excel project loops through all the files, if it finds one has changed, it needs to open the file (read only) so it can import the data without being interrupted with a userform / welcome message, close it, and carry on searching looping all files checking for changed modify-dates.
It should run constantly, however, about 20% of the time, a document will be opened read only by the excel code, but the welcome messagebox in the word document will show, indicating thisdocument.readonly incorrectly returned false.
If I debug the word document in this scenario, and do
? thisdocument.readonly
I get a "false" result. However, even the title bar of the word document ends with " (Read-Only)" so it has clearly been opened read-only, thus readonly should return True.
It is not specific to any documents, if I try to repeat opening them it seems to work the next time round (in that it correctly registers a read-only and exits the sub before the messagebox code). I cant find any kind of pattern and can't find any info online, I've been searching this for weeks!
May not be considered the answer, but following Tim William's suggestion, I managed to put together this which completely solves my problem. I struggled at first because I was trying to set the property too early. Complete code is as follows:
Sub CopyFromWord(pFile as String)
Dim wdApp As Object
On Error Resume Next
Set wdApp = GetObject(, "Word.Application")
If Err.Number <> 0 Then
Set wdApp = CreateObject("Word.Application")
Else 'word is already running
End If
On Error Goto 0
'save current setting
secAutomation = wrdApp.Application.AutomationSecurity
'set Word to disable macros when a document is opened via vb:
wrdApp.Application.AutomationSecurity = msoAutomationSecurityForceDisable
'(without using wrdApp prefix it would only apply to the code's App i.e. Excel)
Set wdDoc = wdApp.Documents.Open(Filename:="" & pFile & "", ReadOnly:=True)
wdApp.Visible = False
For Each c In wdDoc.bookmarks
'removed code that copies values from word doc fields to excel sheet
Next c
'restore original setting before closing
wrdApp.Application.AutomationSecurity = secAutomation
wdApp.Activedocument.Close SaveChanges:=False
End Sub
Many thanks to Tim Williams for the link, and the guy who provided the code within the content of that link. This was such a help and is most appreciated.

VBA Import MS Access to MS Word

I have a VBA module in MS-Access that is supposed to load data from a database into Form Fields in a MS-Word document. I thought it was working fine, but it appears to be inconsistent. Sometimes it works and sometimes it doesn't. I can't figure out what keeps it from working. When I step through the debugger it doesn't throw any errors, but sometimes it doesn't open MS-Word.
Here is the relevant code:
Dim appWord As Word.Application
Dim doc As Word.Document
'Avoid error 429, when Word isn't open.
On Error Resume Next
Err.Clear
'Set appWord object variable to running instance of Word.
Set appWord = GetObject(, "Word.Application")
If Err.Number <> 0 Then
'If Word isn't open, create a new instance of Word.
Set appWord = New Word.Application
End If
Set doc = appWord.Documents.Open("\\srifs01\hresourc\EHS Department\EHS Database\IpadUpload\Lab Inspection Deficiency Resolution Report.docx", , True)
'Sometimes word doesn't open and I think the issue is around here.
With doc
.FormFields("frmID").Result = Me!id
.FormFields("frmSupervisor").Result = Me!LabPOC
.FormFields("frmInspector").Result = Me!InspectorName
.FormFields("frmBuilding").Result = Me!BuildingName
.FormFields("frmRoom").Result = Me!Rooms
.FormFields("frmComments").Result = Me!Comments
.Visible = True
.Activate
.SaveAs "'" & Me!id & "'"
.Close
End With
Set doc = Nothing
Set appWord = Nothing
Any help is appreciated. Thanks in advance.
"When I step through the debugger it doesn't throw any errors, but sometimes it doesn't open MS-Word."
That's because you have On Error Resume Next. That instructs VBA to ignore errors.
Assume you've made this change in your code ...
Dim strDocPath As String
strDocPath = "\\srifs01\hresourc\EHS Department\EHS Database" & _
"\IpadUpload\Lab Inspection Deficiency Resolution Report.docx"
Then, when you attempt to open strDocPath, VBA would throw an error if appWord isn't a reference to a Word application instance ... AND you haven't used On Error Resume Next:
Set doc = appWord.Documents.Open(strDocPath, , True)
You can get rid of On Error Resume Next if you change your assignment for appWord to this:
Set appWord = GiveMeAnApp("Word.Application")
If Word was already running, GiveMeAnApp() would latch onto that application instance. And if Word was not running, GiveMeAnApp() would return a new instance.
Either way, GiveMeAnApp() doesn't require you to use On Error Resume Next in your procedure which calls it. Include a proper error handler there instead. And you can reuse the function for other types of applications: GiveMeAnApp("Excel.Application")
Public Function GiveMeAnApp(ByVal pApp As String) As Object
Dim objApp As Object
Dim strMsg As String
On Error GoTo ErrorHandler
Set objApp = GetObject(, pApp)
ExitHere:
On Error GoTo 0
Set GiveMeAnApp = objApp
Exit Function
ErrorHandler:
Select Case Err.Number
Case 429 ' ActiveX component can't create object
Set objApp = CreateObject(pApp)
Resume Next
Case Else
strMsg = "Error " & Err.Number & " (" & Err.Description _
& ") in procedure GiveMeAnApp"
MsgBox strMsg
GoTo ExitHere
End Select
End Function
You could also include a check to make sure appWord references an application before you attempt to use it. Although I don't see why such a check should be necessary in your case, you can try something like this ...
If TypeName(appWord) <> "Application" Then
' notify user here, and bail out '
Else
' appWord.Visible = True '
' do stuff with Word '
End If
I don't use the New keyword when opening or finding an application.
This is the code I use for excel:
On Error Resume Next
Set xlApp = GetObject(, "Excel.Application")
If Err.Number = 429 Then 'Excel not running
Set xlApp = CreateObject("Excel.Application")
End If
On Error GoTo 0
(note also the On Error GoTo 0 - I don't want the resume next to be active all through the code)
The GiveMeAnApp function worked great for me with a similar problem I was experiencing. Except, to avoid Error 462 (cannot connect to server etc) if I closed the Word document after the data merge and attempted another merge of data to Word. (which caused error 462) I did this: Once I call GiveMeAnApp I then called for a New Word document before calling the Word template I wished to transfer data to Word into.
By always having the New Word document present this avoided error 462 in my circumstances. It means I am left with an empty Word doc but this is ok for me and preferable to the only other solution I could come up with which was to quit the db and re open and run the merge to Word aga.
I am grateful for the help set out in this thread. Thanks all.

Excel VBA to Open Multiple Word files in a loop

I apologize in advance for the newbie question -- most of my VBA experience is in Excel, or Word to Excel. In this case, I am going from Excel to Word. I am trying to capture some data off of some Word forms and store it in an Excel file.
Right now, my code works for the first document in the folder, but after that, it hoses up with an automation error "the server threw an exception" (goo!)
Here is my code:
Dim objWordApp As Object
strCurFileName = Dir(strFilePath)
Set objWordApp = CreateObject("word.application")
objWordApp.Visible = True
Do While strCurFileName <> ""
objWordApp.documents.Open strFilePath & strCurFileName
objWordApp.activedocument.Unprotect password:="testcode"
{EXCEL PROCESSING HERE}
strCurFileName = Dir
objWordApp.activedocument.Close 0
Loop
objWordApp.Quit
Set objWordApp = Nothing
I notice that the code works fine if I quit the app and set the object = nothing within the loop. But the way it is now, it bombs-out on the second file in the folder on the "objWordApp.documents.Open strFilePath & strCurFileName" line.
Can I open and close Word documents in a loop without having to create the object over and over? It's really slow when I do it that way.
Thanks for the help -- I like your way much better. Unfortunately, I get the same result. The program dies the second time through the loop on the line that reads:
Set objWordDoc = objWordApp.Documents.Open(objFile.Path)
The error that I get is:
Run-time Error -2147417851 (80010105)
Automation Error
The server threw an exception.
I tried your code on regular word docs (not the ones I'm processing) and it worked fine. The docs I'm running have form fields and macros -- not sure if that makes a difference. I have set the macro security in Word to both "low" and "very high" to make sure the other macros don't interfere.
I just can't figure it out why it works for the first doc and then not the next. I even cloned the first doc but it made no difference.
Still no luck, though. The only thing I can get to work is if I completely wipe the objects and re-create them every time I want to open a file.
Set objFolder = FSO.GetFolder(strFilePath)
For Each objFile In objFolder.Files
Set objWordApp = CreateObject("word.application")
objWordApp.Visible = True
If Right(objFile.Name, 4) = ".doc" Then
Set objWordDoc = objWordApp.documents.Open(Filename:=objFile.Path, ConfirmConversions:=False, _
ReadOnly:=True, AddToRecentFiles:=False, PasswordDocument:="", _
PasswordTemplate:="", Revert:=False, WritePasswordDocument:="", _
WritePasswordTemplate:="", Format:=wdOpenFormatAuto)
[Process DOC]
objWordDoc.Close 0, 1
End If
Set objWordDoc = Nothing
objWordApp.Quit
Set objWordApp = Nothing
Next
I'm not sure why that works and why it won't work the other way. If I have to go this route, I can -- it just seems really slow and inefficient. Is this a bad idea?
I changed the Dir to a FileSystemObject (go to Tools\References and add Microsoft Scripting Runtime) and I was able to successfully open multiple files. If you are having problems, please describe the error you see in the debugger. Also, if you need to recurse into subdirectories, you will need to refactor this.
Private mobjWordApp As Word.Application
Sub Test()
ProcessDirectory "PathName"
End Sub
Property Get WordApp() As Word.Application
If mobjWordApp Is Nothing Then
Set mobjWordApp = CreateObject("Word.Application")
mobjWordApp.Visible = True
End If
Set WordApp = mobjWordApp
End Property
Sub CloseWordApp()
If Not (mobjWordApp Is Nothing) Then
On Error Resume Next
mobjWordApp.Quit
Set mobjWordApp = Nothing
End If
End Sub
Function GetWordDocument(FileName As String) As Word.Document
On Error Resume Next
Set GetWordDocument = WordApp.Documents.Open(FileName)
If Err.Number = &H80010105 Then
CloseWordApp
On Error GoTo 0
Set GetWordDocument = WordApp.Documents.Open(FileName)
End If
End Function
Sub ProcessDirectory(PathName As String)
Dim fso As New FileSystemObject
Dim objFile As File
Dim objFolder As Folder
Dim objWordDoc As Object
On Error Goto Err_Handler
Set objFolder = fso.GetFolder(PathName)
For Each objFile In objFolder.Files
If StrComp(Right(objFile.Name, 4), ".doc", vbTextCompare) = 0 Then
Set objWordDoc = GetWordDocument(objFile.Path)
' objWordDoc.Unprotect Password:="testcode" ' Need to check if it has Password?
ProcessDocument objWordDoc
objWordDoc.Close 0, 1
Set objWordDoc = Nothing
End If
Next
Exit_Handler:
CloseWordApp
Exit Sub
Err_Handler:
MsgBox "Error " & Err.Number & ": " & Err.Description
Resume Exit_Handler
'Resume Next ' or as above
End Sub
Sub ProcessDocument(objWordDoc As Document)
'{EXCEL PROCESSING HERE}'
End Sub
EDIT: I've added some error handling and a little refactoring although there is quite a bit more refactoring that could be done.
There must be something special about the documents you are opening. You might try using different parameters for opening the documents, such as:
Set objWordDoc = objWordApp.Documents.Open( _
FileName:=objFile.Path, ReadOnly:=True)
You may need to add Microsoft Word as a Reference, and if you do that then start using the Word constants (wdDoNotSaveChanges, etc.). Check out the help on Documents.Open and test different parameters.
Also, use the "Set Next Statement" from the Context Menu during debugging and maybe skip the first document and open the second document directly and see if there are issues.
EDIT: I've changed the code to close and reopen Word if you get the automation error you described. You may have to adjust the error numbers, or simply close Word on any error (If Err.Number <> 0 Then ...).
Again, something must be special about your documents (macros, protection, etc.) because this code works on the test cases I have tried. Have you tried manually opening the documents in Word in the same order as the script, updating information similar to your process script, and then closing the documents to see if Word does anything strange?
Closing the Word.Application won't hurt anything, but it will obviously significantly slower.