对于某些版本的 Windows,Windows 服务无法在用户使用 WTSQueryUserToken 登录时启动交互过程

Posted

技术标签:

【中文标题】对于某些版本的 Windows,Windows 服务无法在用户使用 WTSQueryUserToken 登录时启动交互过程【英文标题】:Windows Service fails to start interactive process on user log on with WTSQueryUserToken for some versions of Windows 【发布时间】:2015-04-24 19:26:02 【问题描述】:

按照link,我在我的 C# 解决方案中实现了 WTSQueryUserToken,并从我的 Windows 服务的OnStart 中调用了CreateProcessAsUserWrapper.LaunchChildProcess("app_path") 方法,该方法运行为 "本地系统”。它能够启动可以与桌面交互的进程,但适用于 Windows Professional 而不是 Windows Ultimate。我在 Windows Professional 64 位上尝试过,它能够成功启动用户交互登录过程,但在 Windows Ultimate 64 位版本上,CreateProcessAsUser 方法返回 false,服务可以看到在SCM 运行,而进程不是。

我的进程是一个 win 表单应用程序,它需要使用当前登录的用户帐户运行以进行桌面交互,并使用 InteropServices。会不会是框架问题?如何解决问题?请帮忙。

编辑: 两台机器都运行 Windows 7 64 位。我在CreateProcessAsUser 之后添加了以下代码以获取错误代码,它返回“请求的操作需要提升”作为错误。

string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;

仅在 Windows 7 Ultimate 中发生,在 Windows 7 Professional 中不发生。我需要为每个登录的用户运行我的进程,这就是为什么我认为我不能使用 CreateProcessWithLogonW 方法来代替。我现在该如何解决这个提升权限问题?

编辑: 有2个app.manifest文件,一个在服务项目中,另一个在win表单(进程)项目中。我删除了这两个文件,用WiX 构建了msi 并再次测试。现在它在每台机器上成功运行,每个登录的用户帐户都启动了该进程。但是,现在引起了一个问题。实际上我需要在 windows/system 文件夹下写一些日志,我现在不能这样做。我正在从该过程中抛出“UnauthorizedAccessException”。此外,除管理员外,不允许任何用户停止或卸载进程,但在这种情况下,用户可以启动任务管理器并从那里结束进程,因为它正在使用相应的用户帐户运行。是否可以使用用户帐户但具有提升的权限运行该进程,以便我可以在 windows/system 文件夹下写入我的日志并防止用户在没有管理员权限的情况下停止该进程?至少解决第一个问题,即在系统文件夹下写入日志文件对我来说非常重要。

编辑: 添加SetTokenInformationDuplicateTokenEx 后,我的服务无法启动应用程序,日志SetTokenInformation() 返回 FALSE。错误:客户端未持有所需的权限”。我正在提供完整的代码,请查看我是否已正确更新所有步骤。

using System;
using System.IO;
using System.ComponentModel;
using System.Security.Principal;
using System.Text;
using System.Collections;
using System.Web;
using System.Net;
using System.Diagnostics;
using System.Runtime.InteropServices; // DllImport

