How can I enumerate the open windows (~EnumWindows) of another user session - windows

I have an application that can list the opened windows of the current session. It uses the EnumWindows method from the user32.dll.
I would like to run this code from a windows service, but as the service is not attached to a user session, it returns nothing obviously.
So the question is, how can I enumerate the open windows of another user session (e.g. with a specific logon user)?
Similarly to EnumWindows, I also would like to get the foreground window of the user session as well (like GetForegroundWindow works for the current user).

As far as I'm aware, you can't access the windows of one session from another. It's also worth noting that there's not really any such thing as "the current session" - there may be multiple users logged on through terminal services, or XP's fast user switching.
One approach to this would be to add a program to each user's profile with no UI that just communicates with your service. You'd still have to cope with the fact that there could be multiple active sessions, though.

According to this document you can create a process in an other user's logon session using CreateProcessAsUser, and could enumerate the windows there. You will still need some IPC mechanism to communicate with the service.

The accepted answer is not correct.
So the question is, how can I enumerate the open windows of another user session?
You can enumerate the open windows of any session if you're running as a service running as the local System account.
To do this first enumerate the sessions with WTSEnumerateSessions. Then enumerate the window stations inside each session with EnumWindowStations. Then enumerate the desktops for each Window Station with EnumDesktops. Finally you an enumerate the Windows in those Desktops with EnumWindows.
(e.g. with a specific logon user)
There can be many concurrent logged on users via Terminal services or fast user switching.
Similarly to EnumWindows, I also would like to get the foreground window of the user session as well (like GetForegroundWindow works for the current user).
This can be done by launching an app with a found user token in the Session, Window Station, and Desktop. From there you can call any Win32 API like GetForegroundWindow and report the info back to your parent process.
You can learn more about how Sessions, Window Stations, and Desktops work here.

Related

Windows - tray icon to all users?

Is it allowed to have some process's icon to be displayed in all user sessions including terminal/multiple local logons, without spawning another process per each session? I don't need to share any windows between session, just the status icon to be able to check service's status without taking additional actions..
It's not even possible. Shell_NotifyIcon communicates with a single instance of Explorer.EXE, the one running in the current user context.
Processes can only access the interactive window station and desktops in the same session as the process. So this means you need one process per session.
I know at least one program that do it - TightVNC server running as a service. You can lookup their sources to understandhow it's done.

Is it possible to see system's user desktop? How to desktop of your user from another session?

I run Windows 7.
I run windows service that runs a program with GUI.
I cannot see the GUI of my program because it was started from another session by system or even my user.
Is there a way for me to see my program?
Switch desktop to system user?
Use SetThreadDesktop() to change the thread's context in your service to the user's desktop.
SetThreadDesktop() takes a handle to the desktop as it's first parameter; to get that handle, use EnumDesktops().
EnumDesktops() takes a handle to the window station as it's first parameter; to get that handle, use EnumWindowStations()
To understand what's going on with Window Stations and Desktops, try reading this overview from from MSDN.
Be cautious with this technique. Higher-privileged processes (i.e., services) interacting with the user's desktop are the basis for shatter attacks. You need to write a separate application that runs in the user's context and communicates with your service via pipes or similar.

Sessions, Window stations and Desktops

Sessions, Window Stations and Desktops.
A session consists of all process and
other system objects that represent a
single user's logon session. Sessions
contain Window Stations and Window
Stations contain desktops.
The above is from http://blogs.technet.com/b/askperf/archive/2007/07/24/sessions-desktops-and-windows-stations.aspx
(Similar articles say the same thing, e.g. blogs.technet.com/b/askperf/archive/2007/04/27/application-compatibility-session-0-isolation.aspx and blogs.msdn.com/b/ntdebugging/archive/2007/01/04/desktop-heap-overview.aspx)
I've always understood Sessions and logon sessions to be one and the same thing.
However, reading p. 474, Russinovich and Solomon, Windows Internals, 5th edition, it says (penultimate paragraph):
The CreateProcessWithLogon function
also creates a token by creating a new
logon session with an initial process,
which is how the Runas command
launches processes under alternative
tokens.
So Runas creates a new logon session. If we use Runas to run Notepad under a different user's credentials we see it appear on the desktop. So Notepad is running on the same desktop as everything else. (This is, as far as I understand it, the default desktop in Window Station Winsta0; the interactive window station.) So what we have now are two logon sessions associated with the same Session (the thing that contains window stations). So Sessions and logon sessions would not appear to be one and the same.
Can anybody confirm this please?
Confused
There are indeed two types of sessions here. Here's my understanding of how they work.
A logon session[2] is managed by the Local Security Authority[2], and manages the scope of a user logon. These are created primarily by the winlogon process, but also secondarily by API functions such as LogonUser, CreateProcessAsUser, and CreateProcessWithLogonW, or by applications that use these functions, such as the runas command.
A logon session isn't tied to any specific Object Manager[2] concepts as window stations and desktops. It's basically just a block of information containing the logon SID and some cached security information about the account. This block of information, this logon session, is what an access token points to.
The other type of session is sometimes called a Terminal Services session, Terminal Server session, Remote Desktop session, logon session (as confusing as that is), user logon session, or user session. Usually, though, it's just called a "session", without further qualification.
This is the type of session that you'll usually hear about, and is what window stations belong to. This type of session came about to support multiple interactive GUI logons, as provided by Terminal Services (now known as Remote Desktop), and is now also used to support Fast User Switching. Sessions provide the necessary isolation between the Object Manager objects associated with each user logon.
I don't spend much time messing with all this stuff, so I'm a little fuzzy on some of the details, but I think this is a pretty fair representation of the overall picture. I hope it clears things up a bit.

