Excel Interop disable Msgbox without disabling all VBA - c#

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.
Open the workbook
Click / dismiss any Msgbox's (Stuck part)
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?

The technique that can be used is:
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.
In your main thread, just check for the completion of the task, and add a timeout too.
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);
}

Related

WindowsAccessBridge for Java Automation using C#

I try to automate an java application using WindowsAccessBridge.dll.
I can get the window handle but calling the function isJavaWindow(System.IntPtr hWnd) always return false
Please find my code below:
static void Main()
{
System.Int32 vmID = 0;
System.Int64 _acParent = 0;
string WndName = "GLOBUS EDU";
string ClassName = "SunAwtFrame";
Windows_run();
System.IntPtr hWnd = System.IntPtr.Zero;
hWnd = (System.IntPtr)FindWindow(ClassName, WndName);
bool Found = isJavaWindow(hWnd);
if (!Found) { throw new System.Exception("ERROR: Unable to find window by classname " + ClassName + " and " + WndName + "!"); }
System.Console.WriteLine("Application is finished. Press ENTER to exit...");
System.Console.ReadKey();
}
Interop:
[return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
[System.Runtime.InteropServices.DllImport("WindowsAccessBridge-64.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
private extern static bool getAccessibleContextFromHWNDFct(System.IntPtr hwnd, out System.Int32 vmID, out System.Int32 _acParent);
private static bool getAccesibleContextFromHWND(System.IntPtr hWnd, out System.Int32 vmID, out System.Int64 acParent)
{
System.Int32 ac = -1;
bool retVal = false;
getAccessibleContextFromHWNDFct(hWnd, out vmID, out ac);
acParent = ac;
return retVal;
}
[System.Runtime.InteropServices.DllImport("WindowsAccessBridge-64.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
private extern static bool getAccessibleContextInfo(int vmID, System.IntPtr ac, out AccessibleContextInfo textInfo);
[System.Runtime.InteropServices.DllImport("WindowsAccessBridge-64.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl, ThrowOnUnmappableChar = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private extern static void Windows_run();
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
private static extern System.IntPtr FindWindow(string lpClassName, string lpWindowName);
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
private static extern System.IntPtr FindWindowByCaptionFct(System.IntPtr ZeroOnly, string lpWindowName);
private static System.IntPtr FindWindowByCaption(string WindowTitle) { return FindWindowByCaptionFct(System.IntPtr.Zero, WindowTitle); }
[System.Runtime.InteropServices.DllImport("WindowsAccessBridge-64.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl, ThrowOnUnmappableChar = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private extern static System.Boolean isJavaWindow(System.IntPtr hwnd);
The function FindWindowis working perfect and I'm getting the window handle also Spy++ shows me. The classname is SunAwtFrameas Spy++ says.
My Java applications runs in 64 bit but I tried all the Libraries (-32, -64) and also switched in the VS Configuration Manager from x86 to x64 and back.
The AccessBridge itself is working well - Java-Monkey-64.exe can spy my running java application.
Does anybody has an idea, why this is not working?
Regards,
Jan
I have been fighting with your problem in few days.
i created a program that enumerate window that is java application(of course write on console application), and catch same problem like yours.
then, i rewrite it on WPF application,enumerate all window, then recognize that: besides the normal window, i see a strange window named: "java access bridge", and the problem is clearly:
the Windows_run function need to have an active windows message pump.
another way, you must putting it on the constructor of a WPF application or something same that.
if (result != FALSE) {
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
shutdownAccessBridge();
}
code in Java Monkey application.
After create a hidden window, it performs a PostMessage with a registered message. The JVM side of the access bridge responds to this message, and posts back another message to the window that was created. As such, they communicate by that way.
and more, you only can call JAB functions after the message pump can process messages.
that is reason why the java monkey need to use call back for it's business.
Pass null for class name as in the below code:
IntPtr hWnd = FindWindow(null, "GLOBUS EDU"); //cast to IntPtr is redundant
bool Found = isJavaWindow(hWnd);
Reference is here on Pinvoke documentation, and it works for me!

Starting process and storing handle to window

Would someone mind helping me out with a problem I've been stuck on for a bit? I'm using C# and trying to start a couple processes, and later move those windows to separate monitors.
So far this was the main idea:
Process p1 = Process.Start(#"1.pptx");
Process p2 = Process.Start(#"2.pptx");
SetWindowPos(p1.MainWindowHandle, -1,
0,
0,
100,
100,
SWP_SHOWWINDOW);
SetWindowPos(p2.MainWindowHandle, -1,
200,
200,
100,
100,
SWP_SHOWWINDOW);
But after trying a bunch of different things, I haven't been able to get it to work. Could anyone give me some pointers?
As a side note which is confusing me, if I print those to process IDs (p1, p2), and then run this code:
Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle))
{
Console.WriteLine("Process: {0} ID: {1} Window title: {2}", process.ProcessName, process.Id, process.MainWindowTitle);
}
}
those process IDs don't exist. I know there must be something simple I'm missing...?
UPDATE: The reason for the problem above is that for some reason the MainWindowTitle didn't have a value, so it wasn't printing the pid.
When you use Process.Start to open a document this is handled by the shell. The shell looks in the file association registry and takes whatever steps are needed to open the document.
This may involve creating a new process but equally may not. Office applications will typically reuse already open processes to open new documents. That's what is happening here.
And when this does happen, when no new process is started, the shell returns 0 for the new process handle. That's reflected back to the .net Process object. It explains why you have no main window handle.
So fundamentally your basic approach is flawed. Using Process.Start will not yield window handles for these documents. You'll have to find another way to locate these windows. For instance EnumWindows or a CBT hook. Or perhaps COM automation is the right solution.
As an aside it seems that you did not check for errors when you called SetWindowPos. That would have helped you work this out more quickly. Always check return values when calling Win32 functions.
For those who are still looking for an answer, this is what I did to get it working.
First, use EnumWindows to get a handle to each open window before starting any new process. Then start your process, then check all windows again making sure they're visible and have window text. If you have only 1 new process, chances are that's your new window. If not, I've tried it 3 times before failing. So far, the code has worked great.
Here is the code for helper functions/Win32 API calls.
public delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList lParam);
public static ArrayList GetWindows()
{
ArrayList windowHandles = new ArrayList();
EnumedWindow callBackPtr = GetWindowHandle;
EnumWindows(callBackPtr, windowHandles);
return windowHandles;
}
private static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles)
{
windowHandles.Add(windowHandle);
return true;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
const int SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
public static extern Boolean SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
My main logic then went something like this (adjust as necessary):
List<IntPtr> alreadyOpenWindows = new List<IntPtr>();
foreach (IntPtr ip in GetWindows())
{
alreadyOpenWindows.Add(ip);
}
Process.Start("your command here");
System.Threading.Thread.Sleep(1000);
foreach (IntPtr ip in GetWindows())
{
// To consider it a new window, it must be visible, it must not have been open previously, and it must have window text length > 0
if (IsWindowVisible(ip) && alreadyOpenWindows.Contains(ip) == false)
{
StringBuilder windowText = new StringBuilder();
windowText.Length = 256;
GetWindowText(ip, windowText, windowText.Length);
if (windowText.Length > 0)
{
numNewWindows++;
handle = ip;
// break if your confident there will only be one new window opened
}
}
}
// Check numNewWindows if you'd like
if (handle != IntPtr.Zero)
{
SetWindowPos(handle, -1,
this.GetScreen().WorkingArea.X,
this.GetScreen().WorkingArea.Y,
this.GetScreen().WorkingArea.Width,
this.GetScreen().WorkingArea.Height,
SWP_SHOWWINDOW);
}

How to get rid of "offline mode" message bow in IE 8?

I would like to COMPLETELY get rid of the "Work Offline" message box.
To give some context, this message box appears on a machine running a local webapp.
The access to the network is clearly unstable so a momentary lack should never be blocking : it only delays some background notifications. The web pages only require local resources to be displayed. The urls look like http://localhost:4444/*myApp*/...
The machine runs on XP pro and the browser is IE8.
I have tried the following solutions without success:
Unchecking by hand the menu option File/Work Offline is not enough.
Setting the registry entries HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\WebCheck\LoadSens and LoadLCE to auto, then no then yes
I have tried to programatically force the online mode by calling this method up to every 200ms
[DllImport("wininet.dll")]
private extern static bool InternetSetOption(int hInternet,
int dwOption, ref INTERNET_CONNECTED_INFO lpBuffer, int dwBufferLength);
[StructLayout(LayoutKind.Sequential)]
struct INTERNET_CONNECTED_INFO
{
public int dwConnectedState;
public int dwFlags;
};
private static readonly int INTERNET_STATE_DISCONNECTED = 16;
private static readonly int INTERNET_STATE_CONNECTED = 1;
private static readonly int ISO_FORCE_DISCONNECTED = 1;
private static readonly int INTERNET_OPTION_CONNECTED_STATE = 50;
private static Timer aTimer;
private bool offlineSelected = false;
public void SetIEOfflineMode(bool offline)
{
INTERNET_CONNECTED_INFO ici = new INTERNET_CONNECTED_INFO();
if (offline)
{
ici.dwConnectedState = INTERNET_STATE_DISCONNECTED;
ici.dwFlags = ISO_FORCE_DISCONNECTED;
Debug.WriteLine("switching to offline mode");
}
else
{
ici.dwConnectedState = INTERNET_STATE_CONNECTED;
Debug.WriteLine("switching to online mode");
}
InternetSetOption(0, INTERNET_OPTION_CONNECTED_STATE, ref ici, Marshal.SizeOf(ici));
}
The last attempt almost works. The 'Work Offline' never remains checked but sometimes (quite randomly indeed) the evil message box appears. The problem is that despite it never remains blocking (the working mode switches to online so the pages work properly) it disturbs the end user.
One remark: we cannot chenge the architecture (local web application) even though it may look a bit weird.
Since anyone else couldn't help me, I finally found a solution. A bit dirty but working one.
The trick is simulate a click on the "Try Again" button. I did this by using user32.dll functions. Here are the steps:
You first find the parent window's handle by using the FindWindow function.
You find the button with FindWindowExfrom its caption and its parent window's handle.
You finally send the click with SendMessage
Here is the declaration of the required functions
// For Windows Mobile, replace user32.dll with coredll.dll
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
const uint WM_CLOSE = 0x10;
const uint BM_CLICK = 0x00F5;
And here is the method using them
private bool ClickButton(String window, String button)
{
IntPtr errorPopUp;
IntPtr buttonHandle;
bool found = false;
try
{
errorPopUp = FindWindow(null, window.Trim());
found = errorPopUp.ToInt32() != 0;
if (found)
{
found = false;
buttonHandle = FindWindowEx(errorPopUp, IntPtr.Zero, null, button.Trim());
found = buttonHandle.ToInt32() != 0;
if (found)
{
SendMessage(buttonHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
Trace.WriteLine("Clicked \"" + button + "\" on window named \"" + window + "\"");
}
else
{
Debug.WriteLine("Found Window \"" + window + "\" but not its button \"" + button + "\"");
}
}
}
catch (Exception ex)
{
Trace.TraceError(ex.ToString());
}
return found;
}
window is the title (="Work Offline") of the window and button the caption of the button (="&Try Again").
Note : Do not forget the ampersand ("&") preceding subtitled letters.

C# console application lose focus

I have about 15 different console apps on my local PC and they are running with different time periods as scheduled tasks.
Since I am using this computer as personal usage (such as surfing on YouTube or Watching Movies)
They are jumping on my screen but I have to always minimize them manually.
My goal is, I want them to first appear (which is already doing) and lose automatically focus after a couple of seconds.
Is it possible with console apps on Windows?
If you want to minimize console window, you can use WinApi
const Int32 SW_MINIMIZE = 6;
[DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern IntPtr GetConsoleWindow();
[DllImport("User32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ShowWindow([In] IntPtr hWnd, [In] Int32 nCmdShow);
private static void MinimizeConsoleWindow()
{
IntPtr hWndConsole = GetConsoleWindow();
ShowWindow(hWndConsole, SW_MINIMIZE);
}
Usage:
static void Main(string[] args)
{
Console.WriteLine("Starting foo...");
Thread.Sleep(1000); // hold console for a second on the screen
MinimizeConsoleWindow();
Console.ReadKey();
}

How do I get the classname of the active window?

By using this code I can get the title of the active window..
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
private string GetActiveWindowTitle()
{
const int nChars = 256;
IntPtr handle = IntPtr.Zero;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
return null;
But how should I do to get the classname of the active window?
Simply pinvoke GetClassName(). This returns the Windows class name for a window, it doesn't have anything to do with a C# class. Getting the C# class name for a window in another process is not possible. Take a look at the Managed Spy++ tool for possible hacks if this is a Winforms app.
I expanded Hans Passant's answer into working code:
Usage:
string className = Spy.GetForegroundWindowClassName();
Class:
using System.Runtime.InteropServices;
using System.Text;
public static class Spy
{
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
public static string GetForegroundWindowClassName()
{
IntPtr hWnd = GetForegroundWindow();
var className = new StringBuilder(256);
GetClassName(hWnd, className, className.Capacity);
return className.ToString();
}
}
Side Note: in my case, I just needed a basic utility to tell me the class name of a window so I could reference that in my C# code. After writing the code above, I realized I could achieve the same thing using pre-existing utilities. One such utility I see mentioned often in the C# community is Visual Studio's Spy++ tool. I didn't bother trying that since it requires downloading 2.5 GB of C++ components. Instead, I used the "Window Spy" tool that comes with Autohotkey. Autohotkey is a tiny download compared to what's needed for Spy++, so I think it's a good option if it suits your needs.

Categories