다음을 통해 공유


Invoking UI Components from NT Service via C# PInvoke

I've ran into this problem when there was a requirement to call a UI enabled program from a NT service. Usually the NT service runs under the Session 0 which means NO UI. So if you tried to invoke a program with a UI then it'll also start under Session 0. While to appear a program on the logged in desktop it has to have the same session as a win logon process is having. So I did a work around and fixed the problem by getting the win logon session ID and launching the process with that session Id. This solution will work on windows Vista, 7 and 8. 

Here is the complete solution class that was used:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
  
namespace Shared.ApplicationLoaderUtil
{
    /// <summary>
    /// Class that allows running applications with full admin rights. In
    /// addition the application launched will bypass the Vista UAC prompt.
    /// Windows 8 have two OS modes for running application Windows 8 and Windows Vista mode.
    /// </summary>
    public class  ApplicationLoader
    {
        /// <summary>
        /// The token duplicate.
        /// </summary>
        private const  int TokenDuplicate = 0x0002;
  
        /// <summary>
        /// The maximum allowed.
        /// </summary>
        private const  uint MaximumAllowed = 0x2000000;
  
        /// <summary>
        /// The create new console.
        /// </summary>
        private const  int CreateNewConsole = 0x00000010;
  
        /// <summary>
        /// The idle priority class.
        /// </summary>
        private const  int IdlePriorityClass = 0x40;
  
        /// <summary>
        /// The normal priority class.
        /// </summary>
        private const  int NormalPriorityClass = 0x20;
  
        /// <summary>
        /// The high priority class.
        /// </summary>
        private const  int HighPriorityClass = 0x80;
  
        /// <summary>
        /// The real time priority class.
        /// </summary>
        private const  int RealtimePriorityClass = 0x100;
  
        /// <summary>
        /// The win logon process name.
        /// </summary>
        private const  string WinLogonProcessName = "winlogon";
  
        /// <summary>
        /// The toke n_ type.
        /// </summary>
        private enum  TokenType
        {
            /// <summary>
            /// The token primary.
            /// </summary>
            TokenPrimary = 1,
  
            /// <summary>
            /// The token impersonation.
            /// </summary>
            TokenImpersonation = 2
        }
  
        /// <summary>
        /// The security impersonation level.
        /// </summary>
        private enum  SecurityImpersonationLevel
        {
            /// <summary>
            /// The security anonymous.
            /// </summary>
            SecurityAnonymous = 0,
  
            /// <summary>
            /// The security identification.
            /// </summary>
            SecurityIdentification = 1,
  
            /// <summary>
            /// The security impersonation.
            /// </summary>
            SecurityImpersonation = 2,
  
            /// <summary>
            /// The security delegation.
            /// </summary>
            SecurityDelegation = 3,
        }
  
