Discussion:
SendInput and desktops / window stations
(too old to reply)
Mikko Noromaa
2003-10-08 22:50:34 UTC
Permalink
Hi,

I am trying to create a Windows XP service that launches an application in a
private (hidden) desktop, and sends some keystrokes to it.

So far I have gotten this to work with the following code:

HWINSTA hWinsta=OpenWindowStation(_T("WinSta0"), FALSE, MAXIMUM_ALLOWED);
SetProcessWindowStation (hWinsta);

HDESK hDesk=OpenDesktop(_T("Default"), 0, FALSE, MAXIMUM_ALLOWED);
SetThreadDesktop (hDesk);

si.cb=sizeof(si);
si.lpDesktop=_T("WinSta0\\Default");
retval=CreateProcess(NULL, _T("D:\\Windows\\Notepad.exe"), NULL, NULL,
FALSE, 0, NULL, NULL, &si, &pi);

Sleep (2000);
// ...
// Use SendInput() to send keystrokes that write some text, then do
File/Save As, c:\a.txt and enter.
// ...
Sleep (2000);
TerminateProcess (pi.hProcess, 0);


The above version works fine from the service. Notepad starts on my active
desktop, SendInput() sends the keystrokes to it successfully, and Notepad
saves the new file to c:\a.txt.

Next I tried creating my own window station and desktop:

HWINSTA hWinsta=CreateWindowStation(_T("NsWinSta"), 0, GENERIC_ALL, NULL);
// ...
HDESK hDesk=CreateDesktop(_T("NsDesktop"), NULL, NULL, 0, GENERIC_ALL,
NULL);
// ...
si.lpDesktop=_T("NsWinSta\\NsDesktop");
// ...

All other code is the same as in the first example. When I run this code
from the service, I can see a new Notepad process being created in Task
Manager. The Notepad window does not appear on my desktop, as expected.
However, the SendInput() calls fail with error code 5 (access denied).

Is it possible to use SendInput() to send input to other desktops than the
active one? Do I need to enable some additional rights beyond GENERIC_ALL
for this to work?

--

Mikko Noromaa (***@excelsql.com)
- SQL in Excel, check out ExcelSQL! - see http://www.excelsql.com -
2003-10-09 01:06:07 UTC
Permalink
No. Keyboard input only goes to the interactive desktop. Any thread that is
not on the interactive desktop that calls SendInput will fail.
Mikko Noromaa
2003-10-09 02:05:41 UTC
Permalink
This post might be inappropriate. Click to display it.
Gary Chanson
2003-10-09 03:20:51 UTC
Permalink
Post by Mikko Noromaa
Are there any other ways of sending keystrokes to applications on other
desktops? Short of sending WM_KEYDOWN/WM_KEYUP messages directly to the
application window... My target application will be an old 16-bit Windows
program and I'm not sure if I can control it well enough with just Windows
messages. Sadly, I already had the SendInput method working quite well on
the interactive desktop. :-(
Window messages can only be sent to windows on the same desktop. If you
can send them from a thread on the desktop you create, you can send
WM_KEYDOWN and WM_KEYDOWN messages or, depending on the application, you may
be able to simply send WM_CHAR messages.

--
--

-GJC [MS Windows SDK MVP]
-Software Consultant (Embedded systems and Real Time Controls)
-***@mvps.org

-Abolish public schools
arkadyf
2003-10-09 08:17:35 UTC
Permalink
Additional way - using PlayJournal hook ( you need 16 bit hook dll for that
if you want 16 bit exe receive such )
Arkady
Post by Gary Chanson
Post by Mikko Noromaa
Are there any other ways of sending keystrokes to applications on other
desktops? Short of sending WM_KEYDOWN/WM_KEYUP messages directly to the
application window... My target application will be an old 16-bit Windows
program and I'm not sure if I can control it well enough with just Windows
messages. Sadly, I already had the SendInput method working quite well on
the interactive desktop. :-(
Window messages can only be sent to windows on the same desktop. If you
can send them from a thread on the desktop you create, you can send
WM_KEYDOWN and WM_KEYDOWN messages or, depending on the application, you may
be able to simply send WM_CHAR messages.
--
--
-GJC [MS Windows SDK MVP]
-Software Consultant (Embedded systems and Real Time Controls)
-Abolish public schools
Gary Chanson
2003-10-09 08:48:57 UTC
Permalink
Post by arkadyf
Additional way - using PlayJournal hook ( you need 16 bit hook dll for that
if you want 16 bit exe receive such )
I don't think it has to be a 16 bit DLL. I did think so until I did a
test recently using one of my own programs and found that even though it
uses a 32 bit DLL, it works with 16 bit programs on all platforms. I say "I
think" because the test did not specifically test a JournalPlay hook (I'll
have to rig up such a test when I find some free time).

--

-GJC [MS Windows SDK MVP]
-Software Consultant (Embedded systems and Real Time Controls)
-***@mvps.org


-Abolish public schools
arkadyf
2003-10-09 09:09:06 UTC
Permalink
Maybe , but that NT kernel Oses and W9x behave differently . Need to be
checked :)
Arkady
Post by Gary Chanson
Post by arkadyf
Additional way - using PlayJournal hook ( you need 16 bit hook dll for
that
Post by arkadyf
if you want 16 bit exe receive such )
I don't think it has to be a 16 bit DLL. I did think so until I did a
test recently using one of my own programs and found that even though it
uses a 32 bit DLL, it works with 16 bit programs on all platforms. I say "I
think" because the test did not specifically test a JournalPlay hook (I'll
have to rig up such a test when I find some free time).
--
-GJC [MS Windows SDK MVP]
-Software Consultant (Embedded systems and Real Time Controls)
-Abolish public schools
Gary Chanson
2003-10-09 09:18:52 UTC
Permalink
Post by arkadyf
Maybe , but that NT kernel Oses and W9x behave differently . Need to be
checked :)
I said "all platforms". I already knew a 32 bit hook DLL worked with a
16 bit program on Win9x and I knew that the same hook DLL worked with 32 bit
programs on NT/Win2K but I did not expect the 32 bit DLL to work with a 16
bit program on NT/Win2K. That's what surprised me.

