6

I have the following problem:

From a service I need to start an application in a user session. No human user log on that machine, since it is a server. Launched application must have a session != 0.

Current "solution"

I used a scheduled task at machine startup, that task launch ( in session 0, of course ) an application launching a Remote Desktop logon on the same machine: this creates a user session > 0 and in the user startup the is the final application to launch. It works, but too tricky.

Is there some smartest way? It is critical that I can reuse a user session already on since there is potentially no user logged on.

MAJOR UPDATE

Well after a lot of research and partial successes, and also thanks to some SysAdmin inflexibility about creating an user for a specific pourpose, I decided to use OpenGL instead of WPF for render the 3d portion broken in Session 0. Surprisingly it took less than expected. I think having this question as a reference could be useful to other who want try to render a Viewport3D from a service.

Felice Pollano
  • 32,832
  • 9
  • 75
  • 115
  • 1
    It sounds like you're trying to fix the wrong problem. *Why* does this launched application have to run outside of session 0? – Damien_The_Unbeliever Feb 27 '13 at 14:22
  • @Damien_The_Unbeliever need of video drivers. I would like to say that if I would be able to avoid the session needing, I will with a lot of pleasure. – Felice Pollano Feb 27 '13 at 14:32
  • 1
    Does that sound like what you need? http://blogs.msdn.com/b/winsdk/archive/2009/07/14/launching-an-interactive-process-from-windows-service-in-windows-vista-and-later.aspx – Simon Mourier Mar 02 '13 at 08:21
  • @SimonMourieryeah, I'm actually trying to use that, but not yet had something working – Felice Pollano Mar 02 '13 at 18:55
  • This is indeed quite complex. In what case of the article are you (1A, 1B, 2A, 2B)? – Simon Mourier Mar 03 '13 at 08:22
  • does this application display a UI or is it a service? – DiskJunky Mar 06 '13 at 18:10
  • @DiskJunky it displays a view, even if noone have to see: it uses wpf to background render a report – Felice Pollano Mar 06 '13 at 18:42
  • Doesn't PSExec just do the trick? http://technet.microsoft.com/en-gb/sysinternals/bb897553 This is not different from running f.ex. skydrive or dropbox as another user, right? – atlaste Mar 08 '13 at 07:19
  • @StefandeBruijn Do you have some evidence it will start a session id >0 ? – Felice Pollano Mar 08 '13 at 07:42
  • @FelicePollano I'm not 100% sure, but I thought skydrive doesn't sync if it isn't in a session. Either way it's quite straight forward to see if this works for you, it's just a matter of changing the process.start... – atlaste Mar 08 '13 at 08:27

3 Answers3

0

I'm not sure if this will work, but maybe this answer helps in your case.

Use the class from the answer I link i provided and the following method (with the appropriate values):

public static void EnableVideoDrivers(bool enable)
{
    // every type of device has a hard-coded GUID, put here the one for
    // video drivers
    Guid videoGuid = new Guid("{device GUID}");

    // get this from the properties dialog box of this device in Device Manager
    string instancePath = @"Device Instance Path";

    DeviceHelper.SetDeviceEnabled(videoGuid, instancePath, enable);
}

Here's a list of Popular Device Class GUIDs.

Community
  • 1
  • 1
Alex Filipovici
  • 31,789
  • 6
  • 54
  • 78
0

I'm not sure I understand correctly your needs, but maybe just starting process with given credentials and redirect input and output is what you need. Starting process with given credentials:

  Process p = new Process();

  p.StartInfo = new ProcessStartInfo(fileName, args);
  p.StartInfo.UserName = userName;
  p.StartInfo.Password = pass;

  p.Start();

You may also need to redirect input and output of the application. That problem is well described on CodeProjecgt in this artice.

Slawomir Pasko
  • 907
  • 8
  • 14
  • 1
    It would be nice if it was so simple. Problem is that doing this from a service ( session id=0 ) leave the spawn process in the same session, I need a session id <> 0. – Felice Pollano Mar 07 '13 at 13:55
0

This is how I start a process for a particular usersession from a Local windows service.

It uses C#, with some DLL imports from kernel32.dll, wtsaspi.dll, userev.dll, and advapi32.dll.

