How to start a process from windows service into currently logged in user's session
Asked Answered
L

8

62

I need to start a program from Windows Service. That program is a user UI application. Moreover that application should be started under specific user account.

The problem is that a Window Services run in session #0, but a logged in user sessions are 1,2 etc.

So the question is: how to start a process from a window service in such a way that it run in currently logged in user's session?

I'd emphasis on that the question is not about how to start a process under specific account (it's obvious - Process.Start(new ProcessStartInfo("..") { UserName=..,Password=..})). Even if I install my windows to run under current user account the service will run in session #0 anyway. Setting "Allow service to interact with desktop" doesn't help.

My windows service is .net-based.

UPDATE: first of all, .NET has nothing to do here, it's actually pure Win32 thing. Here's what I'm doing. The following code is in my windows service (C# using win32 function via P/Inkove, I skipped import signatures, they're all here - http://www.pinvoke.net/default.aspx/advapi32/CreateProcessWithLogonW.html):

    var startupInfo = new StartupInfo()
        {
            lpDesktop = "WinSta0\\Default",
            cb = Marshal.SizeOf(typeof(StartupInfo)),
        };
    var processInfo = new ProcessInformation();
    string command = @"c:\windows\Notepad.exe";
    string user = "Administrator";
    string password = "password";
    string currentDirectory = System.IO.Directory.GetCurrentDirectory();
    try
    {
        bool bRes = CreateProcessWithLogonW(user, null, password, 0,
            command, command, 0,
            Convert.ToUInt32(0),
            currentDirectory, ref startupInfo, out processInfo);
        if (!bRes)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
    catch (Exception ex)
    {
        writeToEventLog(ex);
        return;
    }
    WaitForSingleObject(processInfo.hProcess, Convert.ToUInt32(0xFFFFFFF));
    UInt32 exitCode = Convert.ToUInt32(123456);
    GetExitCodeProcess(processInfo.hProcess, ref exitCode);
    writeToEventLog("Notepad has been started by WatchdogService. Exitcode: " + exitCode);

    CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);

The code goes to the line "Notepad has been started by WatchdogService. Exitcode: " + exitCode. Exitcode is 3221225794. And there's no any new notepad started. Where am I wrong?

Leandra answered 25/11, 2010 at 14:59 Comment(0)
L
36

A blog on MSDN describes a solution

It's a terrific helpful post about starting a new process in interactive session from windows service on Vista/7.

For non-LocalSystem services, the basic idea is:

  • Enumerate the process to get the handle of the Explorer.

  • OpenProcessToken should give you the access token. Note : The account under which your service is running must have appropriate privileges to call this API and get process token.

  • Once you have the token, call CreateProcessAsUser with this token. This token already has the right session Id.

Leandra answered 26/11, 2010 at 10:25 Comment(1)
The link is broken. Can you show what you did? I'm working on a similar WatchDog app and not able to get past this issue. Thanks!Cozenage
A
52

The problem with Shrike's answer is that it does not work with a user connected over RDP.
Here is my solution, which properly determines the current user's session before creating the process. It has been tested to work on XP and 7.

https://github.com/murrayju/CreateProcessAsUser

Everything you need is wrapped up into a single .NET class with a static method:

