14

In Windows Vista/7/2008/2008R2, is it at all possible to launch a process in a user's session from a service? Specifically, the local session would be most useful.

Everything I've been reading seems to say this isn't possible, but I figured I'd ask here before giving up completely.

I'm coding in VB.NET, but will take suggestions in anything.

Brad
  • 159,648
  • 54
  • 349
  • 530
  • The local interactive user is what I'm focusing on. I'm mainly interested in locking the workstation from the service, but have a need to execute other programs as well, depending on certain conditions. – Brad Jun 27 '10 at 18:13

5 Answers5

23

It is really possible. The main problem which you have is that Windows should be seen as a terminal server and a users session as a remote session. Your service should be able to start a process which run in the remote session belongs to the user.

By the way, if you write a service which run under Windows XP which is not added to a domain and the fast user switching is activated, you can have the same problems to start a process on running on the second (third and so on) logged users desktop.

I hope you have a user token, which you receive for example with respect of impersonation or you have a dwSessionId of session. If you don't have it you can try use some WTS-function (Remote Desktop Services API http://msdn.microsoft.com/en-us/library/aa383464.aspx, for example WTSEnumerateProcesses or WTSGetActiveConsoleSessionId) or LSA-API to find out the corresponding users session (LsaEnumerateLogonSessions see http://msdn.microsoft.com/en-us/library/aa378275.aspx and LsaGetLogonSessionData see http://msdn.microsoft.com/en-us/library/aa378290.aspx) or ProcessIdToSessionId (see http://msdn.microsoft.com/en-us/library/aa382990.aspx).

You can use GetTokenInformation function with the parameter TokenSessionId (see http://msdn.microsoft.com/en-us/library/aa446671.aspx) to receive the session id dwSessionId of the users session if you knows the users token hClient.

BOOL bSuccess;
HANDLE hProcessToken = NULL, hNewProcessToken = NULL;
DWORD dwSessionId, cbReturnLength;

bSuccess = GetTokenInformation (hClient, TokenSessionId, &dwSessionId,
                                sizeof(DWORD), &cbReturnLength);
bSuccess = OpenProcessToken (GetCurrentProcess(), MAXIMUM_ALLOWED, &hProcessToken);
bSuccess = DuplicateTokenEx (hProcessToken, MAXIMUM_ALLOWED, NULL,
                             SecurityImpersonation,
                             TokenPrimary, &hNewProcessToken);
EnablePrivilege (SE_TCB_NAME);
bSuccess = SetTokenInformation (hNewProcessToken, TokenSessionId, &dwSessionId,
                                sizeof(DWORD));
bSuccess = CreateProcessAsUser (hNewProcessToken, NULL, szCommandToExecute, ...);

This code only a schema. EnablePrivilege is a simple function used AdjustTokenPrivileges to enable SE_TCB_NAME privilege (see http://msdn.microsoft.com/en-us/library/aa446619.aspx as a template). It is important that the process from which you are start a process you have TCB privilege, but if your service run under the Local System you have enough permissions. By the way, following code fragment work with not only Local System account, but the account must have SE_TCB_NAME privilege to be able to switch current terminal server session.

One more remark. In the code above we start new process with the same account as the current process have (for example Local System). You change change a code to use another account for example the users token hClient. It is only important to have a primary token. If you have an impersonation token you can convert it to the primary token exactly like in the code above.

In the STARTUPINFO structure used in CreateProcessAsUser you should use lpDesktop = WinSta0\Default".

Depend on your requirements it could be also needed to use CreateEnvironmentBlock to create an new environment block that you will be passing to the new process.

I recommend you also to read How to ensure process window launched by Process.Start(ProcessStartInfo) has focus of all Forms? where I describe how to force that the process will be started in foreground on the users desktop.

Community
  • 1
  • 1
Oleg
  • 220,925
  • 34
  • 403
  • 798
  • I've just been working through an issue I was having with a service that is doing this, and found your answer here... Very helpful. In my case, I was getting an annoying "access denied" error when I ran a file-open dialog from my spawned process (otherwise the dialog worked fine). Calling `CreateEnvironmentBlock` and passing the result to `CreateProcessAsUser` seems to have fixed it. Do I really need to set that `lpDesktop` value of my `STARTUPINFO` struct? I just left it as NULL. Also, it worked without setting that SE_TCB_NAME privilege. – paddy Sep 19 '12 at 05:13
  • @paddy: Sorry, but all the steps are really required if you have some common case. If the service run under some user account and not as System you really have to set `TokenSessionId` to enable the interactive user to see the windows of the started process. To be able to use `SetTokenInformation` with `TokenSessionId` the service account really need to have `SE_TCB_NAME` privilege and have to enable it. If the Screen Saver work at the time of process starting one should set `lpDesktop` to spectfy either the desktop of screen saver or default user desktop. – Oleg Sep 19 '12 at 05:34
6

If it helps, i was faced with a similar problem, but wanted a pure powershell solution.

I cobbled together bits from other websites and came up with this:

function Invoke-CommandInSession 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ScriptBlock] $expression
    )

    $commandLine = “powershell“

    ## Convert the command into an encoded command for PowerShell
    $commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)
    $encodedCommand = [Convert]::ToBase64String($commandBytes)
    $args = “-Output XML -EncodedCommand $encodedCommand”


    $action = New-ScheduledTaskAction -Execute $commandLine -Argument $args
    $setting = New-ScheduledTaskSettingsSet -DeleteExpiredTaskAfter ([Timespan]::Zero)
    $trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date) + ([Timespan]::FromSeconds(5)))
    $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $setting
    Register-ScheduledTask "Invoke-CommandInSession - $([Guid]::NewGuid())" -InputObject $task
}

Yes, its a little weird, but it works - and most importantly you can call it from within ps remoting

Steve
  • 976
  • 12
  • 12
3

Yes, using CreateProcessAsUser you can do this. MSDN has sample articles, and there are some caveats.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
holtavolt
  • 4,378
  • 1
  • 26
  • 40
3

It can be done but its not considered good practice for services to interact directly with users' sessions as it can create serious security holes.

http://support.microsoft.com/kb/327618

A better approach is to create 2 programs, a backend service and a front end client UI program. The service backend runs all the time and exposes its operations using WCF (for instance). The client program could be run at startup of a user's session.

Bear Monkey
  • 513
  • 2
  • 9
  • I don't believe the contents of the article you linked are still valid for 2008(r2). Session null isolation et al ... –  Jun 27 '10 at 17:56
  • 1
    That's not quite the same thing. Brad is asking about creating a /new/ process with the user's credentials, not performing interactive operations in the user's session as the service process. – Warren Rumak Jun 27 '10 at 17:56
  • 1
    Launching processes in a user's session is still interacting with a user's desktop. Lets not split hairs its still not good practice. Link wasnt the best i agree :) – Bear Monkey Jun 27 '10 at 18:01
  • Yes, my program is actually designed this way. There is a client application that uses remoting to communicate with the service over named pipes. I need to find a way to lock the workstation should someone stop the client process, for security reasons. I totally understand that interacting with the user's session is poor in general... this is slightly different. Thank you though for your response! – Brad Jun 30 '10 at 21:30
1

Subverting Vista UAC in Both 32 and 64 bit Architectures

Code provided is targeted to Vista, but works on Win7 and Win10 too

tchelidze
  • 8,050
  • 1
  • 29
  • 49