--

-GJC [MS Windows SDK MVP]
-Software Consultant (Embedded systems and Real Time Controls)
-***@mvps.org


-Abolish public schools
arkadyf
2003-10-09 09:41:57 UTC
Permalink
So ntvdm do nice job in hooks too :)
Arkady
Post by Gary Chanson
Post by arkadyf
Maybe , but that NT kernel Oses and W9x behave differently . Need to be
checked :)
I said "all platforms". I already knew a 32 bit hook DLL worked with a
16 bit program on Win9x and I knew that the same hook DLL worked with 32 bit
programs on NT/Win2K but I did not expect the 32 bit DLL to work with a 16
bit program on NT/Win2K. That's what surprised me.
--
-GJC [MS Windows SDK MVP]
-Software Consultant (Embedded systems and Real Time Controls)
-Abolish public schools
Jian-Shen Lin[MS]
2003-10-09 02:16:46 UTC
Permalink
Hi Mikko,

without having the desktop in focus sendkeys doesn't work
Before every call to SendInput in your service, make sure that the
current "input desktop" is selected as the desktop for the current thread.
A
very simplified version of the code (irrelevant details, error-checking, &
cleanup code removed) looks like this:



HDESK desktop = OpenInputDesktop(0, FALSE, DESKTOP_CREATEMENU |
DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | DESKTOP_SWITCHDESKTOP |
GENERIC_WRITE);

SetThreadDesktop(desktop);

SendInput(...)


If you had considered creating a keyboard and mouse driver that your
service could communicate with to generate user input. Although this would
obviously be more work, it may be a viable alternative to using SendInput,
etc from your service. The benefit would be
that you would not have to deal with all these potential window
station/desktop issues that could arise and instead allow Windows to route
the
input to the interactive desktop.

Thanks

Jian Shen

This posting is provided "AS IS" with no warranties, and confers no rights.
Mikko Noromaa
2003-10-10 00:07:51 UTC
Permalink
Hi,
Post by Jian-Shen Lin[MS]
without having the desktop in focus sendkeys doesn't work
Before every call to SendInput in your service, make sure that the
current "input desktop" is selected as the desktop for the current thread.
Are you saying that SendInput CAN send input to a non-interactive desktop?
Another response I received said that keyboard input always goes to the
interactive desktop only.

If you check out the code I posted in my original question, you'll see that
I used SetThreadDesktop just as you suggested. My current interpretation is
that SendInput() fails with Access Denied because it is trying to send input
to the interactive desktop, but I have set the thread desktop to another
one. Thus SendInput() does not have access to the interactive desktop, and
fails. Please correct me if this is wrong!
Post by Jian-Shen Lin[MS]
If you had considered creating a keyboard and mouse driver that your
service could communicate with to generate user input. Although this would
obviously be more work, it may be a viable alternative to using SendInput,
etc from your service. The benefit would be
that you would not have to deal with all these potential window
station/desktop issues that could arise and instead allow Windows to route
the input to the interactive desktop.
If I cannot get SendInput() to work, my next course of action is to try
sending WM_KEYDOWN/WM_KEYUP/WM_CHAR messages. I hope to find a solution that
does not involve writing drivers!

--