        /// <summary>
        /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
        /// </summary>
        /// <param name="applicationName">The name of the application to launch</param>
        /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
        /// <returns>true or false</returns>
        public static  bool StartProcessAndBypassUAC(string applicationName, out ProcessInformation procInfo)
        {
            uint winlogonPid = 0;
            IntPtr userTokenDup = IntPtr.Zero;
            IntPtr processPtr = IntPtr.Zero;
            IntPtr ptoken = IntPtr.Zero;
            procInfo = new  ProcessInformation();
  
            // obtain the currently active session id; every logged on user in the system has a unique session id
            uint sessionId = WTSGetActiveConsoleSessionId();
  
            // obtain the process id of the winlogon process that is running within the currently active session
            Process[] processes = Process.GetProcessesByName(WinLogonProcessName);
            foreach (Process p in processes)
            {
                if ((uint)p.SessionId == sessionId)
                {
                    winlogonPid = (uint)p.Id;
                }
            }
  
            // obtain a handle to the winlogon process
            processPtr = OpenProcess(MaximumAllowed, false, winlogonPid);
  
            // obtain a handle to the access token of the winlogon process
            if (!OpenProcessToken(processPtr, TokenDuplicate,  ref  ptoken))
            {
                CloseHandle(processPtr);
                return false;
            }
  
            // Security attribute structure used in DuplicateTokenEx and CreateProcessAsUser
            // I would prefer to not have to use a security attribute variable and to just 
            // simply pass null and inherit (by default) the security attributes
            // of the existing token. However, in C# structures are value types and therefore
            // cannot be assigned the null value.
            SecurityAttributes sa = new  SecurityAttributes();
            sa.Length = Marshal.SizeOf(sa);
  
            // copy the access token of the winlogon process; the newly created token will be a primary token
            if (!DuplicateTokenEx(ptoken, MaximumAllowed, ref sa, (int)SecurityImpersonationLevel.SecurityIdentification, (int)TokenType.TokenPrimary, ref  userTokenDup))
            {
                CloseHandle(processPtr);
                CloseHandle(ptoken);
                return false;
            }
  
            // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
            // the window station has a desktop that is invisible and the process is incapable of receiving
            // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 
            // interaction with the new process.
            Startupinfo si = new  Startupinfo();
            si.Cb = (int)Marshal.SizeOf(si);
            si.Desktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
  
            // flags that specify the priority and creation method of the process
            const int  CreationFlags = NormalPriorityClass | CreateNewConsole;
  
            var path = Path.GetFullPath(applicationName);
            var dir = Path.GetDirectoryName(path);
  
            // create a new process in the current user's logon session
            bool result = CreateProcessAsUser(
                userTokenDup,           // client's access token
                path,                   // file to execute
                string.Format("\"{0}\" {1}", applicationName.Replace("\"", "\"\""),  string.Empty),        // command line
                ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                false,                  // handles are not inheritable
                CreationFlags,        // creation flags
                IntPtr.Zero,            // pointer to new environment block 
                dir,                    // name of current directory 
                ref si,                 // pointer to STARTUPINFO structure
                out procInfo);          // receives information about new process
                 
            if (!result)
            {
                throw new  Win32Exception(Marshal.GetLastWin32Error());
            }
  
            // invalidate the handles
            CloseHandle(processPtr);
            CloseHandle(ptoken);
            CloseHandle(userTokenDup);
  
            return result; // return the result
        }
  
        [DllImport("kernel32.dll", SetLastError = true)]
        private static  extern bool  CloseHandle(IntPtr snapshot);
  