For context, my code will search all user sessions. In my scenario, my service is running on a Windows Terminal server and wants to keep a particular app "alive" in each user's session. Meaning, if we check and its not running anymore, we restart it.

Here is the program logic (abbreviated), this is how you call the method that starts the user process:

foreach(var sesh in ProcessExtensions.GetSessions().Where(r => r.State == "Active").ToList())
{
    var running = procs.Any(r => r.ProcessName == filename && r.SessionId == sesh.SessionId);
    if (!running)
    {
        try
        {
            ProcessExtensions.StartProcessForSession(sesh.SessionId, (string)item, "/restart", System.IO.Path.GetDirectoryName((string)item), true);
        }
        catch (Exception ex)
        {
            Trace.TraceWarning("Error: {0}", ex);
        }
    }
}

Here is the implementation of ProcessExtensions where all of the good stuff is.

Disclaimer - I did not write this code, This is an example I found online and adjusted it to my needs. If you authored the original post. Apologies for the lack of footnote.

ProcessExtensions.cs

public static class ProcessExtensions
{
    #region Win32 Constants

    private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
    private const int CREATE_NO_WINDOW = 0x08000000;

    private const int CREATE_NEW_CONSOLE = 0x00000010;

    private const uint INVALID_SESSION_ID = 0xFFFFFFFF;
    private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;

    #endregion

    #region DllImports