Mikko Noromaa (***@excelsql.com)
- SQL in Excel, check out ExcelSQL! - see http://www.excelsql.com -
Mikko Noromaa
2003-10-10 12:53:10 UTC
Permalink
Hi,
I am sorry to make you confusing about this issue. By examining the
source
for SendInput on both Windows 2000 and Windows XP, I found that there are
really only two restrictions on calling SendInput that you may be running
into. The first check that SendInput performs is to determine is the
calling thread is running on the current input desktop.
Ok, I see now. As defined in the OpenInputDesktop() documentation, "The
input desktop is a desktop on the window station associated with the
logged-on user." Thus it is NOT the desktop I can set by using the
SetThreadDesktop() function! In effect, "current input desktop" ==
"interactive desktop". Is this correct?
The next check is
made to determine if the calling thread has DESKTOP_JOURNALPLAYBACK access
to the input desktop. If either check fails, SendInput will fail.
GetLastError will return 5 (ERROR_ACCESS_DENIED).
I have specified GENERIC_ALL as the access rights in my CreateDesktop()
call. I tried OR'ing DESKTOP_JOURNALPLAYBACK to this, but it had no effect.
I was doing all my tests on Windows XP.

I created a test program that exhibits the problem behaviour. It is included
at the end of this post. The test program is a console application and I was
running it with local administrative rights. If I define
USE_INTERACTIVE_DESKTOP, the program uses the interactive desktop instead of
creating a new one. This works fine. (Note that the actual keystrokes sent
to the Notepad window are made for Finnish keyboard layout - they may not
work with other layouts.)
Do you try to use DESKTOP_JOURNALPLAYBACK flag in your OpenInputDesktop?
If
it fails, the SendInput will not success.
As mentioned above, this is the problem... I am not using
OpenInputDesktop(), instead I am creating a NEW desktop!

Test program code follows:

#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <tchar.h>
#include <stdio.h>

//#define USE_INTERACTIVE_DESKTOP

static void AddKeyPress(INPUT *Inputs, int *NumInputs, int chr, int vk=0,
int UpDown=3);

void main()
{
HWINSTA hWinSta;
HDESK hDesk;
int error, retval;
STARTUPINFO si={0};
PROCESS_INFORMATION pi={0};

#ifdef USE_INTERACTIVE_DESKTOP
hWinSta=OpenWindowStation(_T("WinSta0"), FALSE, MAXIMUM_ALLOWED);
#else
hWinSta=CreateWindowStation(_T("NsWinSta"), 0, GENERIC_ALL, NULL);
#endif
error=GetLastError();
if (hWinSta==NULL) {
printf (_T("Open/CreateWindowStation() failed with error code %d.\n"),
error);
return;
}

retval=SetProcessWindowStation(hWinSta);
error=GetLastError();
if (!retval) {
printf (_T("SetProcessWindowStation() failed with error code %d.\n"),
error);
return;
}

#ifdef USE_INTERACTIVE_DESKTOP
hDesk=OpenDesktop(_T("Default"), 0, FALSE, MAXIMUM_ALLOWED);
#else
hDesk=CreateDesktop(_T("NsDesktop"), NULL, NULL, 0, GENERIC_ALL, NULL);
#endif
error=GetLastError();
if (hDesk==NULL) {
printf (_T("Open/CreateDesktop() failed with error code %d.\n"), error);
return;
}

retval=SetThreadDesktop(hDesk);
error=GetLastError();
if (!retval) {
printf (_T("SetThreadDesktop() failed with error code %d.\n"), error);
return;
}

si.cb=sizeof(si);
#ifdef USE_INTERACTIVE_DESKTOP
si.lpDesktop=_T("WinSta0\\Default");
#else
si.lpDesktop=_T("NsWinSta\\NsDesktop");
#endif
retval=CreateProcess(NULL, _T("Notepad.exe"), NULL, NULL, FALSE, 0, NULL,
NULL, &si, &pi);
error=GetLastError();
if (!retval) {
printf (_T("CreateProcess() failed with error code %d.\n"), error);
return;
}

Sleep (2000);

INPUT Inputs[256]={0};
int NumInputs=0;

AddKeyPress (Inputs, &NumInputs, 'a');
AddKeyPress (Inputs, &NumInputs, 'b');
AddKeyPress (Inputs, &NumInputs, 'c');
AddKeyPress (Inputs, &NumInputs, 0, VK_LMENU, 1);
AddKeyPress (Inputs, &NumInputs, 'f');
AddKeyPress (Inputs, &NumInputs, 0, VK_LMENU, 2);
AddKeyPress (Inputs, &NumInputs, 's');
AddKeyPress (Inputs, &NumInputs, 'C');
AddKeyPress (Inputs, &NumInputs, 0, VK_LSHIFT, 1);
AddKeyPress (Inputs, &NumInputs, ':');
AddKeyPress (Inputs, &NumInputs, 0, VK_LSHIFT, 2);
AddKeyPress (Inputs, &NumInputs, 0, VK_RMENU, 1);
AddKeyPress (Inputs, &NumInputs, '\\');
AddKeyPress (Inputs, &NumInputs, 0, VK_RMENU, 2);
AddKeyPress (Inputs, &NumInputs, 'a');
AddKeyPress (Inputs, &NumInputs, 0, VK_RETURN);

SetLastError (0);
retval=SendInput(NumInputs, Inputs, sizeof(INPUT));
error=GetLastError();
printf (_T("SendInput() returned %d and error code %d.\n"), retval, error);

Sleep (2000);
TerminateProcess (pi.hProcess, 0);
}

