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

Tru*_*ueD 5 c# windows windows-services interopservices

按照此链接,我在我的 C# 解决方案中实现了WTSQueryUserTokenCreateProcessAsUserWrapper.LaunchChildProcess("app_path")OnStart我的 Windows 服务中调用了该方法,该方法作为“LocalSystem”运行。它能够启动可以与桌面交互的进程,但适用于 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;
Run Code Online (Sandbox Code Playgroud)

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

编辑: 有 2 个app.manifest文件,一个在服务项目中,另一个在 win 表单(流程)项目中。我删除这两个文件,建立了msiWiX和再测试。现在它在每台机器上成功运行,每个登录的用户帐户都会启动该进程。但是,现在造成了一个问题。其实我需要在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_CURREN

Har*_*ton 4

总结一下:

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

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

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