从 Vista 上的服务中使用 CreateProcessAsUser 的桌面问题

Posted

技术标签:

【中文标题】从 Vista 上的服务中使用 CreateProcessAsUser 的桌面问题【英文标题】:Desktop problem with using CreateProcessAsUser from a service on Vista 【发布时间】:2010-02-06 06:30:01 【问题描述】:

我正在使用 VC++ 从 Vista 上使用 CreateProcessAsUser 的服务创建进程。当我通过本地登录机器执行此代码时,进程创建成功。当我使用远程桌面 (mstsc) 从另一台机器登录到我的机器并启动我的应用程序时,相同的代码失败。

我可以理解,当我进行远程登录时,活动桌面被视为远程桌面。谁能帮我获取当前登录用户桌面的桌面名称而不是远程桌面的桌面名称。

这是我的代码

ACTIVECONSOLESESSIONIDFUNC lpfnProc;    // WTSGetActiveConsoleSessionId function pointer
HMODULE hModule = NULL;         // Instance for kernel32.dll library
DWORD dwSessionId = 0;          // Session ID
HANDLE hToken = NULL;           // Active session token
HANDLE hDupToken = NULL;        // Duplicate session token
WCHAR szErr[1024] = 0;

LPVOID lpEnvironment = NULL;            // Environtment block

// Get the active session ID
hModule = LoadLibrary(KERNEL32LIB);
if(!hModule)

   //wsprintf(szErr, L"LoadLibrary Error: %d", GetLastError());
   return;


lpfnProc = (ACTIVECONSOLESESSIONIDFUNC)GetProcAddress(hModule,"WTSGetActiveConsoleSessionId"); 

dwSessionId = lpfnProc();

// Get token of the logged in user by the active session ID 
BOOL bRet = WTSQueryUserToken(dwSessionId, &hToken);

if (!bRet)
       
    //wsprintf(szErr, L"WTSQueryUserToken Error: %d", GetLastError());
    return;


// Get duplicate token from the active logged in user's token
bRet = DuplicateTokenEx(hToken,     // Active session token
                 MAXIMUM_ALLOWED,           // Desired access
                         NULL,                      // Token attributes                                         
                         SecurityIdentification,    // Impersonation level
                         TokenPrimary,              // Token type
                         &hDupToken);               // New/Duplicate token
    if (!bRet)
    
        //wsprintf(szErr, L"DuplicateTokenEx Error: %d", GetLastError());
        return;
    

    // Get all necessary environment variables of logged in user
    // to pass them to the process

    bRet = CreateEnvironmentBlock(&lpEnvironment, // Environment block
                                hDupToken,        // New token
                                FALSE);           // Inheritence
    if(!bRet)
    
        //wsprintf(szErr, L"CreateEnvironmentBlock Error: %d", GetLastError());
        return;
    

    HDESK hdeskInput=OpenInputDesktop(0, FALSE, 0); // does not set GetLastError(), so GetLastError() is arbitrary if NULL is returned
    if( hdeskInput==NULL ) 
        TRACE( "hdeskInput==NULL" );
        return false;
    

    // Initialize Startup and Process info  
    startupInfo->cb = sizeof(STARTUPINFO);
    startupInfo->lpDesktop = TEXT("winsta0\\default");



    // Start the process on behalf of the current user 

    BOOL returnCode = CreateProcessAsUser(hDupToken, 
                                applicationName, 
                                commandLine, 
                                NULL,
                                NULL,
                                FALSE,
                                NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
                                lpEnvironment,
                                NULL,
                                startupInfo,
                                &processInformation);

谢谢, F

【问题讨论】:

【参考方案1】:

这里有完整的工作代码来做你想做的事

