I need to interact with an external application running, and send specific keypresses & releases. I've tried to use the SendKeys class, but it does only half of the job, as the keypress is being sent with an immediate keyrelease to the external applications.
I need to be able to simulate a "key hold down" for the external app. I'm now trying to use the SendMessage thing, but for now it won't work at all :( and I don't even get errors.
Ok, case solved. I actually installed VC++ to try the core keybd_event() function, and after it worked I was able to use it wisely in C#.
Here's the code, and surprisingly it's very simple. You'll need to add this using to your code to be able to import dll's: using System.Runtime.InteropServices;
This code will press and hold the '1' button for 3 secs, and then will release for 1 second and repeat the process.
(the code highlight got messed up :/, copy from 'namespace ...' to the last bracket '}')
public class Program
{
[DllImport("user32.dll")]
private static extern void keybd_event(byte bVk, byte bScan,
uint dwFlags, UIntPtr dwExtraInfo);
private static void Main(string[] args)
{
while (true)
{
keybd_event((byte)0x31, (byte)0x02, 0, UIntPtr.Zero);
Thread.Sleep(3000);
keybd_event((byte)0x31, (byte)0x82, (uint)0x2, UIntPtr.Zero);
Thread.Sleep(1000);
}
}
}
have you tried using PostMessage to send WM_KEYDOWN and WM_KEYUP ?
Edit
You would use it this way (I am writing in C++, but you can easily use PInvoke and ..NET)
HWND hwnd = FindWindow(NULL,_T("Mywindow"));
PostMessage(hwnd,WM_KEYDOWN,VK_A,0);
You can use the WSH Scripting Shell to do this:
var shell = new WshShellClass();
var missing = System.Reflection.Missing.Value;
shell.SendKeys("MOO!!!", ref missing);
All you need to do is add a COM reference to "Windows Scripting Host Object", version 1.0. Everything is in the namespace IWshRuntimeLibrary.
The official API is SendInput.
Related
How do I get the name the executable was invoked as (equivalent to C's argv[0])? I actually need to handle somebody renaming the executable and stuff like that.
There's a famous question with lots of answers that don't work. Answers tried:
System.AppDomain.CurrentDomain.FriendlyName
returns the name it was compiled as
System.Diagnostics.Process.GetCurrentProcess().ProcessName
strips extension (ever rename a .exe to a .com?), also sees through symbolic links
Environment.GetCommandLineArgs()[0]
It returns a name ending in .dll, clearly an error.
Assembly.GetEntryAssembly().Location
Returns null
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName
Returns a .dll name again.
The documentation for .net 5.0 says Environment.GetCommandLineArguments()[0] works; however it doesn't actually work. It somehow sees through symbolic links and returns the real executable name.
What I'm trying to do is link all of our stuff into a single multi-call binary so I can use the .net 5 framework reducer on the resulting binary so I don't have to ship about 30MB of .net 5 framework we're not using. I really don't want to do a platform ladder and P/Invoke a bunch of stuff unless I have to.
I'm after argv[0] directly, not the running process executable name. In the case of symbolic links, these differ.
Came across this with .NET 6, where Process.GetCurrentProcess().MainModule?.FileName seems to be working fine now, and there's also Environment.ProcessPath.
If targeting Windows only, it might be safer (more predictable) to use interop. Below are some options, including the native GetModuleFileName and GetCommandLine:
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("Process.GetCurrentProcess().MainModule?.FileName");
Console.WriteLine(Process.GetCurrentProcess().MainModule?.FileName);
Console.WriteLine();
Console.WriteLine("Assembly.GetExecutingAssembly().Location");
Console.WriteLine(Assembly.GetExecutingAssembly().Location);
Console.WriteLine();
Console.WriteLine("Environment.ProcessPath");
Console.WriteLine(Environment.ProcessPath);
Console.WriteLine();
Console.WriteLine("Environment.CommandLine");
Console.WriteLine(Environment.CommandLine);
Console.WriteLine();
Console.WriteLine("Environment.GetCommandLineArgs()[0]");
Console.WriteLine(Environment.GetCommandLineArgs()[0]);
Console.WriteLine();
Console.WriteLine("Win32.GetProcessPath()");
Console.WriteLine(Win32.GetProcessPath());
Console.WriteLine();
Console.WriteLine("Win32.GetProcessCommandLine()");
Console.WriteLine(Win32.GetProcessCommandLine());
Console.WriteLine();
public static class Win32
{
private const int MAX_PATH = 260;
private const int INSUFFICIENT_BUFFER = 0x007A;
private const int MAX_UNICODESTRING_LEN = short.MaxValue;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr GetCommandLine();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
private static extern int GetModuleFileName(
IntPtr hModule, StringBuilder lpFilename, [MarshalAs(UnmanagedType.U4)] int nSize);
public static string GetProcessCommandLine()
{
return Marshal.PtrToStringUni(GetCommandLine()) ??
throw new Win32Exception(nameof(GetCommandLine));
}
public static string GetProcessPath()
{
var buffer = new StringBuilder(MAX_PATH);
while (true)
{
int size = GetModuleFileName(IntPtr.Zero, buffer, buffer.Capacity);
if (size == 0)
{
throw new Win32Exception();
}
if (size == buffer.Capacity)
{
// double the buffer size and try again.
buffer.EnsureCapacity(buffer.Capacity * 2);
continue;
}
return Path.GetFullPath(buffer.ToString());
}
}
}
The output when running via dotnet run:
Process.GetCurrentProcess().MainModule?.FileName
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Assembly.GetExecutingAssembly().Location
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.ProcessPath
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Environment.CommandLine
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.GetCommandLineArgs()[0]
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Win32.GetProcessPath()
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Win32.GetProcessCommandLine()
"C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe"
Oh, and for a Windows Forms application, there always has been Application.ExecutablePath.
Updated, running it on Ubuntu 22.04 with .NET 6.0.2 (with Win32 interop removed), either via dotnet run or directly as ./ProcessPath:
Process.GetCurrentProcess().MainModule?.FileName
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Assembly.GetExecutingAssembly().Location
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.ProcessPath
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Environment.CommandLine
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.GetCommandLineArgs()[0]
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
After watching everything fail, it became necessary to P/Invoke stuff to make this work. While Process.GetCurrentProcess().MainModule?.FileName reliably returns the executable binary (at least when not running under the debugger), this does not provide the command invocation the binary was launched with.
On Windows, GetCommandLine() is P/Invokable and needs only some parsing to get the information. On *n?x, reading /proc/self/cmdline does the same job.
I built a library encapsulating this. https://github.com/joshudson/Emet/tree/master/MultiCall You can find binaries on nuget.org ready to go.
I should have self-answered a long time ago. Better late than never. Nobody seemed to care until now.
Using .NET Core 3.1, this worked for me to restart a currently running program.
string filename = Process.GetCurrentProcess().MainModule.FileName;
System.Diagnostics.Process.Start(filename);
// Closes the current process
Environment.Exit(0);
TL;DR: Trying to solve the issue that InputLanguage changes the input layout but does not update the Language Bar display.
I am writing a custom plugin for Trados Studio. Part of this is interfacing with languages both in the app itself and in Word, as in this question: List of all available languages for Windows .NET framework
The last problem I seem to cannot solve is that in a part of the code I am using InputLanguage to set the keyboard input to en-US.
To clarify, there is a limited API, so I have to be really inventive in automating certain aspects. The best workable approach was to use the default shortcuts in the application:
First I change the input language to en-US.
Then I send some keys to the application.
Then I change the input language back to what it was before.
Then I show a form.
Here is the code:
//change input language to English
InputLanguage currentLang = InputLanguage.CurrentInputLanguage;
InputLanguage newLang = InputLanguage.FromCulture(System.Globalization.CultureInfo.GetCultureInfo("en-US"));
if (newLang == null)
{
MessageBox.Show("The Upload Project function requires the En-US keyboard installed.", "Missing keyboard", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
return;
}
else
{
InputLanguage.CurrentInputLanguage = newLang;
}
//Save the document
SendKeys.SendWait("^s");
InputLanguage.CurrentInputLanguage = currentLang;
//get url and credentials from a custom input form
string[] psw = UploadData.GetPassword(
Settings.GetValue("Upload", "Uri", "https://www.scntranslations.org/ws/services"),
Vars.wsUsername == null ? Settings.GetValue("Upload", "User", "") : Vars.wsUsername,
Vars.wsPassword == null ? "" : Vars.wsPassword
);
Application.DoEvents();
The manifestation that I have is that the Language Bar changes with a delay to EN but by the time the form shows up it should be HU yet it stays EN.
However if I test it with Debug.WriteLine(InputLanguage.CurrentInputLanguage.LayoutName) then the output is the correct language (in my case "Hungarian").
Even after the form is hidden, the language remains EN, yet the keyboard types in Hungarian and the Debug.WriteLine(InputLanguage.CurrentInputLanguage.LayoutName) returns "Hungarian".
I looked at dozens of pages on SO and the web and I tried everything I could think of including System.Threading.Thread.Sleep(1000); and Application.DoEvents() and Sendkeys.Flush(), but nothing triggers Windows to update the Language Bar, and I could not find any solution to this issue.
The earlier version of this problem as given in this question: Change keyboard layout from C# code with .NET 4.5.2
I have made this work now fully with the above implementation of the InputLanguage bar this last hick-up.
Can anybody help in:
giving an explanation for this behavior?
suggesting a solution that triggers the update of the Language Bar from C# code?
UPDATE: The code now fully works. I implemented the BlockInput WinAPI.
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "BlockInput")]
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
public static extern bool BlockInput([System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)] bool fBlockIt);
And also turning off the Caps Lock using Win API:
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
private void TurnOffCapsLock()
{
if (Control.IsKeyLocked(Keys.CapsLock)) // Checks Capslock is on
{
const int KEYEVENTF_EXTENDEDKEY = 0x1;
const int KEYEVENTF_KEYUP = 0x2;
NativeMethods.keybd_event(0x14, 0x45, KEYEVENTF_EXTENDEDKEY, (UIntPtr)0);
NativeMethods.keybd_event(0x14, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP,
(UIntPtr)0);
}
}
So what's left really is getting the Language Bar to correctly show the actual current input language.
I do have the option of sending Alt+Shifts, but I really don't want to do that.
I am working on an application which on the click of a button will clear both the system clipboard and the office clipboard.
I have tried System.Windows.Forms.Clipboard.Clear() and the following
static class WinAPI
{
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern bool OpenClipboard(System.IntPtr WinHandle);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern bool EmptyClipboard();
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern bool CloseClipboard();
public static void ClearClipboard()
{
if (OpenClipboard(System.IntPtr.Zero))
{
EmptyClipboard();
CloseClipboard();
}
}
}
Both of them seem clear only the system clipboard. Is there a way to extend this to office clipboard.
Per this question, the answer appears to be "no, not cleanly":
There has been no VBA support for manipulating the Office clipboard
after Excel 2000. SendKeys is flaky, but it's the only way.
The "SendKeys" referred to is a "solution" where you send the necessary keystrokes or window messages -- assuming one is running, if not, you'll have to start one. There are also other ways to do that, like AutoIt. Unfortunately, all of these solutions are fragile and prone to break between versions of Office, because they rely on internal details of window names and appearances. Before you do this, make sure you're willing to accept the maintenance burden.
A better approach is probably telling whoever wants this application that it can't be done because Microsoft wants clipboard control to remain with the end user -- or, alternatively, that there already is a button for clearing the clipboard, and it exists in Office (Alt, H, FO shows it in my version). That doesn't always work, but when it does it saves you a ton of work.
You could copy a single space to the clipboard, if the other methods are troublesome?
You can try the solution presented here. It's meant for VBA but you can easily convert the code to C#.
Let me know if you need help.
I'm running a small tool (on Windows 7, 32 Bit) that I would like to see what document is open in another application I've tried this, which works for NotePad on Windows.
var myProcess = Process.GetProcessesByName("NotePad");
string title = myProcess[0].MainWindowTitle;
MessageBox.Show(title);
the output is:
"New Text Document - Notepad"
Now, if I try another application it doesn't always give me the correct title but I noticed that most microsoft applications seem to be fine - NotePad, WordPad, EXCEL etc. It's this other software that is an issue. It has a long title but just returns a very simple name.
Here's what I get from my application which has processName = "FooBar"
The actual running window has this up the top:
"FooBar Software Verson 1.2 - [Results]"
and my code gives:
"FooBar"
any ideas?
[EDIT} 2012-11-19
The very crux of this issue is that I was trying to get the name of the open file from the window. It now seems that the software I'm using doesn't display it there. What I've discovered is that a program called "AutoIT3 Window Spy" can get the text I need as the text of the open file is on the window and not only in the title. I downloaded the source (it's part of http://www.autohotkey.com/ which is open source. It seems to rely on many of the suggestions already made but I'm not able to figure it out just yet.) The source code that I"m looking at is c++ and is located here https://github.com/AutoHotkey/AutoHotkey
So I think the solution to my problem may lay elsewhere. This one may go unanswered.
The main window title is what you see when you go in to task manager and look at the Description column, not the window title itself.
It's the title of the process, not the title of a particular window in the process. A given process may have any number of windows open at one time.
If you need the actual window title, you have to hook user32 something like this:
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Security;
namespace Application
{
public class Program
{
public static void Main ( )
{
IntPtr hwnd = UnsafeNativeMethods.FindWindow("Notepad", null);
StringBuilder stringBuilder = new StringBuilder(256);
UnsafeNativeMethods.GetWindowText(hwnd, stringBuilder, stringBuilder.Capacity);
Console.WriteLine(stringBuilder.ToString());
}
}
[SuppressUnmanagedCodeSecurity]
internal static class UnsafeNativeMethods
{
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern int GetWindowText ( IntPtr hWnd, [Out] StringBuilder lpString, int nMaxCount );
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr FindWindow ( string lpClassName, string lpWindowName );
}
}
It's possible that the 'title' you're seeing is some kind of owner-drawn UI element which is overriding the visual representation of the title, while the Windows APIs are likely to ignore that sort of thing.
I'd recommend examining the window with a tool like Spy++ to see if that's the case.
It's also possible that the author of the application decided to override the WM_GETTEXT message and is returning that instead of what's actually in the titlebar, although I'm not 100% sure if GetWindowText() is being called here and whether or not it sends the message explicitly or works some other way.
The developer has stated the following:
"I think the failure may be due to a discontinued property Form.Caption in VB 6.0 that was replaced with a Form.Text in. NET"
Thank you all for your valuable suggestions along the way!
I'm trying to use ShSetFolderPath function in C#. I work on Win7, I've managed to use ShSetKnownFolderPath and it works fine.
Since this function is unavaible in WinXP, i tried to invoke ShSetFolderPath. Because i'm not familiar with invoking, I've done some searching and found something on some French forum. I don't speak French, but this declaration makes sense (as written in Remarks of function documentation in MSDN library):
[DllImport( "Shell32.dll", CharSet = CharSet.Unicode, EntryPoint = "#232" ) ]
private static extern int SHSetFolderPath( int csidl, IntPtr hToken, uint flags, string path );
I call it like that:
private static int CSIDL_DESKTOP = 0x0000;
public static void SetDesktopPath(string path)
{
int ret;
ret = SHSetFolderPath(CSIDL_DESKTOP, IntPtr.Zero, 0, path);
if (ret != 0)
{
Console.WriteLine(ret);
Console.WriteLine(Marshal.GetExceptionForHR(ret));
}
}
It works in Win7, but in XP function returns -2147024809, which means "Value does not fall within the expected range".
My guess is, it's something wrong with Dll importing. Any idea?
Funny thing.
I've taken another look at CSIDL list. And I've realized I was trying to change some "low-level" reference (i guess) to desktop:
CSIDL_DESKTOP = 0x0000, // <desktop>
While I actually wanted to change just folder location, and i should've use this:
CSIDL_DESKTOPDIRECTORY = 0x0010, // <user name>\Desktop.
And THIS works.
It explains everything. Shame on me.
Nah, that's not it. The error code, converted to hex, is 0x80070057. The 7 indicates a Windows error, 57 is error code 87, ERROR_INVALID_PARAMETER, "The parameter is incorrect".
A couple of possible reasons. First is that entry point #232 isn't actually the entry point for SHSetFolderPath(). You might be calling a different function, it wouldn't know what to do with the argument values you pass. Hard to say, it is an unnamed entry point on XP's version of shell32.dll. Or it could be that XP just isn't happy about you changing the desktop folder path. Not that surprising, there's a heckofalot it has to do to actually implement that, refreshing all Explorer.exe views, rebuilding the desktop contents and whatnot.
Check this thread for possible help.