OpenInputDesktop() to determine Secure/Login desktop

Visual C++ 2008
How to establish whether or not the currently interactive user is at either the Locked desktop (Windows-Key L) or the Shutdown Screen (Vista or 7) waiting for programs to close during log-out.
HDESK hd = OpenInputDesktop(0, false, READ_CONTROL);
This works fine for a user app on the Default desktop, but fails with error Code 5 when at the Locked or Shutdown desktops due to, I understand, the user not having permissions to open the secure desktop object.
Calling this from a Service running under the SYSTEM account returns error 1 (invalid function). I believe that the Service is in any case in the wrong session (Session 0) and is unable to determine the interactive desktop for any other session.
I have an app running under the currently interactive user, and also, the system service running, so could do the code from either.
Should I perhaps attempt to enumerate all of the sessions, Window stations and desktops?
Even then how do I determine the currently interactive desktop if I can only make the call to OpenInputDesktop from the SYSTEM service in session 0?
I think you can try these methods:
From a process running in currently interactive user:
Use WTSRegisterSessionNotification to register for session change notifications. Once registered, interactive process would get logon/logoff notifications. More info can be found here:
http://msdn.microsoft.com/en-us/library/aa383841.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2006/01/04/509194.aspx
From a service (running in session 0):
Use GetProcessWindowStation to get the current station handle of service, and save it for later use.
Use WTSGetActiveConsoleSessionId to get session id of current interactive session.
Get station name corresponding to current session id using WTSQuerySessionInformation with WTSWinStationName info class.
Open this station using OpenWindowStation. Set this station to your service process using SetProcessWindowStation.
Now, you can use OpenInputDesktop to check if user has logged-in or not.
Close the opened interactive window station by calling CloseWindowStation. Reset the original window station of service by calling SetProcessWindowStation with station handle saved earlier.
PS: Currently, "WinSta0" is the only interactive station in Windows. So, you can skip WTSGetActiveConsoleSessionId and WTSQuerySessionInformation calls.
Beware:
MSDN about WTSQuerySessionInformation with WTSWinStationName:
Note: Despite its name, specifying this type does not return the window station name. Rather, it returns the name of the Remote Desktop Services session. Each Remote Desktop Services session is associated with an interactive window station. Currently, since the only supported window station name for an interactive window station is "WinSta0", each session is associated with its own "WinSta0" window station. For more information, see Window Stations.

Get Window Station for a non-interactive user per process, user or session?

When using CreateProcessAsUser we pass STARTUPINFO and with lpDesktop NULL, the target is winsta0/default, the interactive desktop of the interactive user.
I wish to target a window station in another session of a second, non-interactive user, say a remote desktop user.
I assume that it can't be winsta0 because that's reserved for the single interactive user.
I am looking at the function list here:
http://msdn.microsoft.com/en-us/library/ms687107(v=VS.85).aspx
I can enumerate window stations on the machine, but how do I identify which window station is connected to which user/process/session?
Each window station is connected to a session
Each process has a target window station
But how, for example if I have a process, or a session ID, do I determine which Window Station it is associated with?
On WinXP/Win2K3 or higher, you could do the following:
Call WTSEnumerateSessions to get a list of active session information (which will also give you the window station name associated to each session).
Pass the session id to WTSQueryUserToken.
Pass the token to GetTokenInformation to get the user's SID.
Pass the user's SID to LsaLookupSids to get the user and domain names.
Also, if you want to identify which session is the active console session, you can compare the session id to the return value of WTSGetActiveConsoleSessionId.
However, I would recommend using the token returned from WTSQueryUserToken to launch a process on the target desktop via CreateProcessAsUser, as Franci mentioned. You'll have to pass it through DuplicateTokenEx to convert it from an impersonation token to a primary token, but it works on WinXP or higher, and Microsoft documents it as the "preferred" way to launch interactive applications from the services desktop on Vista and higher.
You can use GetUserObjectinformation to get the SID of the user associated with that window station.
As for finding the Window Station from a process:
- Get the top-level window handle for the process
- Enumerate the window stations (EnumWindowStations)
- Enumerate the desktops for each window station (EnumDesktops)
- Enumerate the windows for each desktop (EnumDesktopWindows) until you find a match.
Yeah, it's not a straightforward, but it should solve your problem.
Note: On Vista and Win7, the interactive user is not in winsta0. Winsta0 is reserved for the system and services only, the interactive user gets a new windows station and is treated the (mostly) same way as a TS users.

Resources