        /// <summary>
        /// Creates the process as user.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="applicationName">Name of the application.</param>
        /// <param name="commandLine">The command line.</param>
        /// <param name="processAttributes">The process attributes.</param>
        /// <param name="threadAttributes">The thread attributes.</param>
        /// <param name="inheritHandle">if set to <c>true</c> [inherit handle].</param>
        /// <param name="creationFlags">The creation flags.</param>
        /// <param name="environment">The environment.</param>
        /// <param name="currentDirectory">The current directory.</param>
        /// <param name="startupInfo">The startup info.</param>
        /// <param name="processInformation">The process information.</param>
        /// <returns>
        /// true or false
        /// </returns>
        [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
        private static  extern bool  CreateProcessAsUser(
            IntPtr token, 
            string applicationName, 
            string commandLine, 
            ref SecurityAttributes processAttributes,
            ref SecurityAttributes threadAttributes, 
            bool inheritHandle, 
            int creationFlags, 
            IntPtr environment,
            string currentDirectory, 
            ref Startupinfo startupInfo, 
            out ProcessInformation processInformation);
  
        /// <summary>
        /// Duplicates the token ex.
        /// </summary>
        /// <param name="existingTokenHandle">The existing token handle.</param>
        /// <param name="desiredAccess">The dw desired access.</param>
        /// <param name="threadAttributes">The lp thread attributes.</param>
        /// <param name="tokenType">Type of the token.</param>
        /// <param name="impersonationLevel">The impersonation level.</param>
        /// <param name="duplicateTokenHandle">The duplicate token handle.</param>
        /// <returns>true or false</returns>
        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
        private static  extern bool  DuplicateTokenEx(
            IntPtr existingTokenHandle, 
            uint desiredAccess,
            ref SecurityAttributes threadAttributes, 
            int tokenType,
            int impersonationLevel, 
            ref IntPtr duplicateTokenHandle);
  
        [DllImport("kernel32.dll")]
        private static  extern uint  WTSGetActiveConsoleSessionId();
  
        /// <summary>
        /// Opens the process.
        /// </summary>
        /// <param name="desiredAccess">The dw desired access.</param>
        /// <param name="inheritHandle">if set to <c>true</c> [b inherit handle].</param>
        /// <param name="processId">The dw process id.</param>
        /// <returns>integer pointer</returns>
        [DllImport("kernel32.dll")]
        private static  extern IntPtr OpenProcess(uint desiredAccess, bool inheritHandle, uint processId);
  
        /// <summary>
        /// Opens the process token.
        /// </summary>
        /// <param name="processHandle">The process handle.</param>
        /// <param name="desiredAccess">The desired access.</param>
        /// <param name="tokenHandle">The token handle.</param>
        /// <returns>true or false</returns>
        [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurity]
        private static  extern bool  OpenProcessToken(IntPtr processHandle, int desiredAccess, ref IntPtr tokenHandle);
  
        /// <summary>
        /// The process information.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct  ProcessInformation
        {
            /// <summary>
            /// The process.
            /// </summary>
            public IntPtr Process;
  
            /// <summary>
            /// The thread.
            /// </summary>
            public IntPtr Thread;
  
            /// <summary>
            /// The process id.
            /// </summary>
            public uint  ProcessId;
  
            /// <summary>
            /// The thread id.
            /// </summary>
            public uint  ThreadId;
        }
  
        /// <summary>
        /// The security attributes.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct  SecurityAttributes
        {
            /// <summary>
            /// The length.
            /// </summary>
            public int  Length;
  
            /// <summary>
            /// The lp security descriptor.
            /// </summary>
            public IntPtr SecurityDescriptor;
  
            /// <summary>
            /// The b inherit handle.
            /// </summary>
            public bool  InheritHandle;
        }
  
        /// <summary>
        /// The startupinfo.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct  Startupinfo
        {
            /// <summary>
            /// The cb.
            /// </summary>
            public int  Cb;
  
            /// <summary>
            /// The lp reserved.
            /// </summary>
            public string  Reserved;
  
            /// <summary>
            /// The desktop.
            /// </summary>
            public string  Desktop;
  
            /// <summary>
            /// The title.
            /// </summary>
            public string  Title;
  
            /// <summary>
            /// The dw x.
            /// </summary>
            public uint  X;
  
            /// <summary>
            /// The y.
            /// </summary>
            public uint  Y;
  
            /// <summary>
            /// The x size.
            /// </summary>
            public uint  XSize;
  
            /// <summary>
            /// The y size.
            /// </summary>
            public uint  YSize;
  
            /// <summary>
            /// The x count chars.
            /// </summary>
            public uint  XCountChars;
  
            /// <summary>
            /// The y count chars.
            /// </summary>
            public uint  YCountChars;
  
            /// <summary>
            /// The fill attribute.
            /// </summary>
            public uint  FillAttribute;
  
            /// <summary>
            /// The flags.
            /// </summary>
            public uint  Flags;
  
            /// <summary>
            /// The show window.
            /// </summary>
            public short  ShowWindow;
  
            /// <summary>
            /// The reserved 2.
            /// </summary>
            public short  Reserved2;
  
            /// <summary>
            /// The lp reserved 2.
            /// </summary>
            public IntPtr LpReserved2;
  
            /// <summary>
            /// The std input.
            /// </summary>
            public IntPtr StdInput;
  
            /// <summary>
            /// The std output.
            /// </summary>
            public IntPtr StdOutput;
  
            /// <summary>
            /// The std error.
            /// </summary>
            public IntPtr StdError;
        }
    }
}

Hope this will help someone with the similar requirement.