    [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    private static extern bool CreateProcessAsUser(
        IntPtr hToken,
        String lpApplicationName,
        String lpCommandLine,
        IntPtr lpProcessAttributes,
        IntPtr lpThreadAttributes,
        bool bInheritHandle,
        uint dwCreationFlags,
        IntPtr lpEnvironment,
        String lpCurrentDirectory,
        ref STARTUPINFO lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
    private static extern bool DuplicateTokenEx(
        IntPtr ExistingTokenHandle,
        uint dwDesiredAccess,
        IntPtr lpThreadAttributes,
        int TokenType,
        int ImpersonationLevel,
        ref IntPtr DuplicateTokenHandle);

    [DllImport("userenv.dll", SetLastError = true)]
    private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

    [DllImport("userenv.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hSnapshot);

    [DllImport("kernel32.dll")]
    private static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll")]
    private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);

    [DllImport("wtsapi32.dll", SetLastError = true)]
    private static extern int WTSEnumerateSessions(
        IntPtr hServer,
        int Reserved,
        int Version,
        ref IntPtr ppSessionInfo,
        ref int pCount);

    #endregion

    #region Win32 Structs

    private enum SW
    {
        SW_HIDE = 0,
        SW_SHOWNORMAL = 1,
        SW_NORMAL = 1,
        SW_SHOWMINIMIZED = 2,
        SW_SHOWMAXIMIZED = 3,
        SW_MAXIMIZE = 3,
        SW_SHOWNOACTIVATE = 4,
        SW_SHOW = 5,
        SW_MINIMIZE = 6,
        SW_SHOWMINNOACTIVE = 7,
        SW_SHOWNA = 8,
        SW_RESTORE = 9,
        SW_SHOWDEFAULT = 10,
        SW_MAX = 10
    }

    private enum WTS_CONNECTSTATE_CLASS
    {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;
    }

    private enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous = 0,
        SecurityIdentification = 1,
        SecurityImpersonation = 2,
        SecurityDelegation = 3,
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct STARTUPINFO
    {
        public int cb;
        public String lpReserved;
        public String lpDesktop;
        public String lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    private enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation = 2
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
        public readonly UInt32 SessionID;

        [MarshalAs(UnmanagedType.LPStr)]
        public readonly String pWinStationName;

        public readonly WTS_CONNECTSTATE_CLASS State;
    }

    #endregion

    public static IEnumerable<UserSessionData> GetSessions()
    {
        //var bResult = false;
        var hImpersonationToken = IntPtr.Zero;
        //var activeSessionId = INVALID_SESSION_ID;
        var pSessionInfo = IntPtr.Zero;
        var sessionCount = 0;

        if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
        {
            var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            var current = pSessionInfo;

            for (var i = 0; i < sessionCount; i++)
            {
                var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
                current += arrayElementSize;

                var data = new UserSessionData
                {
                    SessionId = (int)si.SessionID,
                    State = si.State.ToString().Substring(3),
                    Name = si.pWinStationName
                };
                yield return data;
            }
        }

    }

    private static bool GetUserTokenForSession(int sessionId, ref IntPtr phUserToken)
    {
        var bResult = false;
        var hImpersonationToken = IntPtr.Zero;
        var pSessionInfo = IntPtr.Zero;

        if (WTSQueryUserToken((uint)sessionId, ref hImpersonationToken) != 0)
        {
            // Convert the impersonation token to a primary token
            bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
                (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
                ref phUserToken);

            CloseHandle(hImpersonationToken);
        }

        return bResult;
    }

    private static bool GetCurrentUserSessionToken(ref IntPtr phUserToken)
    {
        var bResult = false;
        var hImpersonationToken = IntPtr.Zero;
        var activeSessionId = INVALID_SESSION_ID;
        var pSessionInfo = IntPtr.Zero;
        var sessionCount = 0;

        // Get a handle to the user access token for the current active session.
        if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
        {
            var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            var current = pSessionInfo;

            for (var i = 0; i < sessionCount; i++)
            {
                var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
                current += arrayElementSize;

                if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
                {
                    activeSessionId = si.SessionID;
                }
            }
        }

        // If enumerating did not work, fall back to the old method
        if (activeSessionId == INVALID_SESSION_ID)
        {
            activeSessionId = WTSGetActiveConsoleSessionId();
        }

        if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
        {
            // Convert the impersonation token to a primary token
            bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
                (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
                ref phUserToken);

            CloseHandle(hImpersonationToken);
        }

        return bResult;
    }

    public static bool StartProcessForSession(int sessionId, string appPath, string cmdLine = null, string workDir = null, bool visible = true)
    {
        var hUserToken = IntPtr.Zero;
        var startInfo = new STARTUPINFO();
        var procInfo = new PROCESS_INFORMATION();
        var pEnv = IntPtr.Zero;
        int iResultOfCreateProcessAsUser;

        startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));

        try
        {
            if (!GetUserTokenForSession(sessionId, ref hUserToken))
            {
                throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
            }

            uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
            startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
            startInfo.lpDesktop = "winsta0\\default";

            if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
            {
                throw new Exception("StartProcessInSession: CreateEnvironmentBlock failed.");
            }

            if (!CreateProcessAsUser(hUserToken,
                appPath, // Application Name
                cmdLine, // Command Line
                IntPtr.Zero,
                IntPtr.Zero,
                false,
                dwCreationFlags,
                pEnv,
                workDir, // Working directory
                ref startInfo,
                out procInfo))
            {
                iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
                throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed.  Error Code -" + iResultOfCreateProcessAsUser);
            }

            iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
        }
        finally
        {
            CloseHandle(hUserToken);
            if (pEnv != IntPtr.Zero)
            {
                DestroyEnvironmentBlock(pEnv);
            }
            CloseHandle(procInfo.hThread);
            CloseHandle(procInfo.hProcess);
        }

        return true;
    }

    public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true)
    {
        var hUserToken = IntPtr.Zero;
        var startInfo = new STARTUPINFO();
        var procInfo = new PROCESS_INFORMATION();
        var pEnv = IntPtr.Zero;
        int iResultOfCreateProcessAsUser;

        startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));

        try
        {
            if (!GetCurrentUserSessionToken(ref hUserToken))
            {
                throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
            }

            uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
            startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
            startInfo.lpDesktop = "winsta0\\default";

            if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
            {
                throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed.");
            }

            if (!CreateProcessAsUser(hUserToken,
                appPath, // Application Name
                cmdLine, // Command Line
                IntPtr.Zero,
                IntPtr.Zero,
                false,
                dwCreationFlags,
                pEnv,
                workDir, // Working directory
                ref startInfo,
                out procInfo))
            {
                iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
                throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed.  Error Code -" + iResultOfCreateProcessAsUser);
            }

            iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
        }
        finally
        {
            CloseHandle(hUserToken);
            if (pEnv != IntPtr.Zero)
            {
                DestroyEnvironmentBlock(pEnv);
            }
            CloseHandle(procInfo.hThread);
            CloseHandle(procInfo.hProcess);
        }

        return true;
    }

}
Glenn Ferrie
  • 10,290
  • 3
  • 42
  • 73