How to wait for shell command to fully open application? - excel

Currently, my VBA code is able to verify if a file exists or not. If it exists, it opens the file and replaces all instances of "Test" with "Hello". If it does not exist, it sends a message stating so. My problem is that upon opening the file (in this instance a Word document), it may take several seconds and the code returns a "run-time error". Is there a way I can wait for the application to fully open until proceeding? I have tried the "Application.Wait" method, but it is not so reliable, since the file may take more than a second. Additionally, I have seen other forums suggest using WScript.Shell instead, but is there a way I can use the type in my code?
If strFileExists = "" Then
MsgBox "The selected file doesn't exist"
Else
Set objShell = CreateObject("shell.application")
objShell.ShellExecute strFileName, "", "", "open", 3
Application.Wait (Now + TimeValue("00:00:01"))
Set myRange = ActiveDocument.Content
myRange.Find.Execute FindText:="Test", ReplaceWith:="Hello", _
Replace:=wdReplaceAll
Set objShell = Nothing
End If

You can use Windows API FindWindowExA or FindWindowA functions to solve your problem (I suppose you're using Windows). BTW, I've implemented API solution for the same problem at my work (cannot provide the code, as I am not at my work desktop and on Linux, ATM).
Find what the window title is (just open the file and type the title you see in the title bar). Usually, it consists of a file name and the name of the application (e.g., my_file.docx - Word or my_file - Word). Then, put FindWindowExA or FindWindowA function into While or Until loop. Pass the window title to the function. Once, the window with the specified title is found, exit the loop.

Related

How to save Word document using Excel VBA on Mac OS?

I am trying to run an Excel VBA subroutine to save a Word document.
Language version: Word/Excel version 16.41
Operating system: Mac OS Mojave 10.14.6
I tried: (https://www.reddit.com/r/vba/comments/ivwxlw/trouble_with_path_when_saving_basic_word_document/)
(https://answers.microsoft.com/en-us/msoffice/forum/all/excel-vba-macro-to-save-as-basic-word-document/df78bf58-ec21-4502-abfe-bc3df1fca7ae)
I am starting from scratch on a different computer and operating system. I am now using Mac OS Majave 10.14.6 and Word/Excel version 16.41. I was able to open tools-references-and select the Word library, but now References is unhighlighted and unclickable. (it was briefly before also)
I encounter:
Run time error -2146959355 (80080005) Automation Error.
The line that needs debugging:
Set wdApp = New Word.Application
Option Explicit
Sub CreateBasicWordReportEarlyBinding()
Dim wdApp As Word.Application
Set wdApp = New Word.Application
With wdApp
.Visible = True
.Activate
.Documents.Add
With .Selection
.ParagraphFormat.Alignment = wdAlignParagraphCenter
.BoldRun
.Font.Size = 18
.TypeText "Best Movies Ever"
.BoldRun
.Font.Size = 12
.TypeText vbNewLine
.ParagraphFormat.Alignment = wdAlignParagraphLeft
.TypeParagraph
End With
Range("A2", Range("A2").End(xlDown).End(xlToRight)).Copy
.Selection.Paste
.ActiveDocument.SaveAs2 Environ("UserProfile") & "\Desktop\MovieReport.docx"
.ActiveDocument.Close
.Quit
End With
Set wdApp = Nothing
End Sub
There have been problems using COM Automation (which is what New Word.Application", CreateObject etc are doing) on the Mac versions of Office for some years now. You may have seen similar questions elsewhere.
The trouble is that not everyone seems to experience these problems, which suggests that they could result from a configuration issue. The usual suspect would be "something to do with Mac OS Sandboxing". However, I have never seen a support document by anyone, including Microsoft, that tells you how to fix that. I have done clean installs of Office on clean installs of Mac OS and encountered and reported the problems, and that's when I think the software author should really investigate the problem and provide fix or a workaround.
Here, (same Office version, but Mac OS Catalina 10.15.6) what I find is that...
The problems are a bit different depending on which application you are running or trying to automate.
In Excel, trying to use an early-bound object of type Word.Application always fails. So you cannot use
Dim wdApp As New Word.Document
or
Dim myApp As Word.Document
Set myApp = New Word.Document
(It doesn't matter whether or not you have defined the correct reference in VBE Tools->References. If you don't, you'll see see a compile-time error anyway and VBA won't actually run the code).
So you need
Dim wdApp As Object
and to use CreateObject (if Word is not already running or when you you need a new instance of Word, on Windows at least) or GetObject (when you want to connect to a/the existing instance of Word.
However, here I find that CreateObject only works sometimes, and I haven't been able to work out why. It always starts Word if it hasn't started, but sometimes it waits for Word to start and returns a reference to the Word object and sometimes it does not. Tests are not completely conclusive, but it actually looks as if it works a maximum of "every other time you call it in an Excel session", It looks to me as if Excel retains some state information that it should not and thinks it "knows" that WOrd has started when in fact it hasn't.
In contrast, GetObject seems to work OK. Normally it returns an error if Word has not started, but returns a reference to the Word object if it has. So I tried to use something like this
Dim wdApp As Object
On Error Resume Next
Set wdApp = CreateObject("Word.Application")
Err.Clear
If wdApp Is Nothing Then
Set wdApp = GetObject(,"Word.Application")
End If
But then I still sometimes get error 429 ActiveX component can't create object in the GetObject line - in those cases it looks as if CreateObject isn't waiting for Word to start.
So I looked at the possibility of Starting Word without using COM. There are a few ways you could try to do that on Mac, but the simplest is to use the MacScript function to run a bit of AppleScript to do it. It's simplest because all the code can be in the VBA Sub/Function - you don't need any external files.
MacScript is actually deprecated because of problems with Mac OS sandboxing. You are really supposed to use AppleScriptTask instead. But MacScript currently seems to do the job, except that it always raises a VBA error (which IMO it should not), so we have to mess around with VBA error trapping.
Here, the following code always works. For now.
Dim theApp As Object
On Error Resume Next
MacScript "tell application id ""com.microsoft.Word"" to activate"
Err.Clear
'On Error Goto problem ' you need to set this up
Set theApp = GetObject(,"Word.Application")
' just be careful
If theApp Is Nothing Then
Debug.Print "theApp Is Nothing"
Else
Debug.Print TypeName(theApp)
' get on with what you need to do
End If
The other thing I tried quite hard to do was see if I could then take advantage of early binding (for Intellisense etc.) by adding this code at the appropriate points:
Dim myApp As Word.Application
Set myApp = theApp
'or
Set myApp = theApp.Application
But that never worked. So I seem to be stuck with late binding.
if you find that you cannot use MacScript, you can use AppleScriptTask. At its simplest you put a text file called myStartWordScript.scpt in a folder in the user's "Library", here
~/Library/Application Scripts/com.microsoft.Excel
I used a script like this:
on myStartWord(dummy as text)
tell application id "com.microsoft.Word"
activate
end tell
return "Word has started"
end myStartWord
Then you can ditch some of that error heandling stuff and use VBA code like this:
Dim theApp As Object
Debug.Print AppleScriptTask("myStartWordScript","myStartWord","")
'On Error Goto problem ' you need to set this up
Set theApp = GetObject(,"Word.Application")
' just be careful
If theApp Is Nothing Then
Debug.Print "theApp Is Nothing"
Else
Debug.Print TypeName(theApp)
' get on with what you need to do
End If
See freeflow's comment, which should be posted as answer. In VBE, pick Tools > References > Microsoft Word 16.0 Object Library.
There's 2 ways you can automate word from Excel:
Early Binding: Add a reference to the microsoft word object library.
Late Binding: declare your variables as objects.
In your case I would use early binding to have access to intellisense.enter image description here
To find the proper syntax for saving to a Mac OS X folder :
open a file
record a macro
save the file (choose your folder)
stop the recording
edit the macro
see how Word translated it in VisualBasic
example with Word 16 (2019):
ActiveDocument.SaveAs2 FileName:= _
"/Users/myusername/anyfolder/nameofthefile" & DocNum & ".txt" _
, FileFormat:=wdFormatText, LockComments:=False, Password:="", _
AddToRecentFiles:=True, WritePassword:="", ReadOnlyRecommended:=False, _
EmbedTrueTypeFonts:=False, SaveNativePictureFormat:=False, SaveFormsData _
:=False, SaveAsAOCELetter:=False, Encoding:=65001, InsertLineBreaks:= _
False, AllowSubstitutions:=False, LineEnding:=wdCROnly

My vba code closes a document before the print command can be executed

The code I am trying to run is in excel vba and its supposed to open a word document print it and then close the document. For some reason it seems that the code doesn't finish sending the document to the printer and yet it still closes. So the code runs to completion and doesn't generate an error message but nothing manages to print.
When I run the code step by step the document does manage to print. I tried adding: Application.Wait(Now + TimeValue("0:00:05")) to give it time to work. I tried a another form of that line in case the program was telling word to wait instead of excel: Excel.Application.Wait(Now + TimeValue("0:00:05")). I've also tried playing around with the time making it wait 10 second instead of 5.
Any help would be great
If ENG28 = "" Then
Else
Set objWord = CreateObject("Word.Application")
Set objDoc = objWord.Documents.Open(ENG28)
objWord.Visible = True
objDoc.PrintOut
Application.Wait(Now + TimeValue("0:00:05"))
objWord.Quit 0
End If
I see that you made use of the .Wait method however there is a better way as you can never guarantee that your document is in a printed state within the 5-10 seconds threshold.
The PrintOut method basically has an argument you may add named Background. The default value of this argument is True which would mean that printing occurs in the background and the code continues to run which is causing your file to close before completing the print function. In this case, if you set the Background argument value to False, the macro will not take any more instructions until the printing is done.
try changing the PrinOut line to the following, see if that helps:
objDoc.PrintOut Background:=False

How can I have an Excel VBA application in use without multiuser lock?

I have an app I coded in Excel that suits the needs of my project; it serves the purpose of keeping track of quite a lengthy process and prerequisites and such.
It feeds off of a certain number of tables in my file.
The thing is, only one user can currently work on that file; and since we have multiple teams working on different parts in parallel, it would be nice to host that somehow in a way that would remove the single-user restriction.
Do any of you have an idea of how I could work around this?
I worked on a solution for a very similar project of keeping track of a hospital's labor utilization (nursing employee census, if you will) on a day-to-day basis across every nursing-based department in the hospital system. This solution relies on a couple conditions:
That it will be unlikely two or more people will need to save data to the final file at the same time (meaning within seconds of each other).
All the various users of the file will have access to at least one commonly-shared network drive or location.
In our case, we created a new file each day, but it wouldn't be difficult to adjust the data-writing code to append data, rather than create a new file and dump data into that new file.
The rough outline of the process is this:
Create a read-only destination file (.xlsx in our case) in a network location that contains tables of data split between n worksheets.
Create an interactive form (.xlsm) that allows user input and then on form submission, opens the destination .xlsx file and saves the form data to it, then closes it. This interactive .xlsm file can be placed in the same network location, with shortcuts created on as many peoples' desktops (or departmental shares, for example) as necessary.
With the speed of Excel and VBA, this means you're only "opening" the destination file for a second or two to write the form data, no matter how long one user may have a copy of the form open.
One thing that will be necessary is to check if the file is open, and gracefully alert the user if they need to try again, which you can do with a function covering the related error codes, for example:
Function IsFileOpen(FileName As String)
Dim iFilenum As Long
Dim iError As Long
On Error Resume Next
iFilenum = FreeFile()
Open FileName For Input Lock Read As #iFilenum
Close iFilenum
iError = Err
On Error GoTo 0
Select Case iError
Case 0: IsFileOpen = False
Case 70: IsFileOpen = True
Case 53: IsFileOpen = "Not Found"
Case Else: Error iError
End Select
End Function
which can be called via some code like (pseudo code):
Private Sub UpdateData(ByVal thesheet As String)
Dim xlApp As New Excel.Application
Dim xlWkbk As New Excel.Workbook
If Not IsFileOpen(FileName) Then
Set xlWkbk = xlApp.Workbooks.Open(filename)
xlWkbk.Worksheets(theSheet).Activate
Else
MsgBox "Sorry, the file is currently in use. Please try again", vbOKOnly
Exit Sub
End If
End Sub
Or you could have it simply wait a few seconds (e.g. Wait 5) or more if the writing process doesn't cover that much data. The specific amount of seconds to wait would depend on testing write times based on your scenario and your data. That would be added as a nested If Not statement inside the previous one.
Then, when the result is that the file is not in use, simply write a series of subroutines to write the form data (stored as variables) to the destination sheet. End with something like
xlWkbk.Save
xlWkbk.Close
Set xlWkbk = Nothing
Set xlApp = Nothing
to save and close the workbook and clear your variables (memory cleanup and all that).
You may already be aware of this practice, but while you'll want to keep Excel visible during development, you'll definitely want to set Application.Visible = False on the production files for two reasons:
This will prevent users from getting confused by a lot of automation
It covers Application.Updating as well, which will really speed up data processing.

How to release an Aspen object and clear memory

This is my first time using such forum.
I have exactly the same question as here:
How to release an object and clear memory in VBA
In this thread, the question was unfortunately not solved...
With Excel VBA I connect to another program (namely Aspen EDR). For that purpose I have an according Add-In installed. To access Aspen EDR I need to add an object. After I'm done I want to release the object to save some memory. First thing I tried is this:
Dim ObjEDR As BJACApp
Dim Path As String
Path = 'assume this is the correct path to the file i want to open
Set ObjEDR = New BJACApp ' Create the BJAC object
If Not ObjEDR.FileOpen(Path) Then
MsgBox "Can't open file!"
End If
'...
Set ObjEDR = Nothing
After I set the object nothing, Excel does not release the memory (as I can see in my task manager). Of course after a few hundred iterations (I have to open a lot of these files) I get an error message, that Excel is out of memory. I read a few threads and apparently nothing only deletes some kind of reference to the object but not the object itself, so I tried adding fileclose
'...
ObjEDR.FileClose
Set ObjEDR = Nothing
When executing the FileClose I can see that a little memory is released (0.5 of 3MB) but still there is a lot of memory accumulating.
Also when not using the "Now" it is not working and I get "runtime error'424': Object required" when executing Set ObjEDR = BJACApp
I also read about "pointers" that might cause the staying memory increase, but how can I find and clear/delete them?
Does anyone has an idea?
I would really appreciate it!
If .Quit (or the object's equivalent) and setting the object to Nothing is not working for you, then you could try relying on VBA's garbage collector to do the job.
Essentially what this means is that you would need to split the sub in two, have the main sub, and within that sub call the sub that will open and close your object. Hopefully, upon the second sub exiting, VBA will clean up those objects.
Sub Main()
Dim filePath As String
For Each [..] In [..] ' Or use a Do...Loop
filePath = 'assume this is the correct path to the file i want to open
openObj filePath 'call the sub below
Next [..]
End Sub
Sub openObj(ByVal Path As String)
Dim ObjEDR As BJACApp
Set ObjEDR = New BJACApp ' Create the BJAC object
If Not ObjEDR.FileOpen(Path) Then
MsgBox "Can't open file!"
End If
[...] 'your code to perform the needed actions with your obj
ObjEDR.FileClose
Set ObjEDR = Nothing
End Sub
I don't know anything about this object, but you should also try .Quit and .Close
Another method is to not create a new object for each path. Place the Set ObjEDR on the outside of your loop, and utilize the same object every time you open the new file.
Ok, to those who are interested:
The support of Aspen Tech told me that
ObjEDR.dispose()
should work, but just for versions above V8.4.
So this did not solved my problem and I built a workaround using MATLAB which opens and closes Excel after each run. So I loose time opening and closing the Excel file, but the memory of excel is not increasing until it stops working.

Side-Step Application.MsgBox in VBA (Excel)

In order to head off a storm of "comment it out" replies, here is my situation:
I have a process is normally run 1 iteration by 1 iteration. A user manually hits a button that calls a macro which, upon completion, pops up a message box that reports the total time length the macro ran for. It's pretty handy for diagnosing issues. This code is locked down and I cannot modify it.
I am trying to do this at scale. Because the code in the main spreadsheet and workbook are locked, I have a separate workbook open in the same instance of excel with a macro that operates the locked down workbook. Rather than 1 by 1, I've got a set of 300 I'm trying to run through. Right now I have to babysit the thing and hit space to get past the MsgBox. Does anyone know of any tricks to prevent me having to monitor the thing? Either disabling the pop-ups or some way to make them non-modal. Maybe a trick to make the mouse click?
You're right in knowing that the best way to fix the issue is to correct the code. In which case you would probably make the pop-ups toggle-able.
However, I wrote this for you which could be used as a potential work around. It utilizes VBScript to "sort-of" simulate multithreading so that you can send a key to the modal Msgbox. Assuming you can do what you want to do via code, simply call SendDelayedKeys before the action that will cause a Msgbox. You may have to tinker with the Delay based upon your circumstances as 100 milliseconds may not be enough. To change the Delay, just call like this: SendDelayedKeys 500 for 500 milliseconds.
Sub SendDelayedKeys(Optional Delay As Long = 100, Optional keys As String = """ """)
Dim oFSO As Object
Dim oFile As Object
Dim sFile As String
sFile = "C:\SendKeys.vbs" 'Make this a valid path to which you can write.
'Check for the .vbs file.
If Not Len(Dir$(sFile)) Then
'Create the vbs file.
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFile = oFSO.CreateTextFile(sFile)
oFile.WriteLine "Set WshShell = WScript.CreateObject(""WScript.Shell"")"
oFile.WriteLine "WScript.Sleep CLng(WScript.Arguments(0))"
oFile.WriteLine "WshShell.SendKeys WScript.Arguments(1)"
oFile.Close
End If
Shell "wscript C:\SendKeys.vbs " & Delay & " " & keys
End Sub
Sub ProofOfConcept()
'Using default parameters which sends a space after 100 milliseconds
SendDelayedKeys
MsgBox "I disappear on my own!"
End Sub
A word of warning: Any solution that utilizes SendKeys is a fragile solution and should be avoided when possible. However, when your options are limited and you need to avoid a manual process, sometimes it's your only option.
Since SiddhartRout rightly pointed out that this could be solved using API calls: here's a link with C# code that would close your msgbox every second.
The problem here really isn't strictly a problem more code can (or indeed should) solve.
There are a great many things to consider and any solution will be more complex AND less reliable than the problem it is initially trying to solve. But lets look at your options...
SendKeys is not reliable for that kind of use, what happens if the dialogue says "would you like me to save this workbook?" just after making a change that was meant to be temporary or "would you like to play global thermonuclear war?" Plus with a batch process like that you want to get on with something else while you wait, even if it's only to come here to downvote trolls. If nothing else you may not be in control of this code so what kind of mess will it cause when the maintainers realise msgbox is bad UX and kill it?
FindWindow API calls would let you check the content in the window to make sure it says what you're expecting but then you're potentially asking some bit of quick & dirty vbscript to go into a race condition until the right window comes up. Can you guarantee that the threads won't lock up?. What about platform issues - what happens if someone wants to run your code on their shiny new surface? What happens when your 64 bit modal Excel dialogue window can't be seen by the 32-bit api calls you were making? What about a new version of office that doesn't present modal dialogues in the same way? None of those problems are insurmountable but each adds complexity (aka opportunity for failure.)
The best solution is fix the actual problem you have identified from the outset which is that the original code throws up an unnecessary modal dialogue. Someone needs to fix that - it doesn't have to be you but if you estimate how much time that modal dialogue wastes in lost productivity that should get you a solid business case for getting it sorted.

Resources