0

I need to get a list of all currently opened MSACCESS instances in the system (windows) to be able to close any of them from within my app. I have no problems with EXCEL and WINWORD but can't hook up with Access.

I use Office 2016 and I see that MSACCESS creates separate procss for each opened database file. So I think I have to get application instances from window handles. I've tried to adapt this code: How to iterate through instance of Excel c#

I'm able to get all MSACCESS processes but the Excel or Word code isn't working for MSACCESS. The Code line:

if (buf.ToString() == "EXCEL7")

Always gives me the MsoCommandBarDock value. Any thoughts on how I can achieve this?

10
  • 1
    the class name of the MS Access main window happens to be OMain (CS_OWNDC style). why should it be EXCEL7? be aware that this will be different between versions and may change at any time when MS choose to update. MsoCommandBarDock is one or two levels too deep in the window hierarchy. Commented Oct 31, 2018 at 11:53
  • 1
    In Access, you can directly get the application object from the hWnd of the top-level window. In Excel, you need to get a specific nested window, and can acquire the application object from it's hWnd. This means you should use a significantly more simple approach for Access. You can just directly call AccessibleObjectFromWindow with the hWnd acquired from (int)p.MainWindowHandle. You can scrap all code enumerating child windows. I'd write up a short answer, but my C# is beginner level at best and I don't have a test environment handy, and this should get you started. Commented Oct 31, 2018 at 12:24
  • if you are interested in just closing the app why not just get the process by name and kill it? Commented Oct 31, 2018 at 16:38
  • @krishKM The app should save data and close gracefully. The simple kill is not enough :( Commented Nov 1, 2018 at 9:43
  • @ErikvonAsmuth This is it! I have to omit child finder routine to get the access to MSAccess Application class. Please post a new answer so I can accept it. Commented Nov 1, 2018 at 10:19

3 Answers 3

1

Based on the answer for Excel, the Access version is similar:

const uint OBJID_NATIVEOM = 0xFFFFFFF0;

var procs = new List<Process>(Process.GetProcessesByName("MSACCESS.EXE"));

foreach (var p in procs)
{
    var mainHandle = (int)p.MainWindowHandle;
    if (mainHandle > 0)
    {
        var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
        Microsoft.Office.Interop.Access.Application app = null;
        int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
        if (res >= 0)
        {
            Debug.Assert(app.hWndAccessApp == mainHandle);
            Console.WriteLine(app.Name);
        }
    }
}

I tested it with Access 2016 on Windows 10, en-us locale. The major difference is that the window hierarchy of access is not as convoluted as the one of Excel, therefore you can omit the iteration of child windows.

Disclaimer: This relies on the internal structure of a closed-source Windows application. Microsoft as its vendor discourages this kind of tricks for obvious reasons: they may ship and update or release a new version at any time where the inner structure (the window hierarchy) has changed, breaking code that relies on this. Also, MS Access used to have a single document view mode, which may present you with two versions of window hierarchy in the same release. Don't do this in commercial products / productive software.

Sign up to request clarification or add additional context in comments.

Comments

1

According to the answer from Cee McSharpface, in 2021 (Microsoft Access for Microsoft 365 MSO (16.0.14326.20504) 64-bit and Windows 10 20H2) I had to adapt the solution as follows:

[DllImport("oleacc.dll")]
private static extern int AccessibleObjectFromWindow(
int hwnd, uint dwObjectID, byte[] riid,
    ref Microsoft.Office.Interop.Access.Application ptr);  

const uint OBJID_NATIVEOM = 0xFFFFFFF0;

var procs = new List<Process>(Process.GetProcessesByName("MSACCESS"));

foreach (var p in procs)
{
    var mainHandle = (int)p.MainWindowHandle;
    if (mainHandle > 0)
    {
        var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
        Microsoft.Office.Interop.Access.Application app = null;
        int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
        if (res >= 0)
        {
            Debug.Assert(app.hWndAccessApp() == mainHandle);
            Console.WriteLine(app.Name);
        }
    }
}

Please notice the following changes:

GetProcessesByName uses "MSACESS" instead of "MSACCESS.EXE", according to this documentation:

The process name is a friendly name for the process, such as Outlook, that does not include the .exe extension or the path

AccessibleObjectFromWindow uses a ref Microsoft.Office.Interop.Access.Application ptr because there is no Window object like in the Excel interop.

Comments

0

There are many ways of doing this inlcuding retrieve COM Objects from ROT (running object table). Since your need is "just" to be able to close apps, following code should work fine.

using System.Diagnostics;
using System.Linq;

Process.GetProcessesByName("MSACCESS").All(x => x.CloseMainWindow());

This sends a close message to all Access main windows, which is similar to user closing the app.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.