How to automatically click on open file button running VBA procedure - vba

I am looking for a way to click on a button 'open file'.
I wrote a excel-VBA macro that connects with SAP module. What it should do is to add a file as an attachment in SAP. As a result I get pop-up window, just a standard windows open file window to get the file name and open file button.
My question is how to automatically insert a file name (always known) and click on that open file button?
I tried sendkeys from within excel-VBA, but it turns out that the once the open file window pops up the whole Sub routine stops.
I know I need Winapi to do this.

You probably need to do a DoEvents to allow the keys to be processed by SAP.
Quoting from VBA help:
DoEvents Function
Yields execution so that the operating system can process other
events.
Syntax
DoEvents( )
Remarks
The DoEvents function returns an Integer representing the number of
open forms in stand-alone versions of Visual Basic, such as Visual
Basic, Professional Edition. DoEvents returns zero in all other
applications.
DoEvents passes control to the operating system. Control is returned
after the operating system has finished processing the events in its
queue and all keys in the SendKeys queue have been sent.
DoEvents is most useful for simple things like allowing a user to
cancel a process after it has started, for example a search for a
file. For long-running processes, yielding the processor is better
accomplished by using a Timer or delegating the task to an ActiveX EXE
component.. In the latter case, the task can continue completely
independent of your application, and the operating system takes case
of multitasking and time slicing.
Caution Any time you temporarily yield the processor within an event
procedure, make sure the procedure is not executed again from a
different part of your code before the first call returns; this could
cause unpredictable results. In addition, do not use DoEvents if other
applications could possibly interact with your procedure in unforeseen
ways during the time you have yielded control.

Related

Excel Interop in .Net - Catch VBA errors?

Similar to Errors when calling certain Excel VBA macros from C#, I want to implement an error handler that will catch VBA errors and return gracefully.
Basically, I'm opening up an arbitrary macro workbook in Excel, and if the macro fails (i.e. you get a VBA error dialog with "end" and "debug" buttons), I want my application to handle it internally instead of using the VBA's dialogs. This is primarily so that a buggy book won't freeze processing (I'm using a queue of sorts to process multiple workbooks).
I already know about the DisplayAlerts=False property, and On Error Resume Next inserted into each routine, but I don't want to need to inject this into everything (especially since some of these workbooks may already modify the mentioned settings and reset them mid-routine!)
My best idea so far is to write up some sort of process watcher that looks for windows titled "Microsoft Visual Basic" and sendkey an "E" (to click the end button).
I've read in a few places that there's a property called Application.BreakOnVBAError that can be set to False, but it doesn't seem to exist in the interop assembly.
With the help of pinvoke.net and MWinAPI, I've been able to enumerate all of the windows created under Excel's process ID, which I can then drill down into to discover the elements of the dialog (if it's a dialog such as the Microsoft Visual Basic dialog).
Since it's an automated instance, the main window is hidden so the only thing that should show up are message boxes and the VBA error box, which I can then send keyboard input messages to to End the script when necessary.
Of course, this method never hits the "Debug" button so I should never see the VBE windows, but just in case I handle that too.
Source code available upon request?

SendKeys to click on a dialog box button in Access

I am using Access 2013. I searched a variety of online resources. I thought SendKeys was the answer.
Once per quarter, my client will receive an updated Access database. All table names should be identical each quarter. I want them to run a macro, specify the location of the new file, and then the macro updates the linked tables and executes all other queries I’ve built (I have the last part working).
The part I have not be able to get working is to check the “Always prompt for a new location box”, check the “select all” box and click OK (and then click OK and close after the client specifies the new file location). Below is the code I am using.
Function Open_LinkedTableManager()
DoCmd.RunCommand acCmdLinkedTableManager 'this step works fine
'the following lines, up until Application.Run don’t appear to be
'doing anything. The code will run, but I have to manually execute
'each of the steps I am trying to automate before it gets to the
'Application.Run step
SendKeys "%a", True ' also tried SendKeys "%(a)" and "+a", "a", etc,
'True; Alt+a checks the "Always prompt for a new location box”
SendKeys "%s", True ' also tried SendKeys "%(s)", True; Alt+s checks the "select all" 'box
SendKeys "{Enter}" ' then user specifies location of new file
SendKeys "{Enter}" ' click OK after receiving message "All selected linked tables 'were successfully refreshed"
' click Close to close linked table manager and proceed to the next step below (not 'sure how to do this)
Application.Run ("Update_all_queries") ' this is working;
End Sub
If sending to yourself then try DoEvents after each sendkey.
DoEvents Function
Yields execution so that the operating system can process other events.
Syntax
DoEvents( )
Remarks
The DoEvents function returns an Integer representing the number of open forms in stand-alone versions of Visual Basic, such as Visual Basic, Professional Edition. DoEvents returns zero in all other applications.
DoEvents passes control to the operating system. Control is returned after the operating system has finished processing the events in its queue and all keys in the SendKeys queue have been sent.
DoEvents is most useful for simple things like allowing a user to cancel a process after it has started, for example a search for a file. For long-running processes, yielding the processor is better accomplished by using a Timer or delegating the task to an ActiveX EXE component.. In the latter case, the task can continue completely independent of your application, and the operating system takes case of multitasking and time slicing.
Caution Any time you temporarily yield the processor within an event procedure, make sure the procedure is not executed again from a different part of your code before the first call returns; this could cause unpredictable results. In addition, do not use DoEvents if other applications could possibly interact with your procedure in unforeseen ways during the time you have yielded control.
I have solved your dilemma. All I needed to do was place the sendkey statements before the call to to the linked tabled manager. See Below - Worked Great For Me! I was also able to add all of the commands in your order and they worked great. Good luck, hope this helped. Let me know. Adam
PS: If you have many tables to change the path on, this will be painful for the user for every table you are forcing them to have to set the path for.
SendKeys "%s", 1
DoCmd.RunCommand acCmdLinkedTableManager

