Jump to content
VBRookie

Send ALT-key to hidden window in standalone VBScript

Recommended Posts

In a VBScript that's run standalone (e.g. via double-click on a .vbs file), I'm trying to send an ALT-key combination to a non-active window with a given title, e.g. "Command Prompt" (CMD).(In CMD, for example, sending Alt-Space Alt-E Alt-S should call up the menu entry for highlighting everything. But CMD is just a sample for a program without API that I want to control via key sends without making it the active window.) Abusing Word.Tasks, I succeeded in sending normal WM_CHAR messages to CMD via task.sendwindowmessage.However, sending WM_KEYDOWN or WM_SYSKEYDOWN, VK_MENU, WM_SYSKEYUP etc. messages seemed not to have any effect, and WM_SYSCHAR messages or WM_CHAR messages with the ALT-bit 29 set in the lParam result in normal characters being sent. With Notepad, messages did not appear to work at all. Here's the code:

' === getAppTask ===' Gets the task for an application whose windows title contains' the string <appTitle>.' Returns "true" if successfull, "false" if not.' ==================Dim WordSet Word = CreateObject("Word.Application")Function getAppTask(appTitle, ByRef task)dim TasksSet Tasks = Word.TasksFor Each task in TasksIf Task.Visible Thendim tasknametaskname = task.nameif instr(1,taskname,appTitle) thengetAppTask = trueexit functionend ifend ifnextgetAppTask = falseEnd Function' === Test Code ===Const WM_KEYDOWN = &H100Const WM_KEYUP = &H101Const WM_CHAR = &H102Const WM_SYSKEYDOWN = &H104Const WM_SYSKEYUP = &H105Const WM_SYSCHAR =&H106Const WM_SYSCOMMAND =&H112Const WM_CLOSE = &H10Const VK_SHIFT = &H10Const VK_MENU = &H12Const SC_KEYMENU =&HF100dim task' === Test Notepad (must be open with Untitled) ===if getAppTask("Untitled - Notepad", task) thentask.Activate ' just for debugging, so I see the task has been found' this does not work:task.SendWindowMessage WM_CHAR, Asc("A"), 0' this does not work either:task.SendWindowMessage WM_SYSCOMMAND, SC_KEYMENU, Asc("F")task.SendWindowMessage WM_SYSCOMMAND, SC_KEYMENU, Asc("O")end if' === Test CMD (must be open) ===if getAppTask("Command Prompt", task) thentask.Activate ' just for debugging, so I see the task has been found' this works:task.SendWindowMessage WM_CHAR, Asc("A"), 0' this does not work:task.SendWindowMessage WM_SYSKEYDOWN, VK_MENU, &h20000000task.SendWindowMessage WM_SYSKEYDOWN, Asc(" "), &h20000000task.SendWindowMessage WM_SYSKEYUP, Asc(" "), &h20000000task.SendWindowMessage WM_SYSKEYDOWN, Asc("E"), &h20000000task.SendWindowMessage WM_SYSKEYUP, Asc("E"), &h20000000task.SendWindowMessage WM_SYSKEYDOWN, Asc("S"), &h20000000task.SendWindowMessage WM_SYSKEYUP, Asc("S"), &h20000000task.SendWindowMessage WM_SYSKEYUP, VK_MENU, 0' this does not work either:task.SendWindowMessage WM_SYSCHAR, Asc(" "), &h20000000task.SendWindowMessage WM_SYSCHAR, Asc("E"), &h20000000task.SendWindowMessage WM_SYSCHAR, Asc("S"), &h20000000end ifWord.Quit 

I tried different combinations of WM messages with different lParams, but without success.Maybe PostMessage instead of SendWindowMessage would work, but- I don't know whether it's available in stand-alone VBScript, and- I don't know how to get the HWND of the task (which is NOT started by the script) since I was not able to import FindWindow in stand-alone VBScript. Any suggestions? (Please NOT SendKey since it requires the window to have the focus.)Thanks! VBRookie

Edited by VBRookie
  • Like 1

Share this post


Link to post
Share on other sites

