代码之家  ›  专栏  ›  技术社区  ›  Shrike

如何从Windows服务启动进程到当前登录用户的会话

  •  44
  • Shrike  · 技术社区  · 15 年前

    我需要从Windows服务启动程序。该程序是一个用户界面应用程序。此外,该应用程序应该在特定的用户帐户下启动。

    问题是窗口服务在会话0中运行,但登录的用户会话为1、2等。

    所以问题是:如何从窗口服务启动进程,使其在当前登录的用户会话中运行?

    我强调的是,问题不在于如何在特定帐户下启动进程(这很明显是-process.start(new processstartinfo(“..”)用户名=,密码=.))。即使我将Windows安装为在当前用户帐户下运行,该服务仍将在会话0中运行。 设置“允许服务与桌面交互”没有帮助。

    我的Windows服务基于.NET。

    更新 : 首先,.NET与此无关,它实际上是纯粹的win32操作。 这就是我要做的。以下代码在我的Windows服务中(c通过p/inkove使用win32函数,我跳过了导入签名,它们都在这里- http://www.pinvoke.net/default.aspx/advapi32/CreateProcessWithLogonW.html ):

        var startupInfo = new StartupInfo()
            {
                lpDesktop = "WinSta0\\Default",
                cb = Marshal.SizeOf(typeof(StartupInfo)),
            };
        var processInfo = new ProcessInformation();
        string command = @"c:\windows\Notepad.exe";
        string user = "Administrator";
        string password = "password";
        string currentDirectory = System.IO.Directory.GetCurrentDirectory();
        try
        {
            bool bRes = CreateProcessWithLogonW(user, null, password, 0,
                command, command, 0,
                Convert.ToUInt32(0),
                currentDirectory, ref startupInfo, out processInfo);
            if (!bRes)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        catch (Exception ex)
        {
            writeToEventLog(ex);
            return;
        }
        WaitForSingleObject(processInfo.hProcess, Convert.ToUInt32(0xFFFFFFF));
        UInt32 exitCode = Convert.ToUInt32(123456);
        GetExitCodeProcess(processInfo.hProcess, ref exitCode);
        writeToEventLog("Notepad has been started by WatchdogService. Exitcode: " + exitCode);
    
        CloseHandle(processInfo.hProcess);
        CloseHandle(processInfo.hThread);
    

    代码转到“记事本已由WatchDogService启动”行。退出代码:“+退出代码。出口代码为3221225794。 没有新的记事本启动。 我哪里错了?

    8 回复  |  直到 8 年前
        1
  •  36
  •   Hakan Fıstık MuriloKunze    9 年前

    Shrike的答案的问题是,它不能与通过RDP连接的用户一起工作。
    这是我的解决方案,它在创建进程之前正确地确定了当前用户的会话。它已经在XP和7上进行了测试。

    https://github.com/murrayju/CreateProcessAsUser

    您所需要的一切都用静态方法包装成一个.NET类:

    public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible)
    
        2
  •  27
  •   Thomas Weller    8 年前

    A blog on MSDN describes a solution

    这篇关于在Vista/7上的Windows服务的交互式会话中启动新进程的文章非常有用。

    对于非本地系统服务,基本思想是:

    • 枚举进程以获取资源管理器的句柄。

    • OpenProcessToken应该为您提供访问令牌。 注意:运行服务的帐户必须具有调用此API和获取进程令牌的适当权限。

    • 一旦你有了令牌,就用这个令牌调用createprocessusser。此令牌已具有正确的会话ID。

        3
  •  8
  •   Dirk Vollmar    15 年前

    这样做是个坏主意。虽然可能并非完全不可能,但微软竭尽所能使这一切变得尽可能困难,因为它使所谓的 破碎攻击 . 看看拉里·奥斯特曼 wrote about it back in 2005 :

    这是一个坏主意的主要原因是,交互式服务能够实现一类称为“粉碎”攻击的威胁(我相信是因为它们“粉碎窗口”)。

    如果您搜索“粉碎攻击”,您可以看到这些安全威胁如何工作的一些细节。微软也发布了 KB article 327618 它扩展了有关交互式服务的文档,MichaelHoward为msdn库撰写了一篇关于交互式服务的文章。最初,粉碎攻击是针对具有后台窗口消息泵(已修复很久)的Windows组件的,但它们也被用于攻击弹出UI的第三方服务。

    第二个不好的原因是服务交互进程标志不能正常工作。服务UI在系统会话(通常为会话0)中弹出。另一方面,如果用户正在另一个会话中运行,则用户永远不会看到UI。有两个主要场景让用户在另一个会话中进行连接-终端服务和快速用户切换。TS并不常见,但是在家庭场景中,多人使用一台计算机时,通常会启用fus(例如,我们有4个人在厨房的计算机上几乎一直登录)。

    交互服务是坏主意的第三个原因是交互服务不能保证与Windows Vista一起工作:)作为进入Windows Vista的安全强化过程的一部分,交互用户登录到系统会话以外的会话-第一个交互用户在会话1中运行,而不是会话0。这样做的效果是完全减少了对膝盖的攻击——用户应用程序无法与运行在服务中的高权限窗口交互。

    建议的解决方法是使用用户系统托盘中的应用程序。

    如果您可以安全地忽略上述问题和警告,则可以按照以下说明操作:

    Developing for Windows: Session 0 Isolation

        4
  •  6
  •   Tobias81    11 年前

    下面是我如何实现它。 它将尝试以当前登录用户的身份(从服务)启动进程。 这是基于将几个来源整合到一起,形成一些有用的东西。

    它实际上是纯粹的Win32/C++,所以对原始问题可能没有100%的帮助。 但我希望它能为其他人节省一些时间来寻找类似的东西。

    它需要Windows XP/2003(不适用于Windows 2000)。 必须链接到wtsapi32.lib

    #define WINVER 0x0501
    #define _WIN32_WINNT 0x0501
    #include <Windows.h>
    #include <WtsApi32.h>
    
    bool StartInteractiveProcess(LPTSTR cmd, LPCTSTR cmdDir) {
        STARTUPINFO si;
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        si.lpDesktop = TEXT("winsta0\\default");  // Use the default desktop for GUIs
        PROCESS_INFORMATION pi;
        ZeroMemory(&pi, sizeof(pi));
        HANDLE token;
        DWORD sessionId = ::WTSGetActiveConsoleSessionId();
        if (sessionId==0xffffffff)  // Noone is logged-in
            return false;
        // This only works if the current user is the system-account (we are probably a Windows-Service)
        HANDLE dummy;
        if (::WTSQueryUserToken(sessionId, &dummy)) {
            if (!::DuplicateTokenEx(dummy, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &token)) {
                ::CloseHandle(dummy);
                return false;
            }
            ::CloseHandle(dummy);
            // Create process for user with desktop
            if (!::CreateProcessAsUser(token, NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) {  // The "new console" is necessary. Otherwise the process can hang our main process
                ::CloseHandle(token);
                return false;
            }
            ::CloseHandle(token);
        }
        // Create process for current user
        else if (!::CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi))  // The "new console" is necessary. Otherwise the process can hang our main process
            return false;
        // The following commented lines can be used to wait for the process to exit and terminate it
        //::WaitForSingleObject(pi.hProcess, INFINITE);
        //::TerminateProcess(pi.hProcess, 0);
        ::CloseHandle(pi.hProcess);
        ::CloseHandle(pi.hThread);
        return true;
    }
    
        5
  •  5
  •   Tono Nam    13 年前

    我在这里找到了解决方案:

    http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

    我认为这是一个很好的链接。

        6
  •  2
  •   Remy Lebeau    15 年前

    我不知道如何在.NET中实现它,但一般来说,您需要使用win32 API CreateProcessAsUser() 函数(或其.NET要求的任何内容),指定所需用户的访问令牌和桌面名称。这是我在C++服务中使用的,它工作得很好。

        7
  •  1
  •   Alex Ureche    8 年前

    实施 默里尤 编码到w10上的Windows服务。从程序文件运行可执行文件时总是抛出错误-2,而我认为这是由于服务的启动路径设置为system32。 指定 工作迪尔 在添加以下行之前没有解决该问题 创建进程计数器 在里面 开始处理当前用户 :

    if (workDir != null)
       Directory.SetCurrentDirectory(workDir);
    

    附言:这应该是一个评论,而不是一个回答,但我还没有必要的声誉。我花了一段时间调试,希望能节省一些时间。

        8
  •  1
  •   Nilufar Makhmudova    8 年前

    在我的情况下,接受的答案不起作用,因为我启动的应用程序需要管理员权限。我所做的是创建一个批处理文件来启动应用程序。它包含以下内容:

    C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe  "%~dp0MySoft.exe"
    

    然后,我将此文件的位置传递给startprocessAscurrentUser()方法。做了这个把戏。

    推荐文章