public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible)
Allpurpose answered 9/6, 2014 at 14:49 Comment(11)
What if there are two active RDP sessions at the same time? Or an RDP session and a local session?Farina
@zespri The code assumes that there is only one active session, so it effectively chooses one at random. I doubt that the WTSEnumerateSessions api makes any guarantees about ordering, but the code will choose the last active session in the list. I don't think there is any way for you to know which session is the "correct" one, but if there is, you could just modify that for loop. I assume this is on a server OS?Allpurpose
Excellent. Just what I needed and was looking for. Works a treat.General
Just a note that the process id for metro apps in win 10 is different to what the procInfo.dwProcessId reportsStolzer
I've tried this and I'm having a GetUserSessionToken failed. I wonder if it is any change to windows APIs...Brann
This works great, except when the process needs elevated privilegies. How can we start the process with the highest privilegies from WinService?Charleycharlie
It worked..... Only thing seems misplaced parameters in DuplicateTokenEx method for TokenType vs ImperssionationLevelNinnyhammer
@RonakPatel if you are saying that there is a bug in the code, please make a github issue and/or pull request with as much detail as possible!Allpurpose
It would be better practice to at least outline the code here, rather than relying on the link to live forever.Coseismal
It's March 2021 and this does not work anymore: GetSessionUserToken failed and no updates in years. Too bad - I could use it!Outofdate
@Daniel Williams, It does work, but it only works when ran with Service User credentials. Only with service credentials can you get the user session token correctly. I just tested it successfully in a small dummy service I set up to do it in.Khaki
L
36

A blog on MSDN describes a solution

It's a terrific helpful post about starting a new process in interactive session from windows service on Vista/7.

For non-LocalSystem services, the basic idea is:

  • Enumerate the process to get the handle of the Explorer.

  • OpenProcessToken should give you the access token. Note : The account under which your service is running must have appropriate privileges to call this API and get process token.

  • Once you have the token, call CreateProcessAsUser with this token. This token already has the right session Id.

Leandra answered 26/11, 2010 at 10:25 Comment(1)
The link is broken. Can you show what you did? I'm working on a similar WatchDog app and not able to get past this issue. Thanks!Cozenage
P
10

It's a bad idea to do so. Although probably not totally impossible, Microsoft did everything to make this as hard as possible, as it enables so-called Shatter Attacks. See what Larry Osterman wrote about it back in 2005:

The primary reason for this being a bad idea is that interactive services enable a class of threats known as "Shatter" attacks (because they "shatter windows", I believe).

If you do a search for "shatter attack", you can see some details of how these security threats work. Microsoft also published KB article 327618 which extends the documentation about interactive services, and Michael Howard wrote an article about interactive services for the MSDN Library. Initially the shatter attacks went after windows components that had background window message pumps (which have long been fixed), but they've also been used to attack 3rd party services that pop up UI.

The second reason it's a bad idea is that the SERVICE_INTERACTIVE_PROCESS flag simply doesn't work correctly. The service UI pops up in the system session (normally session 0). If, on the other hand, the user is running in another session, the user never sees the UI. There are two main scenarios that have a user connecting in another session - Terminal Services, and Fast User Switching. TS isn't that common, but in home scenarios where there are multiple people using a single computer, FUS is often enabled (we have 4 people logged in pretty much all the time on the computer in our kitchen, for example).

The third reason that interactive services is a bad idea is that interactive services aren't guaranteed to work with Windows Vista :) As a part of the security hardening process that went into Windows Vista, interactive users log onto sessions other than the system session - the first interactive user runs in session 1, not session 0. This has the effect of totally cutting shatter attacks off at the knees - user apps can't interact with high privilege windows running in services.

The proposed workaround would be to use an application in the system tray of the user.

If you can safely ignore the issues and warnings above, you might follow the instructions given here:

Developing for Windows: Session 0 Isolation

Pall answered 25/11, 2010 at 15:6 Comment(4)
I don't want my service to be interactive (showing any UI), I only want it to be able to start another process (in user's session). This is a bit a different case, isn't?Leandra
@Shrike: If you don't want to show a UI then why is it not sufficient to start that process in session 0? I think you need to add more details about why that process requires an interactive session and why the suggested workaround is not suitable for you.Pall
There're two actors: windows-service and UI-program. The server itseft doesn't show UI, it just start the program (as a new process). The program has UI (and requires session 1). My service is kind of watchdog, periodically "pings" the program and start/restart it if something wrong. I'm adding some clarification into original postLeandra
Thanks for the link to very interesting post. But it doesn't help as just refers to "use CreateProcessAsUser". I know this, see my code sample. Created process gets into session #0 instead of interactive, I don't known how to fix it.Leandra
R
7