P.S. - Motivation for my post above Here is why I want to be able to send Alt-Keys to applications whose windows are non-active: I'm using scripts to automate manual procedures involving several applications, e.g. for an automatic startup script at logon time.Using the latter, I can have a coffee break after login, and when I return, the most important applications have started, and within the applications, the most important sub-applications have been started (which in my case are started via Alt-Key combinations from the parent application), or some menu options of the applications are automatically carried out (using Alt-Key combinations). Since startup of some applications, such as Lotus Notes, takes a long time due to some network services involved, they tend to pop up in front of currently active windows after a random time. In addition, as a user, I'd like to be able to work with already started apps (e.g. checking my mail) before all apps have finished loading and before their Alt-Key commands are processed. For these reasons I cannot reliably use SendKeys because it only works with the active window, so if another window pops into the foreground just between task.Activate and SendKeys, stealing the focus, the keys may be sent to this other window instead. Using Word.tasks and task.SendWindowMessage (as in my post above) appears to solve the problem for "normal" keys (at least for some applications such as "Command Prompt", albeit not for Notepad) - these "normal" keys can be sent to a non-active window.But I was not able to make it work with Alt-Key combinations. Any help would be appreciated.Thanks in advance. VBRookie

Edited by VBRookie

Share this post


Link to post
Share on other sites

It sounds like you need to use WM_SYSKEYDOWN for alt-keys and WM_KEYDOWN for non-alt-keys. That's what I get from this: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646280(v=vs.85).aspxhttp://msdn.microsoft.com/en-us/library/windows/desktop/ms646286(v=vs.85).aspx Otherwise, one way to do this might be to spam the AppActivate command before each keystroke to make sure the specific window has focus before sending each keystroke. http://technet.microsoft.com/en-us/library/ee156592.aspx

Share this post


Link to post
Share on other sites

Hi justsomeguy, thanks for your reply.

It sounds like you need to use WM_SYSKEYDOWN for alt-keys and WM_KEYDOWN for non-alt-keys. That's what I get from this: http://msdn.microsof...s646280(v=vs.85).aspxhttp://msdn.microsof...s646286(v=vs.85).aspx
I tried to change the bottom of my code to the following:
' this does not work:task.SendWindowMessage WM_SYSKEYDOWN, VK_MENU, &h20000000task.SendWindowMessage WM_KEYDOWN, Asc(" "), &h20000000task.SendWindowMessage WM_KEYUP, Asc(" "), &h20000000task.SendWindowMessage WM_KEYDOWN, Asc("E"), &h20000000task.SendWindowMessage WM_KEYUP, Asc("E"), &h20000000task.SendWindowMessage WM_KEYDOWN, Asc("S"), &h20000000task.SendWindowMessage WM_KEYUP, Asc("S"), &h20000000task.SendWindowMessage WM_SYSKEYUP, VK_MENU, 0

but it still didn't work. Any idea what's wrong?