namespace CSCreateProcessAsUserFromService

    class CreateProcessAsUserWrapper1
    
        static String tmp_log = Path.Combine(CreateDirectoryAtSystemFolder(), "Temp-Report-Service.txt");
        static string errorMessage;

        public static void LaunchChildProcess(string ChildProcName)
        
            IntPtr ppSessionInfo = IntPtr.Zero;
            UInt32 SessionCount = 0;

            File.AppendAllText(tmp_log, "\n" + "LaunchChildProcess() OK 1" + "\n");

            if (WTSEnumerateSessions(
                (IntPtr)WTS_CURRENT_SERVER_HANDLE,  // Current RD Session Host Server handle would be zero.
                0,                                  // This reserved parameter must be zero.
                1,                                  // The version of the enumeration request must be 1.
                ref ppSessionInfo,                  // This would point to an array of session info.
                ref SessionCount                    // This would indicate the length of the above array.
                ))
            
                File.AppendAllText(tmp_log, "\n" + "WTSEnumerateSessions OK 2" + "\n");

                for (int nCount = 0; nCount < SessionCount; nCount++)
                
                    // Extract each session info and check if it is the 
                    // "Active Session" of the current logged-on user.
                    //ppSessionInfo = new IntPtr(ppSessionInfo.ToInt32() + nCount * Marshal.SizeOf(typeof(WTS_SESSION_INFO)));
                    WTS_SESSION_INFO tSessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(
                        ppSessionInfo + nCount * Marshal.SizeOf(typeof(WTS_SESSION_INFO)),
                        typeof(WTS_SESSION_INFO)
                        );
                    /*WTS_SESSION_INFO tSessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(
                        new IntPtr(ppSessionInfo.ToInt32() + nCount * Marshal.SizeOf(typeof(WTS_SESSION_INFO))),
                        typeof(WTS_SESSION_INFO)
                        );*/

                    if (WTS_CONNECTSTATE_CLASS.WTSActive == tSessionInfo.State)
                    
                        WindowsIdentity m_ImpersonatedUser;

                        IntPtr hToken = IntPtr.Zero;
                        IntPtr hTokenDuplicate = IntPtr.Zero;
                        const int SecurityImpersonation = 2;
                        const int TokenType = 1;

                        File.AppendAllText(tmp_log, "\n" + "if (WTS_CONNECTSTATE_CLASS.WTSActive == tSessionInfo.State) 3" + "\n");

                        if (RevertToSelf())
                        
                            File.AppendAllText(tmp_log, "\n" + "RevertToSelf() TRUE 4 " + "\n");

                            if (WTSQueryUserToken(tSessionInfo.SessionID, out hToken))
                            
                                File.AppendAllText(tmp_log, "\n" + "if (WTSQueryUserToken(tSessionInfo.SessionID, out hToken)) TRUE 5 " + "\n");

                                LUID luid = new LUID();
                                // Security attibute 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.
                                SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
                                sa.Length = Marshal.SizeOf(sa);
                                //if (DuplicateToken(hToken, SecurityImpersonation, ref hTokenDuplicate) != 0)
                                if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED/*GENERIC_ACCESS*/, ref sa,
                                    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                                    (int)TOKEN_TYPE.TokenPrimary, out/*ref*/ hTokenDuplicate))
                                
                                    File.AppendAllText(tmp_log, "\n" + " DuplicateTokenEx() TRUE 6 " + "\n");

                                    WindowsImpersonationContext m_ImpersonationContext;
                                    m_ImpersonatedUser = new WindowsIdentity(hTokenDuplicate);
                                    using (m_ImpersonationContext = m_ImpersonatedUser.Impersonate())
                                    
                                        if (m_ImpersonationContext != null)
                                        
                                            File.AppendAllText(tmp_log, Environment.NewLine + "User Name: " +
                                                      WindowsIdentity.GetCurrent(TokenAccessLevels.MaximumAllowed).Name +
                                                      Environment.NewLine + "SID: " +
                                                      WindowsIdentity.GetCurrent(TokenAccessLevels.MaximumAllowed).User.Value);

                                            TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES
                                            
                                                PrivilegeCount = 1,
                                                Privileges = new Int32[3]
                                            ;

                                            tp.Privileges[1] = luid.HighPart;
                                            tp.Privileges[0] = luid.LowPart;
                                            tp.Privileges[2] = SE_PRIVILEGE_ENABLED;

                                            //Adjust Token privilege
                                            if (SetTokenInformation(hTokenDuplicate,
                                                TOKEN_INFORMATION_CLASS.TokenSessionId,
                                                ref tSessionInfo.SessionID,
                                                (UInt32)Marshal.SizeOf(tSessionInfo.SessionID)))
                                            
                                                File.AppendAllText(tmp_log, "\n" + " SetTokenInformation() TRUE 7 " + "\n");

                                                if (AdjustTokenPrivileges(hTokenDuplicate,
                                                   false, ref tp, Marshal.SizeOf(tp),
                                                   IntPtr.Zero, IntPtr.Zero))
                                                
                                                    File.AppendAllText(tmp_log, "\n" + " AdjustTokenPrivileges() TRUE 8 " + "\n");

                                                    //ImpersonateLoggedOnUser(hToken);
                                                    //WindowsIdentity.Impersonate(hToken);

                                                    //errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                                                    //File.AppendAllText(tmp_log, "\n" + "LaunchChildProcess-ImpersonateLoggedOnUser-" + errorMessage + "\n");

                                                    // Launch the child process interactively 
                                                    // with the token of the logged-on user.
                                                    PROCESS_INFORMATION tProcessInfo;
                                                    STARTUPINFO tStartUpInfo = new STARTUPINFO();
                                                    tStartUpInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
                                                    tStartUpInfo.lpDesktop = @"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
                                                    int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
                                                    IntPtr pEnv = IntPtr.Zero;
                                                    if (CreateEnvironmentBlock(ref pEnv, hTokenDuplicate, true))
                                                    
                                                        dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
                                                        File.AppendAllText(tmp_log, "\n" + " CreateEnvironmentBlock() TRUE 9 " + "\n");
                                                    
                                                    else
                                                    
                                                        pEnv = IntPtr.Zero;
                                                        File.AppendAllText(tmp_log, "\n" + " CreateEnvironmentBlock() ELSE 9 " + "\n");
                                                    
                                                    // create a new process in the current user's logon session
                                                    bool ChildProcStarted = CreateProcessAsUser(
                                                        hTokenDuplicate,        // client's access token
                                                        ChildProcName,          // file to execute
                                                        null,                   // command line
                                                        ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                                                        ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                                                        false,                  // handles are not inheritable
                                                        dwCreationFlags,        // creation flags
                                                        IntPtr.Zero,            // pointer to new environment block 
                                                        null,                   // name of current directory 
                                                        ref tStartUpInfo,                 // pointer to STARTUPINFO structure
                                                        out tProcessInfo            // receives information about new process
                                                        );

                                                    /*bool ChildProcStarted = CreateProcessAsUser(
                                                       //hToken,             // Token of the logged-on user.
                                                       hTokenDuplicate,    // Token of the logged-on user.
                                                       ChildProcName,      // Name of the process to be started.
                                                       null,               // Any command line arguments to be passed.
                                                       IntPtr.Zero,        // Default Process' attributes.
                                                       IntPtr.Zero,        // Default Thread's attributes.
                                                       false,              // Does NOT inherit parent's handles.
                                                       0,                  // No any specific creation flag.
                                                       null,               // Default environment path.
                                                       null,               // Default current directory.
                                                       ref tStartUpInfo,   // Process Startup Info. 
                                                       out tProcessInfo    // Process information to be returned.
                                                       );*/

                                                    errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                                                    File.AppendAllText(tmp_log, "\n" + "LaunchChildProcess-CreateProcessAsUser-" + errorMessage + "\n");

                                                    if (ChildProcStarted)
                                                    
                                                        // The child process creation is successful!

                                                        // If the child process is created, it can be controlled via the out 
                                                        // param "tProcessInfo". For now, as we don't want to do any thing 
                                                        // with the child process, closing the child process' handles 
                                                        // to prevent the handle leak.
                                                        CloseHandle(tProcessInfo.hThread);
                                                        CloseHandle(tProcessInfo.hProcess);

                                                        File.AppendAllText(tmp_log, "\n" + "ChildProcStarted() 10 true!" + "\n");
                                                    
                                                    else
                                                    
                                                        // CreateProcessAsUser failed!
                                                        errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                                                        File.AppendAllText(tmp_log, "\n" + " ChildProcStarted() 10 failed! ERROR: " + errorMessage + "\n");
                                                    

                                                    // Whether child process was created or not, close the token handle 
                                                    // and break the loop as processing for current active user has been done.
                                                    CloseHandle(hToken);
                                                    CloseHandle(hTokenDuplicate);
                                                    // Undo impersonation
                                                    //m_ImpersonationContext.Undo();
                                                    break;
                                                
                                                else
                                                
                                                    errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                                                    File.AppendAllText(tmp_log, "\n" + " AdjustTokenPrivileges() FALSE 8 ERROR: " + errorMessage + "\n");
                                                
                                            
                                            else
                                            
                                                errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                                                //File.AppendAllText(tmp_log, "\n" + "LaunchChildProcess-ImpersonateLoggedOnUser-" + errorMessage + "\n");
                                                File.AppendAllText(tmp_log, "\n" + " SetTokenInformation() FALSE 7 ERROR: " + errorMessage + "\n");
                                            
                                        
                                    
                                
                                else
                                
                                    errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                                    File.AppendAllText(tmp_log, "\n" + "DuplicateTokenEx() FALSE 6 ERROR: " + errorMessage + "\n");
                                
                            
                            else
                            
                                // WTSQueryUserToken failed!
                                errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                                File.AppendAllText(tmp_log, "\n" + "if (WTSQueryUserToken(tSessionInfo.SessionID, out hToken)) FALSE 5 ERROR: " + errorMessage + "\n");
                            
                        
                        else
                        
                            errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                            File.AppendAllText(tmp_log, "\n" + "RevertToSelf() FALSE 4 ERROR: " + errorMessage + "\n");

                        
                    
                    else
                    
                        // This Session is not active!
                        errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                        File.AppendAllText(tmp_log, "\n" + "if (WTS_CONNECTSTATE_CLASS.WTSActive == tSessionInfo.State) FALSE 3 This Session is not active! ERROR: " + errorMessage + "\n");
                    
                

                // Free the memory allocated for the session info array.
                WTSFreeMemory(ppSessionInfo);
            
            else
            
                // WTSEnumerateSessions failed!
                errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                File.AppendAllText(tmp_log, "\n" + "WTSEnumerateSessions FAILED 2 ERROR: " + errorMessage + "\n");
            
        


        #region P/Invoke WTS APIs
        /// <summary>
        /// Struct, Enum and P/Invoke Declarations of WTS APIs.
        /// </summary>
        /// 

        private const int WTS_CURRENT_SERVER_HANDLE = 0;
        private enum WTS_CONNECTSTATE_CLASS
        
            WTSActive,
            WTSConnected,
            WTSConnectQuery,
            WTSShadow,
            WTSDisconnected,
            WTSIdle,
            WTSListen,
            WTSReset,
            WTSDown,
            WTSInit
        

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct WTS_SESSION_INFO
        
            public UInt32 SessionID;
            public string pWinStationName;
            public WTS_CONNECTSTATE_CLASS State;
        

        [DllImport("WTSAPI32.DLL", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool WTSEnumerateSessions(
            IntPtr hServer,
            [MarshalAs(UnmanagedType.U4)] UInt32 Reserved,
            [MarshalAs(UnmanagedType.U4)] UInt32 Version,
            ref IntPtr ppSessionInfo,
            [MarshalAs(UnmanagedType.U4)] ref UInt32 pSessionInfoCount
            );

        [DllImport("WTSAPI32.DLL", SetLastError = true, CharSet = CharSet.Auto)]
        static extern void WTSFreeMemory(IntPtr pMemory);

        [DllImport("WTSAPI32.DLL", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);
        #endregion


        #region P/Invoke CreateProcessAsUser
        /// <summary>
        /// Struct, Enum and P/Invoke Declarations for CreateProcessAsUser.
        /// </summary>
        /// 

        #region WMI Constants

        private const String cstrScope = "root\\CIMV2";
        private const String cstrLoggenInUser = "SELECT * FROM Win32_ComputerSystem";

        #endregion

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct STARTUPINFO
        
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct PROCESS_INFORMATION
        
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        
            public int Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        

        #region Enumerations

        enum TOKEN_INFORMATION_CLASS
        
            TokenUser = 1,
            TokenGroups,
            TokenPrivileges,
            TokenOwner,
            TokenPrimaryGroup,
            TokenDefaultDacl,
            TokenSource,
            TokenType,
            TokenImpersonationLevel,
            TokenStatistics,
            TokenRestrictedSids,
            TokenSessionId,
            TokenGroupsAndPrivileges,
            TokenSessionReference,
            TokenSandBoxInert,
            TokenAuditPolicy,
            TokenOrigin,
            MaxTokenInfoClass  // MaxTokenInfoClass should always be the last enum
        


        enum TOKEN_TYPE : int
        
            TokenPrimary = 1,
            TokenImpersonation = 2
        

        enum SECURITY_IMPERSONATION_LEVEL : int
        
            SecurityAnonymous = 0,
            SecurityIdentification = 1,
            SecurityImpersonation = 2,
            SecurityDelegation = 3,
        

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID
        
            public Int32 LowPart;
            public Int32 HighPart;
        

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID_AND_ATRIBUTES
        
            LUID Luid;
            Int32 Attributes;
        

        [StructLayout(LayoutKind.Sequential)]
        struct TOKEN_PRIVILEGES
        
            public Int32 PrivilegeCount;
            //LUID_AND_ATRIBUTES
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public Int32[] Privileges;
        

        #endregion

        #region Constants

        public const uint GENERIC_ACCESS = 0x1000000;
        public const uint MAXIMUM_ALLOWED = 0x2000000;
        public const int CREATE_NEW_CONSOLE = 0x00000010;

        public const int IDLE_PRIORITY_CLASS = 0x40;
        public const int NORMAL_PRIORITY_CLASS = 0x20;
        public const int HIGH_PRIORITY_CLASS = 0x80;
        public const int REALTIME_PRIORITY_CLASS = 0x100;

        const Int32 READ_CONTROL = 0x00020000;

        const Int32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;

        const Int32 STANDARD_RIGHTS_READ = READ_CONTROL;
        const Int32 STANDARD_RIGHTS_WRITE = READ_CONTROL;
        const Int32 STANDARD_RIGHTS_EXECUTE = READ_CONTROL;

        const Int32 STANDARD_RIGHTS_ALL = 0x001F0000;

        const Int32 SPECIFIC_RIGHTS_ALL = 0x0000FFFF;

        const Int32 TOKEN_ASSIGN_PRIMARY = 0x0001;
        const Int32 TOKEN_DUPLICATE = 0x0002;
        const Int32 TOKEN_IMPERSONATE = 0x0004;
        const Int32 TOKEN_QUERY = 0x0008;
        const Int32 TOKEN_QUERY_SOURCE = 0x0010;
        const Int32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
        const Int32 TOKEN_ADJUST_GROUPS = 0x0040;
        const Int32 TOKEN_ADJUST_DEFAULT = 0x0080;
        const Int32 TOKEN_ADJUST_SESSIONID = 0x0100;

        const Int32 TOKEN_ALL_ACCESS_P = (
            STANDARD_RIGHTS_REQUIRED |
            TOKEN_ASSIGN_PRIMARY |
            TOKEN_DUPLICATE |
            TOKEN_IMPERSONATE |
            TOKEN_QUERY |
            TOKEN_QUERY_SOURCE |
            TOKEN_ADJUST_PRIVILEGES |
            TOKEN_ADJUST_GROUPS |
            TOKEN_ADJUST_DEFAULT);

        const Int32 TOKEN_ALL_ACCESS = TOKEN_ALL_ACCESS_P | TOKEN_ADJUST_SESSIONID;

        const Int32 TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;


        const Int32 TOKEN_WRITE = STANDARD_RIGHTS_WRITE |
                                      TOKEN_ADJUST_PRIVILEGES |
                                      TOKEN_ADJUST_GROUPS |
                                      TOKEN_ADJUST_DEFAULT;

        const Int32 TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE;

        //const UInt32 MAXIMUM_ALLOWED = 0x2000000;

        const Int32 CREATE_NEW_PROCESS_GROUP = 0x00000200;
        const Int32 CREATE_UNICODE_ENVIRONMENT = 0x00000400;

        /*const Int32 IDLE_PRIORITY_CLASS = 0x40;
        const Int32 NORMAL_PRIORITY_CLASS = 0x20;
        const Int32 HIGH_PRIORITY_CLASS = 0x80;
        const Int32 REALTIME_PRIORITY_CLASS = 0x100;*/

        //const Int32 CREATE_NEW_CONSOLE = 0x00000010;

        const string SE_DEBUG_NAME = "SeDebugPrivilege";
        const string SE_RESTORE_NAME = "SeRestorePrivilege";
        const string SE_BACKUP_NAME = "SeBackupPrivilege";

        const Int32 SE_PRIVILEGE_ENABLED = 0x0002;

        const Int32 ERROR_NOT_ALL_ASSIGNED = 1300;

        [StructLayout(LayoutKind.Sequential)]
        struct PROCESSENTRY32
        
            UInt32 dwSize;
            UInt32 cntUsage;
            UInt32 th32ProcessID;
            IntPtr th32DefaultHeapID;
            UInt32 th32ModuleID;
            UInt32 cntThreads;
            UInt32 th32ParentProcessID;
            Int32 pcPriClassBase;
            UInt32 dwFlags;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            string szExeFile;
        

        const UInt32 TH32CS_SNAPPROCESS = 0x00000002;

        const Int32 INVALID_HANDLE_VALUE = -1;

        #endregion

        /* [DllImport("ADVAPI32.DLL", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            string lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation
            );*/

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

        [DllImport("KERNEL32.DLL", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool CloseHandle(IntPtr hHandle);
        #endregion

        [DllImport("kernel32.dll")]
        public static extern UInt32 WTSGetActiveConsoleSessionId();

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern Boolean LookupPrivilegeValue(IntPtr lpSystemName, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern Boolean AdjustTokenPrivileges(IntPtr TokenHandle, Boolean DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, Int32 BufferLength, IntPtr PreviousState, IntPtr ReturnLength);

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern Boolean SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, ref UInt32 TokenInformation, UInt32 TokenInformationLength);

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

        [DllImport("advapi32.DLL")]
        public static extern bool ImpersonateLoggedOnUser(IntPtr hToken); //handle to token for logged-on user

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);

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

        ///
        /// A process should call the RevertToSelf function after finishing 
        /// any impersonation begun by using the DdeImpersonateClient, 
        /// ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, 
        /// ImpersonateSelf, ImpersonateAnonymousToken or SetThreadToken function.
        /// If RevertToSelf fails, your application continues to run in the context of the client,
        /// which is not appropriate. You should shut down the process if RevertToSelf fails.
        /// RevertToSelf Function: http://msdn.microsoft.com/en-us/library/aa379317(VS.85).aspx
        ///
        /// A boolean value indicates the function succeeded or not.
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool RevertToSelf();



        [DllImport("shell32.dll")]
        public static extern bool SHGetSpecialFolderPath(IntPtr hwndOwner, [Out]StringBuilder lpszPath, int nFolder, bool fCreate);

        static string System32_SysWOW64_Folder()
        
            StringBuilder path = new StringBuilder(260);
            SHGetSpecialFolderPath(IntPtr.Zero, path, 0x0029, false);
            return path.ToString();
        

        //=================== Create directory =====================
        private static string CreateDirectoryAtSystemFolder()
        
            string tmpDir = null;
            string sysFolderPath = System32_SysWOW64_Folder();
            //string sysFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); //System32_SysWOW64_Folder();
            //string sysFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); //ProgramFiles_Folder();
            tmpDir = sysFolderPath + "\\logdata"; //ConfigurationSettings.AppSettings["ImageSavedPath"].ToString();
            if (!System.IO.Directory.Exists(@tmpDir))
            
                System.IO.Directory.CreateDirectory(@tmpDir);
            

            return tmpDir;
        
    

正如我在之前的编辑中提到的,之前在添加 DuplicateTokenExSetTokenInformation 之前,该服务能够运行应用程序,但无法在 System32 文件夹下登录。为了实现这一点,我更新了上面的代码,现在SetTokenInformation() 方法显示了权限错误。

编辑: 最后,该服务可以在登录的用户帐户上启动我的进程,并将我的所有日​​志文件写入正确的用户 appdata 路径。我已删除 SetTokenInformation。使用CreateEnvironmentBlock() 方法的以下代码帮助进程获得单独的用户环境并检索用户应用数据路径。

int dwCreationFlags = DETACHED_PROCESS;
IntPtr pEnv = IntPtr.Zero;
if (CreateEnvironmentBlock(ref pEnv, hToken, true))

    dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;

else

    pEnv = IntPtr.Zero;


bool ChildProcStarted = CreateProcessAsUser(
    hToken,                 // client's access token
    ChildProcName,          // file to execute
    null,                   // command line
    ref saProcess,          // pointer to process SECURITY_ATTRIBUTES
    ref saThread,           // pointer to thread SECURITY_ATTRIBUTES
    false,                  // handles are not inheritable
    dwCreationFlags,        // creation flags
    pEnv,                   // pointer to new environment block 
    null,                   // name of current directory 
    ref tStartUpInfo,       // pointer to STARTUPINFO structure
    out tProcessInfo        // receives information about new process
    );

【问题讨论】:

什么版本的 Windows? XP、Vista、7、8、8.1 还是 10? CreateProcessAsUser 返回什么错误代码? @HarryJohnston,两者都是 Windows 7。很抱歉我错过了该信息。我已经编辑了这个问题。在 CreateProcessAsUser 之后出现错误“请求的操作需要提升”。 这意味着您尝试启动的可执行文件将仅在管理员帐户中运行,要么是因为清单指定了requireAdministrator,要么因为它没有清单并且Windows启发式猜测它是一个安装程序,或者因为有一个兼容性设置要求它以管理员身份运行。 非常感谢哈利。我按照你的建议解决了这个问题。不过我有一些新问题。请看我的编辑。另外,如果可能,请发表您的评论作为答案,因为它解决了问题。 好的,因此 GUI 应用程序被有意配置为以管理员身份运行,以便它可以写入这些日志文件。正确的解决方案是让服务而不是 GUI 应用程序来进行日志记录。类似地,服务应该执行所有不允许用户中断的工作,GUI 应用程序应该只显示一个用户界面,因此用户是否终止它并不重要。不过,根据您的特定需求和情况,还有其他选择,例如,***.com/a/21127414/886887 【参考方案1】:

总结一下:

最初的问题是目标可执行文件被配置为需要 UAC 提升,但必须在用户的上下文中运行,即使用户不是管理员也是如此。这与它是从服务启动这一事实没有直接关系,但这是一个复杂的因素,因为这意味着启动应用程序的尝试失败而不是导致提升对话框。解决方法是将应用程序清单中的 requestedExecutionLevelrequireAdministrator 更改为默认值 asInvoker

当应用程序重新配置为不需要 UAC 提升时,它无法正常运行,因为它试图将日志文件写入全局文件夹。解决方案是将日志文件写入每个用户的合适位置。

最后一个问题是应用程序在从服务启动时未正确找到每个用户的位置。这是因为它获得了服务环境块的副本,而不是适合用户的副本。解决方案是使用CreateEnvironmentBlock() 生成合适的环境块并将其作为CreateProcessAsUser 的lpEnvironment 参数传递。

【讨论】:

这次我尝试使用 Windows XP 机器,结果 WTSEnumerateSessions 返回 false 并显示错误“绑定句柄无效”。我遇到了这个link。是该机器的Windows更新问题还是通过我的代码有一些解决方案?第一次安装我的 msi 设置时,该服务能够启动我的进程,但在重新启动后,该服务无法启动进程并显示上述错误消息。我检查过这台 XP 机器是否安装了 .Net framework 2、3 和 3.5。 该链接与此方案无关。最可能的原因是您没有为 WTS_CURRENT_SERVER_HANDLE (第一个参数)传递正确的值,这可能是因为 P/Invoke 声明不正确。如果您无法解决,我建议您发布一个新问题。

以上是关于对于某些版本的 Windows,Windows 服务无法在用户使用 WTSQueryUserToken 登录时启动交互过程的主要内容,如果未能解决你的问题,请参考以下文章

教你如何在WINDOWS Server2003上部署一个Asp.Net的网站

网站里的win8,core核心版又是啥版本

windows版切换到pageoffice国产版(支持uos和麒麟操作系统)

某些 Windows 8 和 8.1 上的 Unity3d 4.6 UI 图像错误

在windows 8.1下无法利用批处理文件(.bat)启动bukkit(minecraft水桶服)

使用 gcc c++ 获取 Windows 版本