VB.NET exe that talks to out-of-process COM on activate event bombs when external script AppActivates it

I wrote a VB.NET Windows Forms app that requests a string from an out-of-process COM object every time the activate event fires. My form has two tabs, so I need to programmatically flip to the correct tab every time my window gains focus. Works fine, until...
By chance, someone ran a vbscript (yes, script, not exe) that contains:
Set shell = CreateObject("WScript.Shell")
shell.AppActivate("Window Title That Matches My App")
This script consistently crashes my app. Usually so badly that the Exception dialog usually can't paint itself. I have to kill it from task manager. Sometimes the Exception is readable. (I also confirmed the exception by attaching to the running exe with Visual Studio). It's: "System.Runtime.InteropServices.COMException (0x8001010D): An outgoing call cannot be made since the application is dispatching an input-synchronous call."
What's really messing with my mind is that my app has multiple instance detection using a mutex, and if an existing instance is running, my own code (compiled) uses VB.NET's own AppActivate keyword, and this does NOT crash my app. It activates the running instance and exits the redundant instance as expected.
The problem seems solely to be triggered by cscript/wscript's AppActivate. I wrote a 3-liner .vbs to confirm this. It's repeatable.
Is there a way to trap or avoid this in my compiled app?
It's not clear to me WHY this approach actually fixes the problem, but it DOES work:
Add a timer to the form.
Move all the _Activated code to the timer's _Tick event.
Make the _Activated event start the timer.
Make the _Tick event stop the timer then perform the COM stuff.

Script in IE to Insert Text at Cursor (in or out of IE)

I need to take text already obtained and stored in a variable and place it in another window: IE or any other windows application.
Context:
An asynchronous application is running in IE, most likely in the background, and when an event fires in the application, certain text needs to be inserted wherever the cursor/carrot is.
I am not restricted to any particular technology so if it even needs to be an ActiveX component I am open to anything.
This is something due for a project by the end of the week so ANY suggestions/ideas are VERY welcome.
Thank you in advance.
You'll need to write an ActiveX control that calls the SendInput function.
However, this sounds like a dumb idea. What are you trying to do?

Running VBA before any forms open