Otherwise, one way to do this might be to spam the AppActivate command before each keystroke to make sure the specific window has focus before sending each keystroke. http://technet.micro...y/ee156592.aspx
That's what the script I am currently using actually does, however, as pointed out in my post "P.S. - Motivation for my post above", that's not an ideal option because1. it's unreliable because once in a while a popping up application or a user interaction may steal the focus between AppActivate and SendKeys, and2. users should be able to continue working while the script is running in the background, sending keys to non-active Apps. I can't believe the creators of VBScript did not think of something so basic as sending an ALT-key to a non-active window :-( ... VBRookie Edited by VBRookie

Share this post


Link to post
Share on other sites

If I an understanding the documentation correctly (which is not very straightforward with Microsoft documention), then this would send Alt-E: task.SendWindowMessage WM_SYSKEYDOWN, Asc("E"), &h20000000

I can't believe the creators of VBScript did not think of something so basic as sending an ALT-key to a non-active window :-( ...
Really? You can't believe that? It's not necessarily VBScript, it's Windows Script Host you're using. VBScript is just the language you're using it with. But BASIC was developed as an easy-to-read language that could be used by non-programmers. It wasn't developed to be powerful or elegant, just easy to read. It's a wonder why so many people use it today. Wikipedia claims that in 2006 that 59% of .NET framework developers used VB.NET as their only programming language. That's a lot of "programmers" using a language that was designed for people who didn't know how to program. You don't need to use VBScript though, by default you can also use JScript with WSH if you prefer the syntax. http://en.wikipedia.org/wiki/Windows_Script_Host

Share this post


Link to post
Share on other sites
If I an understanding the documentation correctly (which is not very straightforward with Microsoft documention), then this would send Alt-E: task.SendWindowMessage WM_SYSKEYDOWN, Asc("E"), &h20000000
That appears to be identical to the line "task.SendWindowMessage WM_SYSKEYDOWN, Asc("E"), &h20000000" from my originally posted code. It seems to send a "normal" E instead of Alt-E. My code is ready to run, if you wish, you can copy&paste it into a .vbs file and run it by double-clicking (open a "Command Prompt" window first). Since documentation and practice seem to differ, it seems important to try out what really works. Unfortunately, I don't have admin rights and can't install a windows message logger that comes with some development IDEs - does anyone else perhaps have one and could track what key messages are sent to CMD (and with which lParams) when hitting Alt-Space Alt-E Alt-S?Thanks in advance.
Really? You can't believe that? It's not necessarily VBScript, it's Windows Script Host you're using. VBScript is just the language you're using it with. But BASIC was developed as an easy-to-read language that could be used by non-programmers. It wasn't developed to be powerful or elegant, just easy to read. It's a wonder why so many people use it today. Wikipedia claims that in 2006 that 59% of .NET framework developers used VB.NET as their only programming language. That's a lot of "programmers" using a language that was designed for people who didn't know how to program. You don't need to use VBScript though, by default you can also use JScript with WSH if you prefer the syntax. http://en.wikipedia.org/wiki/Windows_Script_Host
You're right, it's of course not VB and its syntax but the Scripting Host that is apparently missing an ALT-key send function with a dedicated recipient task (of which the script only knows the window title). I'm more familiar with JavaScript than VB, too, but 1) I'm re-using a bunch of already working VB code for the "real" project, and 2) it appears that JScript uses the same scripting host functionality, so the problem would remain in JScript.

Share this post


Link to post
Share on other sites

Right, using a different language isn't going to let you use the Windows API any differently, I just find several things to be quicker and easier using Javascript (working with arrays, objects, etc). If the applications you're trying to automate have a COM API then you should be able to use COM to automate them, but it sounds like you're looking for a general-purpose automation tool to send keystrokes to any background application that your script hasn't necessarily opened. I just don't think they've made something like that possible, the API they expose looks like it's trying to allow you to automate things via code as if you were typing things on a keyboard, which obviously doesn't work itself for background applications.

Share this post


Link to post
Share on other sites

Thanks, justsomeguy, for your answers (BTW: cool Picture you have, the monitor white looks like eyes of a car like Lightning McQueen on the thumbnail).