//Function to run a process as active user from windows service
void ImpersonateActiveUserAndRun(WCHAR* path, WCHAR* args)

    DWORD session_id = -1;
    DWORD session_count = 0;

    WTS_SESSION_INFOA *pSession = NULL;


    if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
    
        //log success
    
    else
    
        //log error
        return;
    

    for (int i = 0; i < session_count; i++)
    
        session_id = pSession[i].SessionId;

        WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
        WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;

        DWORD bytes_returned = 0;
        if (::WTSQuerySessionInformation(
            WTS_CURRENT_SERVER_HANDLE,
            session_id,
            WTSConnectState,
            reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
            &bytes_returned))
        
            wts_connect_state = *ptr_wts_connect_state;
            ::WTSFreeMemory(ptr_wts_connect_state);
            if (wts_connect_state != WTSActive) continue;
        
        else
        
            //log error
            continue;
        

        HANDLE hImpersonationToken;

        if (!WTSQueryUserToken(session_id, &hImpersonationToken))
        
            //log error
            continue;
        


        //Get real token from impersonation token
        DWORD neededSize1 = 0;
        HANDLE *realToken = new HANDLE;
        if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
        
            CloseHandle(hImpersonationToken);
            hImpersonationToken = *realToken;
        
        else
        
            //log error
            continue;
        


        HANDLE hUserToken;

        if (!DuplicateTokenEx(hImpersonationToken,
            //0,
            //MAXIMUM_ALLOWED,
            TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
            NULL,
            SecurityImpersonation,
            TokenPrimary,
            &hUserToken))
        
            //log error
            continue;
        

        // Get user name of this process
        //LPTSTR pUserName = NULL;
        WCHAR* pUserName;
        DWORD user_name_len = 0;

        if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
        
            //log username contained in pUserName WCHAR string
        

        //Free memory                         
        if (pUserName) WTSFreeMemory(pUserName);

        ImpersonateLoggedOnUser(hUserToken);

        STARTUPINFOW StartupInfo;
        GetStartupInfoW(&StartupInfo);
        StartupInfo.cb = sizeof(STARTUPINFOW);
        //StartupInfo.lpDesktop = "winsta0\\default";

        PROCESS_INFORMATION processInfo;

        SECURITY_ATTRIBUTES Security1;
        Security1.nLength = sizeof SECURITY_ATTRIBUTES;

        SECURITY_ATTRIBUTES Security2;
        Security2.nLength = sizeof SECURITY_ATTRIBUTES;

        void* lpEnvironment = NULL;

        // Get all necessary environment variables of logged in user
        // to pass them to the new process
        BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
        if (!resultEnv)
        
            //log error
            continue;
        

        WCHAR PP[1024]; //path and parameters
        ZeroMemory(PP, 1024 * sizeof WCHAR);
        wcscpy(PP, path);
        wcscat(PP, L" ");
        wcscat(PP, args);

        // Start the process on behalf of the current user 
        BOOL result = CreateProcessAsUserW(hUserToken, 
            NULL,
            PP,
            //&Security1,
            //&Security2,
            NULL,
            NULL,
            FALSE, 
            NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
            //lpEnvironment,
            NULL,
            //"C:\\ProgramData\\some_dir",
            NULL,
            &StartupInfo,
            &processInfo);

        if (!result)
        
            //log error
        
        else
        
            //log success
        

        DestroyEnvironmentBlock(lpEnvironment);

        CloseHandle(hImpersonationToken);
        CloseHandle(hUserToken);
        CloseHandle(realToken);

        RevertToSelf();
    

    WTSFreeMemory(pSession);

【讨论】:

说得好,赞!【参考方案2】:

什么是启动服务以创建新流程,因为这与合适的答案有关? WTSGetActiveConsoleSessionId 获取当前物理控制台会话的会话 ID,而不是您意义上的“活动”会话 ID。对于终端服务,可能有许多不同的用户都连接到不同的会话,因此您不能只选择一个并希望,但实际上可能有也可能没有实际登录的用户。

一种方法是使用WTSEnumerateSessions 列出所有活动会话并尝试找到您真正想要的会话,您可以使用WTSQuerySessionInformation 之类的名称来获取该会话的用户名。

如果您的服务这样做是为了响应您的用户帐户的某些请求(而不是对某些自动事件采取行动),那么如果您使用诸如 RPC/Named Pipes/DCOM 等之类的东西,您总是可以模拟用户(假设安全性设置为允许非匿名/识别模拟)并将线程的令牌复制到主令牌并使用它。

【讨论】:

谢谢泰伦。我上面粘贴的代码是我通过浏览网络得到的。我对桌面或会话一无所知。我希望你明白我的问题。我的问题是我应该从我的机器登录到我的桌面,然后应用程序运行正常,即。我能够从服务创建进程,但是当我从另一台机器(mstsc)登录到我的桌面时,我的服务无法创建进程。因为它在调用 WtsQueryUserToken 时失败,错误为 1008 ERROR_NO_TOKEN;我得到的会话 id 是 3,而我的实际是 1。请帮忙。 如前所述,实际上创建进程的是什么,如上面的代码是如何被调用的以及在什么上下文中。 我的应用程序有一个在系统帐户下运行的服务创建进程。我想创建一个单独的进程来处理服务的 GUI 相关任务。当我的服务从应用程序接收到管道消息时,尝试创建进程。【参考方案3】:

经过大量谷歌搜索后,我能够找到解决问题的方法。

这是我找到解决方案的链接。 http://www.codeproject.com/KB/vista-security/interaction-in-vista.aspx

感谢每一位试图帮助我的人。

【讨论】:

当然我希望你是那台机器上唯一的人,否则你仍然可以在另一个桌面上随机创建一个进程:) 所以即使在这种情况下我也需要解决问题。我需要做什么?

以上是关于从 Vista 上的服务中使用 CreateProcessAsUser 的桌面问题的主要内容,如果未能解决你的问题,请参考以下文章

Visual Studio 2008 + Windows Vista 上的嵌入式 Web 服务器

Windows XP 与 Vista/7 上的 MS Crypto API 行为

带有 .NET 安装程序的 Windows Vista 上的文件安全性

如何使用 MFC 创建类似于 Vista 的扩展按钮?

Windows Vista 发布 | 历史上的今天

无法从 Vista x64 Windows 服务连接到 SQL Server 2005 Db