0

I'm trying to automate an excel file which has VBA in it. This VBA is protected so I can't access it.

Here is what I need the automated script to do.

  1. Open the workbook
  2. Click / dismiss any Msgbox's (Stuck part)
  3. Enter a cell and let the workbook's vba do it's thing

So I have found I can open the book without popups by using:

var app = new Excel.Application();
app.DisplayAlerts = false;
app.Visible = false;
app.EnableEvents = false;
app.Workbooks.Open(@"path...");

But then the VBA within the book is also disabled so I can't do step 3 above.

How can I just disable all Msg box's then re-enable them at the end?

Jack
  • 2,891
  • 11
  • 48
  • 65
  • The msgboxes are produced by the VBA code in the form of calling the actual MsgBox function? – GSerg Dec 16 '20 at 20:23
  • @GSerg Yes they're in the form `MsgBox ("message")` – Jack Dec 16 '20 at 21:11
  • Also in the form: `MsgBox("Click to confirm") = vbOK` etc – Jack Dec 16 '20 at 21:13
  • https://stackoverflow.com/q/46115555/11683? – GSerg Dec 16 '20 at 21:14
  • I can't edit the VBA I need to it via C#. Or if there is a way within C# to edit the VBA to comment out the boxes etc. – Jack Dec 16 '20 at 21:15
  • It tells you that there isn't. – GSerg Dec 16 '20 at 21:16
  • That questions says it's not possible to disable them from within VBA. I proved above it is possible to disable them via C# but that requires removing all macros which I don't want to do. – Jack Dec 16 '20 at 21:26
  • 1
    Calling `EnableEvents = false` is just as possible from VBA as it is from C#. That does not remove macros, it only stops the standard Excel events from firing, and apparently your offending code is inside handlers for these events. Having executed `EnableEvents = false`, you can still call the handler macro manually, and it will run, and the msgbox will show. – GSerg Dec 16 '20 at 21:28
  • Can I ask: are the msgboxes ONLY in an opening of the workbook? That is, I wish to discover if ANY MsgBoxes appear during the routine that fires when you do Step 3 "Enter a cell and let the workbook's vba do it's thing". If the message boxes appear only during the OPENING of the workbook, you could re-enable Events AFTER the last line in your code snippet in your OP like so: "app.EnableEvents = false;" – MacroMarc Dec 16 '20 at 21:39
  • Sorry in my comment it should set it to true, instead of false – MacroMarc Dec 16 '20 at 21:53
  • Another way is to use another thread/task which continually checks for the Excel Msgbox window and closes it (use FindWindow etc from winAPI). You can rejoin/end threads, or end tasks after the macro has completed etc – MacroMarc Dec 16 '20 at 22:16
  • @MacroMarc unfortunately they do also appear at the end of the `let vba do it's thing` which is a pain' – Jack Dec 16 '20 at 22:17
  • @MacroMarc I did not know that was possible! That could do the trick! – Jack Dec 16 '20 at 22:17
  • some SO links. Most are VB, but look up how to transform to C#: https://stackoverflow.com/questions/5712930/c-sharp-how-to-get-handle-over-a-specific-window-using-user32-dll ; https://stackoverflow.com/questions/58635223/vba-how-to-use-findwindowex-when-have-windows-handle ; https://stackoverflow.com/questions/5241984/findwindowex-from-user32-dll-is-returning-a-handle-of-zero-and-error-code-of-127 – MacroMarc Dec 16 '20 at 22:19
  • Fantastic! I believe that has done the trick! I'm going to experiment with it some more tomorrow but if you want you can take the answer from the first link and this: https://stackoverflow.com/questions/9519206/give-a-windows-handle-native-how-to-close-the-windows-using-c and submit it as your answer @MacroMarc ! – Jack Dec 16 '20 at 22:28

1 Answers1

1

The technique that can be used is:

  1. Run the Excel code in a function on another thread. This is because there are many things that Excel can put up to block the execution, such as Msgboxes and other dialogs from Excel, and if you do not control the Excel code-behind, then you should wish to abort that Task on a timeout basis.

  2. In your main thread, just check for the completion of the task, and add a timeout too.

  3. I made the WindowHandler as a separate class with the winAPI functions from user32.dll etc from examples here: Close window via SendMessage AND here: FindWindow Function Codes

    class WindowHandler {
     [DllImport("user32.dll", SetLastError = true)]
     static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    
     // Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
     [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
     static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
    
     [DllImport("user32.dll", CharSet = CharSet.Auto)]
     private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    
     private const UInt32 WM_CLOSE = 0x0010;
    
     public static void CloseWindow(IntPtr hwnd) {
         SendMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
     }
     public static IntPtr FindWindow(string windowName) {
         var hWnd = FindWindowByCaption(IntPtr.Zero, windowName);
         return hWnd;
     }
     public static void CloseMsgBox() {
         CloseWindow(FindWindow("Microsoft Excel"));
     }
    } 
    

So now the code execution looks crudely like:

// The OpenExcel Action would actually be all the Excel code encapsulated into one function to run in a separate thread
Task t = Task.Run(OpenExcel); 
// Be aware that Excel can have many different popups or VBA issues which may cause execution to stall.
TimeSpan timeLimit = new TimeSpan(0, 0, 10);  // 10 secs or acceptable time limit for Excel
DateTime startTime = DateTime.Now;
while (!t.IsCompleted) {
    if (DateTime.Now - startTime > timeLimit)
        break;  //or do other exception routine, if Excel execution is taking an unacceptable amount of time!
    WindowHandler.CloseMsgBox(); //close any Msgboxes
    Thread.Sleep(200);
}
MacroMarc
  • 3,214
  • 2
  • 11
  • 20
  • Works a treat, thanks Marco. The trick of using another thread is great! – Jack Dec 17 '20 at 16:11
  • Happy to help. This is the sort of thing we do as standard when automating Excel 'from afar', as I do in the RPA world, where we do not always have control of the VBA code etc. – MacroMarc Dec 17 '20 at 16:37