I'm struggling with a VBA Sub that is called by a button. This Sub opens an Configuration.xls Excel spreadsheet from a hard coded file path. A MsgBox tell me about the current workspace - the workspace changes from the current file to the just opened one. All is fine here.
I now want to execute this Sub from an outside batch that calls a VBS that calls the VBA Sub. The workspace after opening the Configuration.xls file remains the same and does not change to Configuration.xls. Additionally when calling the Sub by VBS the function gets executed twice - No clue why.
So my question is - why do I have different behaviors between the two calling mechanisms?
I simplified the code below as it shows the same behavior as my more complex real code.
Sub ReadConfiguration()
MsgBox ActiveWorkbook.Name
FileExcel = "D:\_Trash\VBA_VBS\Configuration.xls"
Workbooks.Open Filename:=FileExcel, ReadOnly:=True, IgnoreReadOnlyRecommended:=True
strFileName = FunctionGetFileName(FileExcel)
MsgBox ActiveWorkbook.Name
On Error Resume Next
Set wBook = Workbooks(strFileName)
If Err Then
Exit Sub
End If
ActiveWorkbook.Close savechanges:=False
End Sub
'*****************************************************
Function FunctionGetFileName(FullPath As Variant)
Dim StrFind As String
Do Until Left(StrFind, 1) = "\"
iCount = iCount + 1
StrFind = Right(FullPath, iCount)
If iCount = Len(FullPath) Then Exit Do
Loop
FunctionGetFileName = Right(StrFind, Len(StrFind) - 1)
End Function
'*****************************************************
The VBS looks like this
Dim args, objExcel
Set args = WScript.Arguments
Set objExcel = CreateObject("Excel.Application")
objExcel.Workbooks.Open args(0)
objExcel.Visible = False
objExcel.Run "Module1.ReadConfiguration()"
objExcel.ActiveWorkbook.Close(0)
objExcel.Quit
I just want to let you know about the solution of this issue allthough I cannot explain completely. The solution is to get rid of the "()" behind the macro call. This has the effect that the VBS script is run twice and the Workbook 'scope' is mixed up.
So easy solution but still the question WHY- What do I tell the function additionally when adding the "()"?
Thanks for your help!
TheMadMatt
Related
I have an Excel VBA macro which I need to run when accessing the file from a batch file, but not every time I open it (hence not using the open file event). Is there a way to run the macro from the command line or batch file? I'm not familiar with such a command.
Assume a Windows NT environment.
You can launch Excel, open the workbook and run the macro from a VBScript file.
Copy the code below into Notepad.
Update the 'MyWorkbook.xls' and 'MyMacro' parameters.
Save it with a vbs extension and run it.
Option Explicit
On Error Resume Next
ExcelMacroExample
Sub ExcelMacroExample()
Dim xlApp
Dim xlBook
Set xlApp = CreateObject("Excel.Application")
Set xlBook = xlApp.Workbooks.Open("C:\MyWorkbook.xls", 0, True)
xlApp.Run "MyMacro"
xlApp.Quit
Set xlBook = Nothing
Set xlApp = Nothing
End Sub
The key line that runs the macro is:
xlApp.Run "MyMacro"
The simplest way to do it is to:
1) Start Excel from your batch file to open the workbook containing your macro:
EXCEL.EXE /e "c:\YourWorkbook.xls"
2) Call your macro from the workbook's Workbook_Open event, such as:
Private Sub Workbook_Open()
Call MyMacro1 ' Call your macro
ActiveWorkbook.Save ' Save the current workbook, bypassing the prompt
Application.Quit ' Quit Excel
End Sub
This will now return the control to your batch file to do other processing.
The method shown below allows to run defined Excel macro from batch file, it uses environment variable to pass macro name from batch to Excel.
Put this code to the batch file (use your paths to EXCEL.EXE and to the workbook):
Set MacroName=MyMacro
"C:\Program Files\Microsoft Office\Office15\EXCEL.EXE" "C:\MyWorkbook.xlsm"
Put this code to Excel VBA ThisWorkBook Object:
Private Sub Workbook_Open()
Dim strMacroName As String
strMacroName = CreateObject("WScript.Shell").Environment("process").Item("MacroName")
If strMacroName <> "" Then Run strMacroName
End Sub
And put your code to Excel VBA Module, like as follows:
Sub MyMacro()
MsgBox "MyMacro is running..."
End Sub
Launch the batch file and get the result:
For the case when you don't intend to run any macro just put empty value Set MacroName= to the batch.
you could write a vbscript to create an instance of excel via the createobject() method, then open the workbook and run the macro. You could either call the vbscript directly, or call the vbscript from a batch file.
Here is a resource I just stumbled accross:
http://www.codeguru.com/forum/showthread.php?t=376401
If you're more comfortable working inside Excel/VBA, use the open event and test the environment: either have a signal file, a registry entry or an environment variable that controls what the open event does.
You can create the file/setting outside and test inside (use GetEnviromentVariable for env-vars) and test easily. I've written VBScript but the similarities to VBA cause me more angst than ease..
[more]
As I understand the problem, you want to use a spreadsheet normally most/some of the time yet have it run in batch and do something extra/different. You can open the sheet from the excel.exe command line but you can't control what it does unless it knows where it is. Using an environment variable is relatively simple and makes testing the spreadsheet easy.
To clarify, use the function below to examine the environment. In a module declare:
Private Declare Function GetEnvVar Lib "kernel32" Alias "GetEnvironmentVariableA" _
(ByVal lpName As String, ByVal lpBuffer As String, ByVal nSize As Long) As Long
Function GetEnvironmentVariable(var As String) As String
Dim numChars As Long
GetEnvironmentVariable = String(255, " ")
numChars = GetEnvVar(var, GetEnvironmentVariable, 255)
End Function
In the Workbook open event (as others):
Private Sub Workbook_Open()
If GetEnvironmentVariable("InBatch") = "TRUE" Then
Debug.Print "Batch"
Else
Debug.Print "Normal"
End If
End Sub
Add in active code as applicable. In the batch file, use
set InBatch=TRUE
Instead of directly comparing the strings (VB won't find them equal since GetEnvironmentVariable returns a string of length 255) write this:
Private Sub Workbook_Open()
If InStr(1, GetEnvironmentVariable("InBatch"), "TRUE", vbTextCompare) Then
Debug.Print "Batch"
Call Macro
Else
Debug.Print "Normal"
End If
End Sub
I have always tested the number of open workbooks in Workbook_Open(). If it is 1, then the workbook was opened by the command line (or the user closed all the workbooks, then opened this one).
If Workbooks.Count = 1 Then
' execute the macro or call another procedure - I always do the latter
PublishReport
ThisWorkbook.Save
Application.Quit
End If
# Robert: I have tried to adapt your code with a relative path, and created a batch file to run the VBS.
The VBS starts and closes but doesn't launch the macro... Any idea of where the issue could be?
Option Explicit
On Error Resume Next
ExcelMacroExample
Sub ExcelMacroExample()
Dim xlApp
Dim xlBook
Set xlApp = CreateObject("Excel.Application")
Set objFSO = CreateObject("Scripting.FileSystemObject")
strFilePath = objFSO.GetAbsolutePathName(".")
Set xlBook = xlApp.Workbooks.Open(strFilePath, "Excels\CLIENTES.xlsb") , 0, True)
xlApp.Run "open_form"
Set xlBook = Nothing
Set xlApp = Nothing
End Sub
I removed the "Application.Quit" because my macro is calling a userform taking care of it.
Cheers
EDIT
I have actually worked it out, just in case someone wants to run a userform "alike" a stand alone application:
Issues I was facing:
1 - I did not want to use the Workbook_Open Event as the excel is locked in read only.
2 - The batch command is limited that the fact that (to my knowledge) it cannot call the macro.
I first wrote a macro to launch my userform while hiding the application:
Sub open_form()
Application.Visible = False
frmAddClient.Show vbModeless
End Sub
I then created a vbs to launch this macro (doing it with a relative path has been tricky):
dim fso
dim curDir
dim WinScriptHost
set fso = CreateObject("Scripting.FileSystemObject")
curDir = fso.GetAbsolutePathName(".")
set fso = nothing
Set xlObj = CreateObject("Excel.application")
xlObj.Workbooks.Open curDir & "\Excels\CLIENTES.xlsb"
xlObj.Run "open_form"
And I finally did a batch file to execute the VBS...
#echo off
pushd %~dp0
cscript Add_Client.vbs
Note that I have also included the "Set back to visible" in my Userform_QueryClose:
Private Sub cmdClose_Click()
Unload Me
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
ThisWorkbook.Close SaveChanges:=True
Application.Visible = True
Application.Quit
End Sub
Anyway, thanks for your help, and I hope this will help if someone needs it
I'm partial to C#. I ran the following using linqpad. But it could just as easily be compiled with csc and ran through the called from the command line.
Don't forget to add excel packages to namespace.
void Main()
{
var oExcelApp = (Microsoft.Office.Interop.Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
try{
var WB = oExcelApp.ActiveWorkbook;
var WS = (Worksheet)WB.ActiveSheet;
((string)((Range)WS.Cells[1,1]).Value).Dump("Cell Value"); //cel A1 val
oExcelApp.Run("test_macro_name").Dump("macro");
}
finally{
if(oExcelApp != null)
System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcelApp);
oExcelApp = null;
}
}
I generally store my macros in xlam add-ins separately from my workbooks so I wanted to open a workbook and then run a macro stored separately.
Since this required a VBS Script, I wanted to make it "portable" so I could use it by passing arguments. Here is the final script, which takes 3 arguments.
Full Path to Workbook
Macro Name
[OPTIONAL] Path to separate workbook with Macro
I tested it like so:
"C:\Temp\runmacro.vbs" "C:\Temp\Book1.xlam" "Hello"
"C:\Temp\runmacro.vbs" "C:\Temp\Book1.xlsx" "Hello" "%AppData%\Microsoft\Excel\XLSTART\Book1.xlam"
runmacro.vbs:
Set args = Wscript.Arguments
ws = WScript.Arguments.Item(0)
macro = WScript.Arguments.Item(1)
If wscript.arguments.count > 2 Then
macrowb= WScript.Arguments.Item(2)
End If
LaunchMacro
Sub LaunchMacro()
Dim xl
Dim xlBook
Set xl = CreateObject("Excel.application")
Set xlBook = xl.Workbooks.Open(ws, 0, True)
If wscript.arguments.count > 2 Then
Set macrowb= xl.Workbooks.Open(macrowb, 0, True)
End If
'xl.Application.Visible = True ' Show Excel Window
xl.Application.run macro
'xl.DisplayAlerts = False ' suppress prompts and alert messages while a macro is running
'xlBook.saved = True ' suppresses the Save Changes prompt when you close a workbook
'xl.activewindow.close
xl.Quit
End Sub
You can check if Excel is already open. There is no need to create another isntance
If CheckAppOpen("excel.application") Then
'MsgBox "App Loaded"
Set xlApp = GetObject(, "excel.Application")
Else
' MsgBox "App Not Loaded"
Set wrdApp = CreateObject(,"excel.Application")
End If
I have the following vba code. It creates new Excel application and uses it to open a file. Then it MsgBoxes some cell's value in this file.
Sub TestInvis()
Dim ExcelApp As Object
Set ExcelApp = CreateObject("Excel.Application")
Dim WB As Workbook
Set WB = ExcelApp.Application.Workbooks.Open("Y:\vba\test_reserves\test_data\0503317-3_FO_001-2582480.XLS")
Dim title As String
title = WB.Worksheets(1).Cells(5, 4).Value
MsgBox (title)
WB.Save
WB.Close
ExcelApp.Quit
Set ExcelApp = Nothing
End Sub
The problem is that after MsgBoxing it slows down and eventually gives a Microsoft Excel is waiting for another application to complete an OLE action window. Why does it do this? It's not like there are any hard commands being implemented. And how should I deal with it?
This happens because the Excel instance in ExcelApp is waiting for User Input, most likely.
You can try to add ExcelApp.DisplayAlerts = False to skip any pop-ups that might be there.
Also, while troubleshooting add the line ExcelApp.Visible = True so you can see what's going on in the second instance and troubleshoot there.
I encountered this problem in the following situations:
An alert was opened by the Application Instance and it was awaiting user input.
While opening a file, it was coming up with some message about a crash when the file was previously opened and whether I wanted to open the saved version or the in memory version (although this should happen before the msgBox)
If you run the code multiple times and it crashes, it might have the file open as read only since there's another hidden instance of Excel that locked it (check your task manager for other Excel processes)
Rest assured that in any case the problem is not with your code itself - It runs fine here.
Code that works for me.
You can select the file from FileDialog. In comments You have code that close the workbook without saving changes. Hope it helps.
Option Explicit
Sub Import(Control As IRibbonControl)
Dim fPath As Variant
Dim WB As Workbook
Dim CW As Workbook
On Error GoTo ErrorHandl
Set CW = Application.ActiveWorkbook
fPath = Application.GetOpenFilename(FileFilter:="Excel file, *.xl; *.xlsx; *.xlsm; *.xlsb; *.xlam; *.xltx; *.xls; *.xlt ", Title:="Choose file You want to openn")
If fPath = False Then Exit Sub
Application.ScreenUpdating = False
Set WB = Workbooks.Open(FileName:=fPath, UpdateLinks:=0, IgnoreReadOnlyRecommended:=True)
Set WB = ActiveWorkbook
MsgBox("File was opened.")
'Application.DisplayAlerts = False
'WB.Close SaveChanges:=False
'Application.DisplayAlerts = True
'MsgBox ("File was closed")
Exit Sub
ErrorHandl:
MsgBox ("Error occured. It is probable that the file that You want to open is already opened.")
Exit Sub
End Sub
None of these methods worked for me. I was calling a DLL for MATLAB from VBA and a long simulation would pop up that Excel was waiting on another application OLE action, requiring me to click it off for the routine to continue, sometimes quite a few times. Finally this code worked (saved in a new module): https://techisours.com/microsoft-excel-is-waiting-for-another-application-to-complete-an-ole-action/
The way I used it is a little tricky, as the directions don't tell you (here and elsewhere) which causes various VBA errors, so I add to the description for what works in Excel 365:
Create a new module called "ToggleOLEWarning" (or in any new module, important!) which only contains the following code:
Private Declare Function CoRegisterMessageFilter Lib "ole32" (ByVal IFilterIn As Long, ByRef PreviousFilter) As Long
Public Sub KillOLEWaitMsg()
Dim IMsgFilter As Long
CoRegisterMessageFilter 0&, IMsgFilter
End Sub
Public Sub RestoreOLEwaitMsg()
Dim IMsgFilter As Long
CoRegisterMessageFilter IMsgFilter, IMsgFilter
End Sub
Then in your main function, just decorate the long running OLE action with a couple lines:
Call KillOLEWaitMsg
'call your OLE function here'
Call RestoreOLEwaitMsg
And it finally worked. Hope I can save someone the hour or two it took for me to get it working on my project.
Instead of having all the macro's stored in each workbook, we would like to have them stored in one global one. We tried using Personal.xlsb file, however every time excel crashes or system administrator forced restart with excel open it created personal.v.01 ....v.100 files, and they interfered with each other, got corrupted etc.. So instead we are trying to add a small macro to each excel workbook we make which then should open a global excel workbook with all the macros, however it does not open it(normal.xlsb), where is the problem? If I manually run it it works fine, it just does not autorun..
Option Explicit
Public Sub Workbook_Open()
Dim sFullName As String
Dim xlApp As Excel.Application
Dim wbReturn As Workbook
sFullName = "Z:\Dokumentstyring\normal.xlsb"
Set xlApp = GetObject(, "Excel.Application") 'need to do so to open it in same instance otherwise the global macros can not be called.
Set wbReturn = xlApp.Workbooks.Open(filename:=sFullName, ReadOnly:=True)
If wbReturn Is Nothing Then
MsgBox "Failed to open workbook, maybe z drive is down?"
Else
ThisWorkbook.Activate'Dont know how to pass object to modules, so instead activate it and in createbutton set wb= activeworkbook..
Application.Run ("normal.xlsb!CreateButtons")
End If
End Sub
Public Sub Workbook_BeforeClose(Cancel As Boolean)
Dim wb As Workbook
For Each wb In Application.Workbooks
If InStr(UCase(wb.Name), "PARTSLIST") > 0 And wb.Name <> ThisWorkbook.Name Then Exit Sub
Next wb
On Error Resume Next
Workbooks("normal.xlsb").Close
Workbooks("filter.xlsx").Close
End Sub
You create your addin, as just an empty workbook, holding nothing but the code
Like this
Then you add a reference to it, in the workbook that you wish to use, in VBA, like this. My Documents, is a folder on a network drive, not "my documents" local.
And then you can call like so.
So based on input from #Nathan_Sav and #Ralph I have come to a partly good solution:
I have called my addinn Normal and saved this on Z:Dokumenstyring\Normal.xlam
I then removed all the code in Thisworkbook of Normal:
Private Sub Workbook_Open()
Dim ExcelArgs As String
Dim arg As String
ExcelArgs = Environ("ExcelArgs")
'Call deleteMacros.deletePersonalFiles
'MsgBox ExcelArgs
If InStr(UCase(ExcelArgs), "CREO,") > 0 Then
Application.DisplayAlerts = False
If Len(ExcelArgs) > Len("CREO,") Then
arg = Split(ExcelArgs, ",")(1)
Call Creo.addNewPartToPartslist(arg)
End If
Application.DisplayAlerts = True
End If
If InStr(UCase(ExcelArgs), "DOKLIST,") > 0 Then
Application.DisplayAlerts = False
If Len(ExcelArgs) > Len("DOKLIST,") Then
arg = Split(ExcelArgs, ",")(1)
Call ProsjektListen.dbDumpDocOut(arg)
End If
Application.DisplayAlerts = True
End If
and put this in a new workbook called Z:Dokumenstyring\Creo.xlsm
I have so edited all my bat files(which previously were using personal.xlsb):
echo "Launch excel"
Set ExcelArgs=CREO,ADDPART
"C:\Program Files (x86)\Microsoft Office\OFFICE16\Excel.exe" /x /r "z:\Dokumentstyring\Creo.xlsm"
So when I run the bat file it adds a parameter to enviroment, start creo.xlsm, which then starts the addin which launch my userform.
So if I now want to update the look of that that userform I do this by modifying the Z:Dokumenstyring\NormalDebug.xlam, then i save a copy which i write over Z:Dokumenstyring\Normal.xlam and I also told every user to not copy the addin to the default location in excel but keep it in Z:Dokumenstyring\Normal.xlam.
My shapes in my excel sheets seems to work with just the macro name in the procedure, however there might be an conflict if two procedures have the same name, but located in different procedures. So I also altered this to
ws1.Shapes(tempName).OnAction = "Normal.xlam!Custom_Button_Click"
However every click starts a new instance of the addin, how to avoid this?
I've made a macro to open two workbooks and do some stuff with them. This macro runs from a third workbook that calls any other two user selected workbooks for which, before they're opened, I don't know their name.
So! I know Excel 2010 doesn't have a built in function to check if a workbook is open so, I've been trying to compare the workbook against Nothing but it doesn't work and every workaround I find in different sites tend to use the name of the workbook.
Is there another way of doing this?
The idea is to run a macro with the two user defined workbooks and then, maybe, re-running it in the same workbooks but Excel warms me of discarding changes.
Maybe a workaround could be to tell excel when it prompts for reopening, not to reopen and handle that error to just use the same workbooks, for which at least, I know how part or the names will be. For example, one will have the text "cluster" in it, and the other the word "translation" so, maybe in a loop like the next one, I could find and use the workbook I need but just If I already checked if it's open. Or, does this way works to see if it's opened already?
For each wbk in Application.Workbooks
If wbk.Name Like "*cluster*" Then
WorkingWorkbook = wbk.Name
End If
next
My code is as follows:
Sub structure()
Application.ScreenUpdating = False
Dim translationWorkbook As Worksheet
Dim clusterWorkbook As Workbook
If Not clusterWorkbook Is Nothing Then
Set clusterWorkbook = Application.Workbooks.Open(ThisWorkbook.Sheets(1).Range("E5").Value2)
Else
Set clusterWorkbook = Application.Workbooks(parseFilePath(ThisWorkbook.Sheets(1).Range("E5")))
End If
Set translationWorkbook = Application.Workbooks.Open(ThisWorkbook.Sheets(1).Range("E6").Value2).Worksheets("String_IDs_Cluster") 'Translation table target for completing
End Sub
The parameter passed to Workbooks.Open is the one written in the sheet by my next function:
Private Sub MS_Select_Click()
Dim File As Variant
Dim Filt As String
Filt = "Excel 97-2003 File(*.xls), *.xls," & "Excel File(*.xlsx),*.xlsx," & "Excel Macro File (*.xlsm),*.xlsm"
File = Application.GetOpenFilename(FileFilter:=Filt, FilterIndex:=2, Title:="Select Menu Structure File")
If File = False Or File = "" Then
MsgBox "No File Selected"
Exit Sub
End If
ThisWorkbook.ActiveSheet.Range("E5").Value2 = File
End Sub
Same for translationWorkbook but in a different cell and also, I was trying to create a function to parse and use the filename in a full path(Then I discovered the command Dir lol) but when I pass the filename, without the xls extension to Application.Workbooks(file) it sends me a "subscript range error". Why could that be?
Basically my questions are:
How can I check for an open workbook and use it? Either by handling the
error for excel's prompt or by not trying to reopen the same file.
Why does trying to open a workbook with Application.Workbooks() with the return of my function fails? And here my question splits in two... First: with my function, wouldn't it work if I give a string as an argument? Or maybe, before passing it as an argument, I need to assign the result of my function to a variable?
Second: If I try to open a workbook like this Application.Workbooks("clusterworkbook") it sends me another "subscript error" but, before I used the File Dialog prompt, I made it this way and worked fine.
Any help will be appreciated.
EDIT
Function ParseFilePath added:
Function parseFilePath(fullpath As Range) As String
Dim found As Boolean
Dim contStart As Integer
Dim contEnd As Integer
contEnd = InStr(fullpath, ".") - 1
contStart = contEnd
found = False
Do While found = False
If fullpath.Characters(contStart, 1).Text = "\" Then
found = True
Else
contStart = contStart - 1
End If
Loop
parseFilePath = fullpath.Characters(contStart + 1, (contEnd - contStart)).Text
End Function
How can I check for an open workbook and use it? Either by handling the error for excel's prompt or by not trying to reopen the same file.
Have done some small modifications to your procedure structure. Similar to what you were trying testing for the workbook variable to be nothing, only that you have to first attempt to set the variable, the way you were doing it will always return empty as you did not try to set it before. I have also tested for the translation workbook, as it mightt be open as well.
I'm assuming the values in E5 and E6 contain the FullName of the workbook (i.e. path + filename) and that parseFilePath is a function to extract the filename from the FullName.
Sub structure()
Application.ScreenUpdating = False
Dim clusterWorkbook As Workbook
Dim translationWorkbook As Workbook
Dim translationWorksheet As Worksheet
With ThisWorkbook.Sheets(1)
On Error Resume Next
Set clusterWorkbook = Application.Workbooks(parseFilePath(.Range("E5").Value2))
On Error GoTo 0
If clusterWorkbook Is Nothing Then Set clusterWorkbook = Application.Workbooks.Open(.Range("E5").Value2)
'Set Translation table target for completing
On Error Resume Next
Set translationWorkbook = Application.Workbooks(parseFilePath(.Range("E6").Value2))
On Error GoTo 0
If translationWorkbook Is Nothing Then
Set translationWorksheet = Application.Workbooks.Open(.Range("E6").Value2).Sheets("String_IDs_Cluster")
Else
Set translationWorksheet = translationWorkbook.Sheets("String_IDs_Cluster")
End If
End With
End Sub
Why does trying to open a workbook with Application.Workbooks() with
the return of my function fails? And here my question splits in two...
First: with my function, wouldn't it work if I give a string as an
argument? Or maybe, before passing it as an argument, I need to assign
the result of my function to a variable?
Not sure why it did not work, change the prodedure as indicated.
I tested the procedure above using this function to extract the Filename from the Fullname and it worked:
Function parseFilePath(sFullName As String) As String
parseFilePath = Right(sFullName, Len(sFullName) - InStrRev(sFullName, "\"))
End Function
Second: If I try to open a workbook like this Application.Workbooks("clusterworkbook") it sends me another
"subscript error" but, before I used the File Dialog prompt, I made it
this way and worked fine.
Bear in mind that you did not used that line alone, it most probably has something like:
set Workbook = Application.Workbooks("clusterworkbook")
So the command was to set a variable, not to open the workbook, as such the only situation in which this works is that the workbook is already open so the variable gets set. The times when it failed was when the workbook was not open and you tried to set the variable, given you an error.
Suggest to visit these pages
Excel Objects, On Error Statement
I have been using the below code to identify if the excel workbook is open. If yes, then i activate it and do some stuff. If not, i open it and do some stuff.
sub test()
Dim Ret
Ret = IsWorkBookOpen("Your excel workbook full path")
If Ret = False Then
Workbooks.Open FileName:="Your excel workbook full path", UpdateLinks:=False
Else
Workbooks("Workbook name").Activate
End If
end sub
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
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.