... it sounds like you're looking for a general-purpose automation tool to send keystrokes to any background application that your script hasn't necessarily opened. I just don't think they've made something like that possible, the API they expose looks like it's trying to allow you to automate things via code as if you were typing things on a keyboard, which obviously doesn't work itself for background applications.
I'm afraid you're right that they didn't intend this way of control. I just thought since sending "normal" keypresses to non-active windows works (as demonstrated by my original code that sends letters "A" and "E" to the CMD window), sending ALT keys should be possible in a similar way. Unfortunately, it seems it's not possible :-( . Maybe for security reasons (which would be funny since you can do many kinds of other stuff with tasks such as activating, deleting, etc.). I would still be glad if someone with access to a window message tracking tool (such as Spy++ I think) could trace the WM_***KEY*** and WM_***CHAR*** messages that Command Prompt receives when the user presses Alt-Space Alt-A Alt-E in sequence, including their wParams and lParams, and post the result here.Thanks in advance.

Share this post


Link to post
Share on other sites

P.S.:I found an interesting forum thread athttp://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/821a5bd3-665c-414e-91c6-c9415dd67bacwhere the last post reads:

> So based on your description, the messages are posted to the target inactive window successfully, but it does not have the expected effect, right? For the certain keyboard combination, actually each app has its own logic to handle it. In your scenario, maybe these apps won't handle the Ctrl + Z hot key when the window is inactive. [...]As you said. The messages are posted to the target window successfully, but it does not have the expected effect when the window is inactive. If the window is active, it work. So I think the app get state of key is simulate in keyboard-input table. I think keyboard-input table just have one, only active window can get or set state for it. And when another app is active, the keyborad-input table is control by it.So, if we want send key-combine to inactive window (in this case) is impossible.
Similar, the top post athttp://blogs.msdn.com/b/oldnewthing/archive/2005/05/30/423202.aspxsays:
Second, even if you manage to post the input messages into the target window's queue, that doesn't update the keyboard shift states. When the code behind the window calls the GetKeyState function or the GetAsyncKeyState function, it's going to see the "real" shift state and not the fake state that your posted messages have generated.The SendInput function was designed for injecting input into Windows. If you use that function, then at least the shift states will be reported correctly.
(The SendInput function goes to the active window.) That may be a reason why all my attempts failed:The receiving app perhaps knows or finds out that it's not active and therefore ignores Alt key sequences.However, I'm not sure whether this is the real reason because in the code of my original post above, " task.Activate" is called (just for testing) before the SM_SYSKEYDOWN etc. messages are sent. VBRookie

Share this post


Link to post
Share on other sites

I got Alt-F working with Notepad, so the File Menu is dropped down, after changing the bottom of my original code like this:

if getAppTask("Untitled - Notepad", task) then task.Activate    task.SendWindowMessage WM_SYSKEYDOWN, VK_MENU, &h20380001    task.SendWindowMessage WM_SYSKEYDOWN, Asc("F"), &h20000001    task.SendWindowMessage WM_SYSCHAR, Asc("F"), &h20000001    task.SendWindowMessage WM_SYSKEYUP, Asc("F"), &hE0000001    task.SendWindowMessage WM_SYSKEYUP, VK_MENU, &hC0380001    task.SendWindowMessage WM_SYSKEYDOWN, VK_MENU, &h20380001    task.SendWindowMessage WM_SYSKEYDOWN, Asc("O"), &h201E0001    task.SendWindowMessage WM_SYSCHAR, Asc("O"), &h201E0001    task.SendWindowMessage WM_SYSKEYUP, Asc("O"), &hE0000001    task.SendWindowMessage WM_SYSKEYUP, VK_MENU, &hC0380001end ifif getAppTask("Command Prompt", task) then task.Activate    task.SendWindowMessage WM_SYSKEYDOWN, VK_MENU, &h20380001    task.SendWindowMessage WM_SYSKEYDOWN, Asc(" "), &h20200001    task.SendWindowMessage WM_SYSCHAR, Asc(" "), &h20200001    task.SendWindowMessage WM_SYSKEYUP, Asc(" "), &hE0200001    task.SendWindowMessage WM_SYSKEYDOWN, Asc("E"), &h20000001    task.SendWindowMessage WM_SYSCHAR, Asc("E"), &h20000001    task.SendWindowMessage WM_SYSKEYUP, Asc("E"), &hE0000001    task.SendWindowMessage WM_SYSKEYDOWN, Asc("S"), &h20000001    task.SendWindowMessage WM_SYSCHAR, Asc("S"), &h20000001    task.SendWindowMessage WM_SYSKEYUP, Asc("S"), &hE0000001    task.SendWindowMessage WM_SYSKEYUP, VK_MENU, &hC0380001end if

However,

  • only the Alt-F click is received, then it waits until I do some action, and the subsequently sent Alt-O is mysteriously lost;
  • it does NOT work when the Notepad window is not active (which probably means that I can forget about the whole idea of sending Alt-Keys to non-active windows because they will just ignore them).

CMD still only receives "normal" key presses instead of Alt-Combinations (albeit also when inactive). I know some scan codes are not correct, but that does not seem to make much of a difference since Alt-F works in spite of that. So I guess the answer to this whole thread topic is:Forget about it, you cannot control non-active windows (at least not all of them) via Alt-key combinations. If anyone is convinced of the opposite, please post a working sample - feel free to base it on my demo code.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...