So I have an Access database with a front and a back end. I will be distributing it to users soon, but I have no control over where exactly they will put the files on their computers. However, I think I can count on them putting front and back ends in the same folder.
As such, when the front end opens, I want it to check that the linked tables are correctly connected to the back-end database. I have working code for this; however I don't know where to put it. When the front end opens, a menu form is automatically opened (configured through the start-up dialogue box). I have put the code in the OnOpen event, which I thought occurred before any data is loaded, but when I test this out, I get a message telling me that the back-end cannot be found (it's looking in its old location).
Basically, is there an event I can use that runs before any forms have opened?
Create a Macro and name it "autoexec".
For the macro action, select "RunCode" and then set the function name to the name of the function you use to check your linked tables.
As Matt said, create a macro, call it "autoexec", and select "RunCode" as the macro action. The Function Name argument should be the name of the function you wish to run (and not a sub), and if the function has no arguments, you should still put () at the end, or it won't work.
I generally prefer to create a small form that runs a number of checks, such as finding the back-end and so forth, and set various options. The code for the open event of this form might be:
Me.Visible = False
'Determines if the database window is displayed
SetProp "StartupShowDBWindow", False, dbBoolean
'Hide hidden and system objects
SetOption "Show Hidden Objects", False
SetOption "Show System Objects", False
'Find back end
CheckLinkPath
I keep a table of tables to be linked in the front-end and if any are missing, this form can be used to report the error, or any other error for that matter.
Set RS = CurrentDb.OpenRecordset("Select TableName From sysTables " _
& "WHERE TableType = 'LINK'")
RS.MoveFirst
strConnect = db.TableDefs(RS!TableName).Connect
If Not FileExists(Mid(strConnect, InStr(strConnect, "DATABASE=") + 9)) Then
'All is not well
blnConnectError = True
Else
Do Until RS.EOF()
If db.TableDefs(RS!TableName).Connect <> strConnect Then
blnConnectError = True
Exit Do
End If
RS.MoveNext
Loop
End If
If everything is ok, the small form calls the main menu or form and the user never sees the checking form. I would also use this form to open a password prompt, if required.
Before putting your front end and back end in the same folder, think about it. Isn't it worth having 2 folders? What about multiple users on the same computer accessing the same back-end database? What about multiple users accessing the same databse through a network? What is the necessity of having a front end-back end typology if your app is basically a single-user app?
Why don't you add a dialog box to your app, in case your connectivity is lost? You could create a fileDialog object in your code, allowing the user to browse for a *mdb file anywhere on his computer/network. It is then possible to control that the selected mdb file contains all requested tables and open the corresponding links (I guess you are using the transferDatabase command).
And what about additional tools/references you'll need for your app to run when you'll distribute it to your final users? By default, MS Access records the 3 basic ones:
Visual Basic For Application
Microsoft Access Library
Microsoft DAO Library
If your app needs anything else, such as ADO or Office objects (ADODB.recordset or Office commandbars for example), you will have to add the references manually for each installation, as the final user won't be able to open the VBA window and access the tools/references menu.
So, if you need to deploy your app on multiple computers, I strongly advise you to use a deployment tool such as this free one. You'll need a few hours to be able to use it properly, but the result is worth it. You'll be able to give your clients a real installer module. It will create folders, add requested shortcuts, and manage references in the computer's registry. This will make your deployment definitely painless!
EDIT: the autoexec macro is definitely the right solution for calling code before any event.
EDIT: don't forget that your final users can make profit of the runtime version of Access, which is free!
As others have suggested, I'd use the AutoExec macro in this case. If your code that checks the linked tables is currently a sub, change it to a function that returns TRUE if it succeeds. You can then use the "conditions" column in the AutoExec macro to exit the application with a user-friendly error dialog if the link table code fails. Your AutoExec macro could be something like:
Call your LinkTables() function, set condition to terminate startup if it fails.
Call your "Main" startup menu form.
You could send them a *.BAT file that copies the databases to c:\temp (or whatever folder you choose. Setup the linked to this folder before creating the BAT file. Zip it up and email it to them. then you won't have to worry with the extra needed code.