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?
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.
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.
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.
Related
I have developed a c# code snippet to determine if the virtual (on-screen) keyboard was showing or not.
The code below worked fine in Windows 7, 8 and 8.1, but in Windows 10, the IsKeyboardVisible always returns true...
public static bool IsKeyboardVisible() {
Process keyboardProc;
UInt32 WS_DISABLED = 0x8000000;
UInt32 WS_VISIBLE = 0X94000000;
int GWL_STYLE = -16;
IntPtr keyboardHandle = GetKeyboardWindowHandle();
bool visible = false;
if (keyboardHandle != IntPtr.Zero) {
UInt32 style = GetWindowLong(keyboardHandle, GWL_STYLE);
// in Win10, this always returns "true", i.e. WS_DISABLED is
//
//visible = ((style & WS_DISABLED) != WS_DISABLED);
// UPDATE: I found this code helping here
visible = (style == WS_VISIBLE);
}
return visible;
}
I used a tutorial on SO, but it's a while ago so sorry for not crediting the author.
Does anyone know about a working code snippet for all recent Windows versions, so I don't have to check the actual OS to switch on the version...?
UPDATE
I found the original post here, which allowed me to correct the code. So now my problem is the same old Win10 issue - I can't show the virtual keyboard using
string progFiles = #"C:\Program Files\Common Files\Microsoft Shared\ink";
string keyboardPath = Path.Combine(progFiles, "TabTip.exe");
keyboardProc = Process.Start(keyboardPath);
... Again, is there any "all-platform" code I can use, or what is the suggested approach for Win10?
UPDATE 2
I found out about issues running a 32-bit application on a 64-bit os. That being said, the error occurs whether I try to run osk.exe in the System32 or the "sysWOW64` folder... Is there any other way than making a 64-bit release???
After much digging about TabTip.exe, osk.exe and x86 and x64 compatibility issues, I found a solution by searching the osk.exe on my system and trying to run each of them. I found 4 version the following folders:
C:\Windows\System32
C:\Windows\SysWOW64
C:\Windows\WinSxS\amd64_microsoft...
C:\Windows\WinSxS\wow64_microsoft...
It appears the one in C:\Windows\WinSxS\amd64_microsoft... works fine (not the other three though)...
Given the "amd64_...." folder might not be the same on different machines (I actually checked and they don't match, I didn't search whether this depends on the machine, the windows build or anything else...).
So basically I did a small routine to look into WinSxS folder and returning the very firs occurrence of osk.exe, which works just fine. I also made the code working on a 32-bit OS using a simple OS-architecture test:
string OSKpath64 = getOskPath(#"C:\Windows\WinSxS");
if (string.IsNullOrWhiteSpace(OSKpath64)) {
OSKpath64 = "osk.exe";
}
string OSKpath32 = #"C:\Windows\System32\osk.exe";
if (!File.Exists(OSKpath32)) {
OSKpath32 = #"osk.exe";
}
System.Diagnostics.Process.Start((Environment.Is64BitOperatingSystem) ? OSKpath64 : OSKpath32);
UPDATE:
The confusion with one working and one non-working version within the WinSxS folder made me nervous. It works just fine because the amd_.. folder is alphabetically before wow64_....
I therefore suggest to add a test in the getOskPath method to return the first native 64-bit osk.exe (not the emulated one).
Using the IsWin64Emulator method found here, the method looks like this:
static string getOskPath(string dir) {
string path = Path.Combine(dir, "osk.exe");
if (File.Exists(path)) {
Process p = System.Diagnostics.Process.Start(path);
if (p.IsWin64Emulator()) {
path = string.Empty;
}
p.Kill();
return path;
}
DirectoryInfo di = new DirectoryInfo(dir);
foreach (DirectoryInfo subDir in di.GetDirectories().Reverse()) {
path = getOskPath(Path.Combine(dir, subDir.Name));
if (!string.IsNullOrWhiteSpace(path)) {
return path;
}
}
return string.Empty;
}
Same problem with me, I try all answer here, but it not work.
After finding solution with google, this is is ok.
// Step 1: For Load On-Screen Keyboard
const string Kernel32dll = "Kernel32.Dll";
[DllImport(Kernel32dll, EntryPoint = "Wow64DisableWow64FsRedirection")]
public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport(Kernel32dll, EntryPoint = "Wow64EnableWow64FsRedirection")]
public static extern bool Wow64EnableWow64FsRedirection(IntPtr ptr);
IntPtr wow64Value;
//---------------------------------------
// Step 2: Function-----
if (Environment.Is64BitOperatingSystem)
{
if (Wow64DisableWow64FsRedirection(ref wow64Value))
{
System.Diagnostics.Process.Start("osk.exe");
Wow64EnableWow64FsRedirection(wow64Value);
}
}
else
{
System.Diagnostics.Process.Start("osk.exe");
}
//----------------
I'm developing 'Share Monitoring Application' via C# and it's monitoring the sharing activities and I'm using these APIs to implement enumerate shared items/un-sharing shared items.
Api used:
NetShareEnum
NetShareDel
NetShareEnum to enumerate all shared items and NetShareDel to delete shared items (=unshare).
I used SHChangeNotify to remove shared mark and directories working fine. (Delete shared item using NetShareDel is not affected immediately.)
But printer state is not affected by SHChangeNotify. Which mean after deleting shared printer via NetShareDel and call SHChangeNotify with SHCNE_NETUNSHARE and SHCNF_PATHW. Also I used SHCNE_NETUNSHARE and SHCNF_PRINTERW too, but nothing happened.
Shared printer's state mark: http://i.stack.imgur.com/1ZGrI.png
In this picture, you can see the users the right side of check circle and that indicate printer is shared.
But after calling NetShareDel to unshared shared printer and it's succeed, but shared mark is disappear.
Anyone know how to implement this? I'm waiting for your help. :D
Sorry for my bad english.
Have you tried going via WMI?
I haven't used it myself to "unshare" a printer, but I use it alot in an application to edit printers and printer-ports in other ways.
I would think something like this should do the trick.
The Win32_Printer class looks like it has a "shared" property, so I would suggest trying to switch it to false.
https://msdn.microsoft.com/en-us/library/aa394363%28v=vs.85%29.aspx
I haven't tested this code with unsharing, but it is the exact same code I use to change other properties.
//get the printer(s) through wmi query
//prep query
SelectQuery query = new SelectQuery(string.Format("select * from Win32_Printer WHERE Name = '{0}'", "printername"));
//create scope (connect to server)
ManagementScope scope = new ManagementScope("\\\\serverName\\root\\cimv2");
//search for printers
ManagementObjectSearcher search = new ManagementObjectSearcher(scope, query);
//get collection of printers (should be 0 or 1, but it returns a collection regardless because of the query
ManagementObjectCollection printers = search.Get();
//iterate through the 0-1 printers and set Shared to false
foreach (ManagementObject printer in printers)
{
printer.SetPropertyValue("Shared",false);
printer.put();
}
I tried WMI and it works on my computer, but other computers throw an exception. And I think the reason of application throw an exception is the one of required library is missing on the computer.
So I'm looking for the API that can be used instead of the WMI.
Finally I found the GetPrinter and SetPrinter from the MSDN.
And also I found PRINTER_INFO_5 structure. According to MSDN, Attributes field indicate the printer's attribute including printer is shared or not. And this can be checked Attributes field has PRINTER_ATTRIBUTE_SHARED value.
Anyway, this problem can be solved only OpenPrinter, GetPrinter and SetPrinter.
This image shows the before and after calling 'UnsharePrinter' method.
This is the method I made to un-share the shared printer.
(Un-sharing the shared printer can be performed via NetShareDel, but it cannot notify printer is un-shared to the system.)
Boolean UnsharePrinter(String printerName) {
// fill PRINTER_DEFAULTS structure
// and set DesiredAccess to PRINTER_ACCESS_ADMINISTER to
// get rights to call SetPrinter
PRINTER_DEFAULTS pd;
pd.pDatatype = IntPtr.Zero;
pd.pDevMode = IntPtr.Zero;
pd.DesiredAccess = PRINTER_ACCESS_ADMINISTER;
IntPtr pDefaults = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PRINTER_DEFAULTS)));
Marshal.StructureToPtr(pd, pDefaults, true);
IntPtr hPrinter;
// open the printer
if ( !OpenPrinter(printerName, out hPrinter, pDefaults) ) {
Marshal.FreeHGlobal(pDefaults);
return false;
}
// first, call Zero pointer and 0 size to get minimum required space
IntPtr pInfo = IntPtr.Zero;
Int32 pcbNeeded;
GetPrinter(hPrinter, 5, pInfo, 0, out pcbNeeded);
// alloc reqiured space and call GetPrinter
pInfo = Marshal.AllocHGlobal(pcbNeeded);
if ( !GetPrinter(hPrinter, 5, pInfo, pcbNeeded, out pcbNeeded) ) {
Marshal.FreeHGlobal(pInfo);
ClosePrinter(hPrinter);
return false;
}
// pointer to structure
PRINTER_INFO_5 pi5 = (PRINTER_INFO_5) Marshal.PtrToStructure(pInfo, typeof(PRINTER_INFO_5));
Marshal.FreeHGlobal(pInfo);
// if printer is not shared, release the memory and exit
if ( (pi5.Attributes & PRINTER_ATTRIBUTE_SHARED) == 0 ) {
ClosePrinter(hPrinter);
return false;
}
// remove the shared flag
pi5.Attributes &= ~PRINTER_ATTRIBUTE_SHARED;
// alloc pointer and make structure as pointer
pInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PRINTER_INFO_5)));
Marshal.StructureToPtr(pi5, pInfo, true);
// set printer
Boolean r = SetPrinter(hPrinter, 5, pInfo, 0);
Marshal.FreeHGlobal(pInfo);
ClosePrinter(hPrinter);
return r;
}
I would like to get the Path of the windows which has the focus.
Ex: I have 3 windows Opened
a. C:\Windows
b. C:\Windows\System32
c. C:\Users\COMP-0\Documents
And i am working on c (C:\Users\COMP-0\Documents)
So i would like to get this path (C:\Users\COMP-0\Documents) programmatically in C#.
Expanding on this answer to get the selected files in a folder, you can use a similar approach to get the current folder and therefore it's path.
This needs some COM and requires:
Getting the active window using GetForegroundWindow
Find the current list of InternetExplorer windows using SHDocVw.ShellWindows,
Matching handle pointers to find the current window
Getting hold of the folder path inside the active window using the IShellFolderViewDual2 COM interface.
There are a couple of caveats to be aware of:
Special folders (Favourites, My Computer etc) will give you the file path as "::{GUID}" where the GUID points to the CLSID for that folder in the registry. It is probably possible to convert that value to a path.
Going to "Desktop" will return null for the current folder
Focussing Internet Explorer will trigger a match on the active window so we need to ensure we are in a Shell Folder
If in a special folder or Desktop this code will just return the current window title - usually the name of the special folder - using the details in this answer.
private static string GetActiveExplorerPath()
{
// get the active window
IntPtr handle = GetForegroundWindow();
// Required ref: SHDocVw (Microsoft Internet Controls COM Object) - C:\Windows\system32\ShDocVw.dll
ShellWindows shellWindows = new SHDocVw.ShellWindows();
// loop through all windows
foreach (InternetExplorer window in shellWindows)
{
// match active window
if (window.HWND == (int)handle)
{
// Required ref: Shell32 - C:\Windows\system32\Shell32.dll
var shellWindow = window.Document as Shell32.IShellFolderViewDual2;
// will be null if you are in Internet Explorer for example
if (shellWindow != null)
{
// Item without an index returns the current object
var currentFolder = shellWindow.Folder.Items().Item();
// special folder - use window title
// for some reason on "Desktop" gives null
if (currentFolder == null || currentFolder.Path.StartsWith("::"))
{
// Get window title instead
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
}
else
{
return currentFolder.Path;
}
}
break;
}
}
return null;
}
// COM Imports
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
A few days ago, I had to run Excel with current user rights using ProcessStartInfo in order to user some addins that require admin or current user rights.
new Process{
StartInfo = new ProcessStartInfo{
Verb = "runas",
FileName = #"path/to/app.exe",
Arguments = "-officeexcel",
Domain = domain,
UserName = login,
Password = pa ss,
UseShellExecute = false,
}
}.Start();
Thus, i can't use anymore :
Marshal.GetActiveObject("Excel.Application")
to get excel instance. So i tried to use late binding with AccessibleObjectFromWindow() :
int hwnd = (int)FindWindow("XLMAIN", null);
if (hwnd != 0)
{
int hwndChild = 0;
EnumChildCallback cb = new EnumChildCallback(EnumChildProc);
EnumChildWindows(hwnd, cb, ref hwndChild);
if (hwndChild != 0)
{
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
ExcelWindow ptr;
int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out ptr);
if (hr >= 0)
{
using (UILanguageHelper fix = new UILanguageHelper())
{
return (Excel.Application)ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
}
}
}
}
return null;
It worked but the Excel Application I get has zero property and I can't invoke any methods !
I need to run some macros after getting elevated Excel instance !
What did i do wrong ?
You cannot interact with an application launched in elevated mode from a non-elevated one for security reasons.
In your case, I think the only way to achieve what you ask is to elevate your app (or another small app dedicated to this task) first, then, your elevated app, will, on its turn, run excel and perform the tasks you require. Excel will run in elevated mode.
The design pattern in this way is to run your application with the minimum rights required. And then elevate, specifying in the command line a "token" (whatsoever you can use to connect to your application via IPC/sockets).
After reading this post on SO I tried to write a small app I need to read and write hidden registry keys/values.
I checked Registry Manipulation using NT Native APIs and Creating "Hidden" Registry Values links.
First one gave me something to work on, but it's written in C++ while second is a Delphi project working well.
I am not able to convert first and I could try to convert second, but I'd need to find some code to read keys/values too. For this reason I'd like to know if there's something "ready" and tested in C#.
I've also downloaded Proces Hacker v1.11 source code and used it to partially convert Delphi example as shown below, but hidden registry key is accessible (while in Delphi it wasn't) and there are not APIs to write values.
static void Main(string[] args)
{
string KeyNameBuffer = #"\Registry\User\S-1-5-21-3979903645-2167650815-2353538381-1001\SOFTWARE";
string NewKeyNameBuffer = "Systems Internals";
string HiddenKeyNameBuffer = "Can't touch me\0";
string HiddenValueNameBuffer = "Hidden Value";
// Apro la chiave di registro
IntPtr SoftwareKeyHandle = CreateKey(KeyNameBuffer, IntPtr.Zero);
if (SoftwareKeyHandle != IntPtr.Zero)
{
IntPtr SysKeyHandle = CreateKey(NewKeyNameBuffer, SoftwareKeyHandle);
if (SysKeyHandle != IntPtr.Zero)
{
// This key shouldn't be accessible, but it is
IntPtr HiddenKeyHandle = CreateKey(HiddenKeyNameBuffer, SysKeyHandle);
if (HiddenKeyHandle != IntPtr.Zero)
{
// I don't have APIs to write values
}
}
}
}
static IntPtr CreateKey(string keyName, IntPtr rootKey)
{
IntPtr res;
KeyCreationDisposition disp;
ObjectAttributes attributes = new ObjectAttributes(keyName,
ObjectFlags.CaseInsensitive,
new NativeHandle(rootKey));
NtStatus st = Win32.NtCreateKey(out res, KeyAccess.All,
ref attributes, 0,
IntPtr.Zero, RegOptions.NonVolatile, out disp);
return st == NtStatus.Success ? res : IntPtr.Zero;
}
Finally: from Vista on, you cannot write \Registry\Machine part if you're not running your app as Administrator, so in the example I used my user registry key. Is there a way to us native APIs to write that part of the registry if I need to store a per-machine value?
If you want it in HKLM and privileges don't let you, it doesn't matter which API layer you're using, Reg* functions of Nt* ones - it won't let you do that with access denied error.