static void AddKeyPress(INPUT *Inputs, int *NumInputs, int chr, int vk, int
UpDown)
{
if (UpDown&1) {
memset (&Inputs[*NumInputs], 0, sizeof(Inputs[*NumInputs]));
Inputs[*NumInputs].type=INPUT_KEYBOARD;
Inputs[*NumInputs].ki.wVk=chr?VkKeyScan(chr):vk;
Inputs[*NumInputs].ki.dwFlags=0;
(*NumInputs)++;
}

if (UpDown&2) {
memset (&Inputs[*NumInputs], 0, sizeof(Inputs[*NumInputs]));
Inputs[*NumInputs].type=INPUT_KEYBOARD;
Inputs[*NumInputs].ki.wVk=chr?VkKeyScan(chr):vk;
Inputs[*NumInputs].ki.dwFlags=KEYEVENTF_KEYUP;
(*NumInputs)++;
}
}



--

Mikko Noromaa (***@excelsql.com)
- SQL in Excel, check out ExcelSQL! - see http://www.excelsql.com -
Sami Korhonen
2003-10-10 06:36:44 UTC
Permalink
Just a thought; wouldn't it be possible to client/server combination using
ie. named pipe. Server (service) runs as system service & client runs on
selected desktop. This way server may send data over named pipe to client,
which calls SendInput() to the desktop it's running at. This adds some extra
overhead, but I guess it shouldn't matter here :)

Server|Service <==> Client|Desktop <==> Application|Desktop
Post by Mikko Noromaa
Hi,
I am trying to create a Windows XP service that launches an application in a
private (hidden) desktop, and sends some keystrokes to it.
HWINSTA hWinsta=OpenWindowStation(_T("WinSta0"), FALSE, MAXIMUM_ALLOWED);
SetProcessWindowStation (hWinsta);
HDESK hDesk=OpenDesktop(_T("Default"), 0, FALSE, MAXIMUM_ALLOWED);
SetThreadDesktop (hDesk);
si.cb=sizeof(si);
si.lpDesktop=_T("WinSta0\\Default");
retval=CreateProcess(NULL, _T("D:\\Windows\\Notepad.exe"), NULL, NULL,
FALSE, 0, NULL, NULL, &si, &pi);
Sleep (2000);
// ...
// Use SendInput() to send keystrokes that write some text, then do
File/Save As, c:\a.txt and enter.
// ...
Sleep (2000);
TerminateProcess (pi.hProcess, 0);
The above version works fine from the service. Notepad starts on my active
desktop, SendInput() sends the keystrokes to it successfully, and Notepad
saves the new file to c:\a.txt.
HWINSTA hWinsta=CreateWindowStation(_T("NsWinSta"), 0, GENERIC_ALL, NULL);
// ...
HDESK hDesk=CreateDesktop(_T("NsDesktop"), NULL, NULL, 0, GENERIC_ALL,
NULL);
// ...
si.lpDesktop=_T("NsWinSta\\NsDesktop");
// ...
All other code is the same as in the first example. When I run this code
from the service, I can see a new Notepad process being created in Task
Manager. The Notepad window does not appear on my desktop, as expected.
However, the SendInput() calls fail with error code 5 (access denied).
Is it possible to use SendInput() to send input to other desktops than the
active one? Do I need to enable some additional rights beyond GENERIC_ALL
for this to work?
--
- SQL in Excel, check out ExcelSQL! - see http://www.excelsql.com -
Mikko Noromaa
2003-10-10 12:58:03 UTC
Permalink
Hi,
Post by Sami Korhonen
Just a thought; wouldn't it be possible to client/server combination using
ie. named pipe. Server (service) runs as system service & client runs on
selected desktop. This way server may send data over named pipe to client,
which calls SendInput() to the desktop it's running at.
Hmm, you mean a local application running on a hidden desktop could use
SendInput() ? This is definitely worth checking out, however from Jian-Shen
Lin's post I would assume it doesn't work. If the SendInput() function
checks whether the calling thread is running on the current input desktop, a
application local to the desktop is probably bound by the same restriction.

--

Mikko Noromaa (***@excelsql.com)
- SQL in Excel, check out ExcelSQL! - see http://www.excelsql.com -
Loading...