프로그램 실행 관련 질문드립니다.

예를 들어
C:\Windows\system32\notepad.exe
위 full path 를 DB에서 추출하여 실행하는 겁니다.
아래 파일 class ProgramInfo 의 start() 를 실행시 (new ProgramInfo().Start();
아래와 같은 예외오류가 납니다. ㅠㅠ ProcessExtensions.cs 는 제 능력밖의 영역입니다.
어떻게 왜 예외가 발생했는지 알고 싶습니다.

activeSessionId: 1 <<<<< hImpersonationToken: 0 >>>>> bResult: False
bResult: False <<<<<
Unhandled exception. System.Exception: StartProcessAsCurrentUser: GetSessionUserToken failed.
at Test.ProcessExtensions.StartProcessAsCurrentUser(String appPath, String cmdLine, PROCESS_INFORMATION& procInfo) in D:__NET\test\ProcessExtensions.cs:line 309
at Test.ProgramInfo.StartWindow() in D:__NET\test\ProgramInfo.cs:line 53
at Test.ProgramInfo.Start() in D:__NET\test\ProgramInfo.cs:line 42
at Test.Program.Main(String args) in D:__NET\test\Program.cs:line 23
PS D:__NET\test>

/// ProgramInfo.cs

using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using System.Diagnostics;


namespace Test
{
    internal class ProgramInfo
    {
        public bool IsPaused { get; set; }
        public bool IsProcessAlive
        {
            get
            {
                uint dwProcessId = pProcessInfo.dwProcessId;
                if (dwProcessId != 0)
                {
                    return ProcessExtensions.IsProcessRunning(dwProcessId);
                }

                return false;
            }
        }

        ProcessExtensions.PROCESS_INFORMATION pProcessInfo = new ProcessExtensions.PROCESS_INFORMATION();

        public ProgramInfo()
        {
            pProcessInfo.hProcess = IntPtr.Zero;
            pProcessInfo.hThread = IntPtr.Zero;
            pProcessInfo.dwProcessId = 0;
            pProcessInfo.dwThreadId = 0;
        }

        public void Start()
        {
           StartWindow();
        }

        public int StartWindow()
        {
            if (IsProcessAlive)
                return -2;
    
            string exePath = "C:\\Windows\\system32\\notepad.exe";
            string exeParam = "";

            if (ProcessExtensions.StartProcessAsCurrentUser(exePath, exeParam, ref pProcessInfo))
            {
                IsPaused = false;
                return -1;
            }

            return 0;
        }
        
    }

}

// / ProcessExtension.cs

using System;
using System.Diagnostics;
using System.Security;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace Test
{

    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;

        public const uint MAXIMUM_ALLOWED                           = 0x2000000;

        private const uint INVALID_SESSION_ID                       = 0xFFFFFFFF;

        private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE    = IntPtr.Zero;


        private const uint INFINITE         = 0xFFFFFFFF;
        private const uint WAIT_ABANDONED   = 0x00000080;
        private const uint WAIT_OBJECT_0    = 0x00000000;
        private const uint WAIT_TIMEOUT     = 0x00000102;


        #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("kernel32.dll", CharSet=CharSet.Auto)]
        public static extern IntPtr OpenProcess(uint Access, bool InheritHandle, uint ProcessId);

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CreateProcess(
            string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
            ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
            IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);

        [DllImport("kernel32.dll")]
        public static extern uint TerminateProcess(IntPtr hProcess, uint ExitCode);

        [DllImport("kernel32.dll", SetLastError=true)]
        static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        [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
        }

        public enum ProcessAccessRights
        {
            PROCESS_CREATE_PROCESS              = 0x0080, //  Required to create a process.
            PROCESS_CREATE_THREAD               = 0x0002, //  Required to create a thread.
            PROCESS_DUP_HANDLE                  = 0x0040, // Required to duplicate a handle using DuplicateHandle.
            PROCESS_QUERY_INFORMATION           = 0x0400, //  Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken, GetExitCodeProcess, GetPriorityClass, and IsProcessInJob).
            PROCESS_QUERY_LIMITED_INFORMATION   = 0x1000, //  Required to retrieve certain information about a process (see QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted PROCESS_QUERY_LIMITED_INFORMATION. Windows Server 2003 and Windows XP/2000:  This access right is not supported.
            PROCESS_SET_INFORMATION             = 0x0200, //    Required to set certain information about a process, such as its priority class (see SetPriorityClass).
            PROCESS_SET_QUOTA                   = 0x0100, //  Required to set memory limits using SetProcessWorkingSetSize.
            PROCESS_SUSPEND_RESUME              = 0x0800, // Required to suspend or resume a process.
            PROCESS_TERMINATE                   = 0x0001, //  Required to terminate a process using TerminateProcess.
            PROCESS_VM_OPERATION                = 0x0008, //   Required to perform an operation on the address space of a process (see VirtualProtectEx and WriteProcessMemory).
            PROCESS_VM_READ                     = 0x0010, //    Required to read memory in a process using ReadProcessMemory.
            PROCESS_VM_WRITE                    = 0x0020, //   Required to write to memory in a process using WriteProcessMemory.
            DELETE                              = 0x00010000, // Required to delete the object.
            READ_CONTROL                        = 0x00020000, //   Required to read information in the security descriptor for the object, not including the information in the SACL. To read or write the SACL, you must request the ACCESS_SYSTEM_SECURITY access right. For more information, see SACL Access Right.
            SYNCHRONIZE                         = 0x00100000, //    The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state.
            WRITE_DAC                           = 0x00040000, //  Required to modify the DACL in the security descriptor for the object.
            WRITE_OWNER                         = 0x00080000, //    Required to change the owner in the security descriptor for the object.
            STANDARD_RIGHTS_REQUIRED            = 0x000f0000,
            PROCESS_ALL_ACCESS                  = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF),//    All possible access rights for a process object.
        }


        [StructLayout(LayoutKind.Sequential)]
        internal 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;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct STARTUPINFOEX
        {
             public STARTUPINFO StartupInfo;
             public IntPtr lpAttributeList;
        }

        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;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public uint nLength;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        #endregion

        // Gets the user token from the currently active session
        private static bool GetSessionUserToken(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));
                IntPtr current          = (IntPtr)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();
            }
            Console.WriteLine(" >>>>> activeSessionId: " + activeSessionId + " <<<<< hImpersonationToken: " + hImpersonationToken + " >>>>> bResult: " + bResult);

            if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
            {
                Console.WriteLine(">>>>> hImpersonationToken: " + hImpersonationToken);
                // 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);
            }

            Console.WriteLine(">>>>> bResult: " + bResult + " <<<<<");
            return bResult;
        }

        internal static bool StartProcessAsCurrentUser(string appPath, string cmdLine, ref PROCESS_INFORMATION procInfo)
        {
            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 (!GetSessionUserToken(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))
                {
                    throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed.");
                }

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

            return true;
        }

        internal static bool StartProcessAsConsole(string appPath, string cmdLine, ref PROCESS_INFORMATION procInfo)
        {
            var startInfo               = new STARTUPINFOEX();
            startInfo.StartupInfo.cb    = Marshal.SizeOf(typeof(STARTUPINFOEX));

            var pSec = new SECURITY_ATTRIBUTES();
            var tSec = new SECURITY_ATTRIBUTES();
            pSec.nLength = (uint)Marshal.SizeOf(pSec);
            tSec.nLength = (uint)Marshal.SizeOf(tSec);

            try
            {
                if (!CreateProcess(null, appPath, ref pSec, ref tSec, true, CREATE_NO_WINDOW, IntPtr.Zero, null, ref startInfo, out procInfo))
                {
                    throw new Exception("CreateProcess failed.");
                }
            }
            finally
            {
                CloseHandle(procInfo.hThread);
                CloseHandle(procInfo.hProcess);                
            }

            return true;
        }

        public static void StopProcess(uint pid)
        {
            try
            {
                // uint dwThreadId = procInfo.dwThreadId;
                // uint dwProcessId = procInfo.dwProcessId;

                IntPtr hProcess = OpenProcess((uint)ProcessAccessRights.PROCESS_TERMINATE, false, (uint)pid);
                if (hProcess != IntPtr.Zero)
                {
                    // Kill the process 
                    TerminateProcess(hProcess, 9);
                    CloseHandle(hProcess);
                }
                else
                {
                    //_logger.warning("Could not OpenProcess.");
                    throw new Exception("Could not OpenProcess.");
                }
            }
            catch (Exception ex)
            {
                throw ex;
                // _logger.error("unknown error in Stop " + m_strName);
            }

        }

        static public bool IsProcessRunning(uint pid)
        {
            IntPtr process  = OpenProcess(MAXIMUM_ALLOWED, false, pid); // 원본 : SYNCHRONIZE
            uint ret        = WaitForSingleObject(process, 0);
            CloseHandle(process);

            // _logger.info("WaitForSingleObject={0}", ret);
            return ret == WAIT_TIMEOUT;
        }

    }
}