I found the solution in here:

http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

I think this is a great link.

Ruse answered 25/6, 2013 at 21:42 Comment(2)
I think this is the accepted solution but thoroughly explained. Much better.Ethelda
Although this process works with Windows 10, it doesn't appear to work with Windows Server 2016Bull
S
6

Here is how I implemented it. It will try to start a process as the currently logged-in user (from a service). This is based on several sources put together to something that works.

It is actually REALLY PURE WIN32/C++, so might not be 100% helpful to the original questions. But I hope it can save other people some time looking for something similar.

It requires Windows XP/2003 (doesn't work with Windows 2000). You must link to Wtsapi32.lib

#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <WtsApi32.h>

bool StartInteractiveProcess(LPTSTR cmd, LPCTSTR cmdDir) {
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.lpDesktop = TEXT("winsta0\\default");  // Use the default desktop for GUIs
    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));
    HANDLE token;
    DWORD sessionId = ::WTSGetActiveConsoleSessionId();
    if (sessionId==0xffffffff)  // Noone is logged-in
        return false;
    // This only works if the current user is the system-account (we are probably a Windows-Service)
    HANDLE dummy;
    if (::WTSQueryUserToken(sessionId, &dummy)) {
        if (!::DuplicateTokenEx(dummy, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &token)) {
            ::CloseHandle(dummy);
            return false;
        }
        ::CloseHandle(dummy);
        // Create process for user with desktop
        if (!::CreateProcessAsUser(token, NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) {  // The "new console" is necessary. Otherwise the process can hang our main process
            ::CloseHandle(token);
            return false;
        }
        ::CloseHandle(token);
    }
    // Create process for current user
    else if (!::CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi))  // The "new console" is necessary. Otherwise the process can hang our main process
        return false;
    // The following commented lines can be used to wait for the process to exit and terminate it
    //::WaitForSingleObject(pi.hProcess, INFINITE);
    //::TerminateProcess(pi.hProcess, 0);
    ::CloseHandle(pi.hProcess);
    ::CloseHandle(pi.hThread);
    return true;
}
Sands answered 14/10, 2014 at 13:7 Comment(1)
I just tried to build this code, but I get "\HelloWorld/helloworld.cpp:22: undefined reference to `WTSQueryUserToken' collect2.exe: error: ld returned 1 exit status Build finished with error(s)." What could be wrong? Using Windows 10, g++.exe (Rev10, Built by MSYS2 project) 12.2.0Inhabitant
F
6

Implemented @murrayju code into a Windows Service on W10. Running an executable from Program Files always threw Error -2 in VS. I believe it was due to Service's start path set to System32. Specifying workDir didn't fix the issue until I added the following line before CreateProcessAsUser in StartProcessAsCurrentUser:

if (workDir != null)
   Directory.SetCurrentDirectory(workDir);

PS: This should have been a comment rather than an answer, but I don't have the required reputation yet. Took me a while to debug, hope it saves someone time.

Frantic answered 5/11, 2017 at 14:15 Comment(1)
Are you intending for this to be an updated answer? If so, just add the relevant details and post as such. It looks like more than a comment to me, but I don't want to take the time to figure out the whole issue.Emileemilee
R
2

I don't know how to do it in .NET, but in general you would need to use the Win32 API CreateProcessAsUser() function (or whatever its .NET equivilent is), specifying the desired user's access token and desktop name. This is what I use in my C++ services and it works fine.

Repartition answered 25/11, 2010 at 17:19 Comment(1)
I used CreateProcessWithLogonW (msdn.microsoft.com/en-us/library/ms682431%28VS.85%29.aspx), it's almost the sameLeandra
Y
1

The accepted answer did not work in my case as the application I was starting required admin privileges. What I did is I created a batch file to start the application. It contained the following:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe  "%~dp0MySoft.exe"

Then, I passed the location of this file to StartProcessAsCurrentUser() method. Did the trick.

Yamada answered 6/3, 2018 at 6:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.