OpenInputDesktop() to determine Secure/Login desktop - windows-vista

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.

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.

Launch a winform application from a windows service

Please let me know how do I run the app under current logged in user from the service.
To give you background, I have a VB.NET Windows service whose sole functionality is to run a Winform App at a specified time. Apart from that it also sets a system wakeup timer so that the system can be woken up at the specified time, if it goes into standby/sleep, to run the app. This service has to cater to XP/Vista/Win7 desktops on our network. This service won't run on servers and laptops.
The Winform App shows a UI for the user to provide some inputs. If the user does not provide the input within 15 minutes, then it defaults the value and then goes into system tray icon. The user can click on the icon and change the values later (within in a specified time frame and that too only twice).
There is absolutely no interaction between the service and the winform app apart from the service starting the app. It also monitors if the app has been killed by the user/crashed. If it has been killed/crashed, then a new instance is run after 30 mins from previous run.
If there is no user logged on, then also I want the app to be run at the specified time. As I said before, the app has a default timer. So if some user has just logged off from the system, then defaults would be set by the winform app.
Now coming to why I am stuck with this design - I cannot use TaskScheduler because it has been disabled on all machines and security team is not willing to change it. TaskScheduler had the option to wakeup the machine from sleep and other things. So basically I ended up creating a service which is acting like task scheduler.
Currently when I run the app.exe via process.start() within the service, its running under SYSTEM account as the service is also running under LOCAL SYSTEM. So basically I am not getting any UI. Is there anyway to run it under the current logged in user? I am not worried about multiple user login as we wont be running it on servers and switch user is not enabled on our desktops. Even if somebody has done a remote login via mstsc, then also I need the run the app and show the UI to the user.
Please let me know how do I run the app under current logged in user from the service.
Thanks
askids
There were some additional comments that I posted. But I somehow cannot see it :(
Coming back to the original question. I was able to figure it out after several trial and errors. I will put it in detail.
With Vista and above, services run in isolation from other user sessions. They run in session 0. User sessions run in 1 and above. So basically you need to emulate the process as current logged in user.
Use WTSEnumerateSessions and
get of sessions. Check if the sesion
state is active. This will be
current logged on user session. If
there are no active sessions, it
means there is no logged on user. In
my case, there will be only 1 logged
on user. So I need not figure out
the active session (like others may
need to do).
Use WTSQueryUserToken to get the user token in the active session.
Create a primary user token using DuplicateTokenEx
Create an environment using CreateEnvironmentBlock
Use the information above in the CreateProcessAsUser
The reason why it was working in XP and not in Vista was because it looks like the startup default information is different. After I set wShowWindow flag of the startupinfo structure, the GUI would start appearing.
Dim StartupInfo As New STARTUPINFO()
StartupInfo.cb = Marshal.SizeOf(StartupInfo)
StartupInfo.dwFlags = STARTF_USESHOWWINDOW
StartupInfo.wShowWindow = WINDOW_STATUS.SW_SHOWNORMAL
One more additional info. I was trying to set the default desktop using
StartupInfo.lpDesktop = "WinSta0\\Default"
because of which the application would crash upon launch. So I commented it out.
I still have one final issue. The launched app is not in focus. The GUI appears, but in background. But I am thinking, it will once again have to do with some parameters like above. Once I figure it out, I will add in the details.

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.

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

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.

Resources