2010-11-25 55 views
37

我需要從Windows服務啓動程序。該程序是一個用戶UI應用程序。此外,應用程序應該在特定用戶帳戶下啓動。如何從Windows服務啓動進程到當前登錄的用戶會話

的問題是,一個窗口服務會話#0中運行,但在用戶會話已記錄的是1,2等

所以,問題是:如何從一個窗口服務啓動的過程中這樣的它在當前登錄的用戶會話中運行的方式?

我強調,這個問題不是關於如何在特定帳戶下啓動進程(這很明顯 - Process.Start(new ProcessStartInfo(「..」){UserName = ..,Password = ..} ))。即使我安裝我的Windows以當前用戶帳戶運行,該服務仍將在會話#0中運行。 設置「允許服務與桌面交互」不起作用。

我的windows服務是基於.net的。

UPDATE: 首先,.NET在這裏沒有任何關係,它實際上是純粹的Win32的東西。 以下是我正在做的事情。下面的代碼是在我的窗口服務(通過P/Inkove使用Win32函數的C#,我跳過導入簽名,他們都在這裏是 - 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。Exitcode:「+ exitCode。 Exitcode是3221225794. 並沒有任何新的記事本啓動。 我錯在哪裏?

回答

24

A blog on MSDN describes a solution

這是關於在Vista/Windows 7的服務開始在交互式會話的新進程了不起的有用職位。

對於非本地系統服務,其基本思想是:

  • 枚舉過程中得到的資源管理器的手柄。

  • OpenProcessToken應該爲您提供訪問令牌。 注意:您的服務運行的帳戶必須具有適當的權限才能調用此API並獲取流程令牌。

  • 一旦你有令牌調用CreateProcessAsUser令牌。該令牌已經擁有正確的會話ID。

6

這樣做不好主意。儘管可能並非完全不可能,但微軟盡一切努力盡可能地做到這一點,因爲它可以實現所謂的粉碎攻擊。看看拉里·奧斯特曼wrote about it back in 2005

主要的原因是一個糟糕的主意是,交互式服務使一類被稱爲威脅「瘙」攻擊(因爲他們「打破的窗戶」,我相信)。

如果你做「粉碎攻擊」的搜索,你可以看到的這些安全威脅是如何工作的一些細節。微軟還發布了KB article 327618,它擴展了有關交互式服務的文檔,邁克爾霍華德爲MSDN圖書館撰寫了一篇關於交互式服務的文章。最初,破解攻擊發生在具有後臺窗口消息泵(已經固定)的Windows組件之後,但它們也被用來攻擊彈出UI的第三方服務。

第二個原因,這是一個壞主意的是,SERVICE_INTERACTIVE_PROCESS標誌根本無法正常工作。服務UI在系統會話中彈出(通常爲會話0)。另一方面,如果用戶正在另一個會話中運行,則用戶從不會看到該UI。在另一個會話中有用戶連接的兩種主要場景 - 終端服務和快速用戶切換。 TS並不常見,但在有多人使用單臺計算機的家庭情景中,FUS通常是啓用的(例如,我們廚房的計算機上幾乎都有4人登錄)。

的第三個原因是交互式服務是一個壞主意的是,互動服務,不能保證與Windows Vista的工作:)作爲安全強化的過程,進入Windows Vista中的一部分,交互式用戶登錄到比其他會議系統會話 - 第一個交互式用戶在會話1中運行,而不是在會話0中運行。這具有完全切斷膝蓋碎裂攻擊的效果 - 用戶應用程序無法與服務中運行的高權限窗口交互。

建議的解決方法是在用戶的系統托盤中使用應用程序。

如果你可以放心地忽略上述問題,並警告,可能會遵循這裏給出的說明:

Developing for Windows: Session 0 Isolation

+2

我不希望我的服務是交互式的(顯示任何UI),我只希望它能夠啓動另一個進程(在用戶會話中)。這有點不同,不是嗎? – Shrike 2010-11-25 15:23:35

+0

@Shrike:如果你不想展示一個UI,那麼爲什麼僅僅在session 0中啓動這個過程還不夠?我認爲您需要添加更多關於爲什麼該流程需要交互式會話的細節,以及爲什麼建議的解決方法不適合您。 – 2010-11-25 16:01:54

+0

有兩個角色:Windows服務和UI程序。服務器itseft不顯示UI,它只是啓動程序(作爲一個新進程)。程序_has_UI(並需要會話1)。我的服務是一種看門狗,定期「ping」程序並在出現錯誤時啓動/重啓。我在原帖中添加了一些說明 – Shrike 2010-11-25 17:37:14

1

我不知道該怎麼做,在.NET ,但一般情況下,您需要使用Win32 API CreateProcessAsUser()函數(或者其他任何.NET equivilent函數),指定所需的用戶訪問令牌和桌面名稱。這是我在我的C++服務中使用的,它工作正常。

28

伯勞答案的問題是,它不適用於通過RDP連接的用戶。
這是我的解決方案,它可以在創建過程之前正確確定當前用戶的會話。它已經過測試對XP運行和7

https://github.com/murrayju/CreateProcessAsUser

需要被包裹成一個單一的.NET類的靜態方法的一切:

public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible) 
5

這裏是我是如何實現它。 它將嘗試以當前登錄的用戶(來自服務)啓動進程。 這是基於幾個資源放在一起工作。

它實際上是純粹的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; 
} 
0

實現@murrayju代碼到Windows服務上的W10。從Program Files運行可執行文件總是在VS中拋出Error -2。我相信這是由於Service的開始路徑設置爲System32。 指定的workDir直到我之前添加以下行CreateProcessAsUserStartProcessAsCurrentUser沒有解決的問題:

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

PS:這應該是一個評論,而不是一個答案,但我不沒有所需的聲望呢。花了我一段時間去調試,希望能節省一些時間。

0

接受的答案在我的情況下不起作用,因爲我開始的應用程序需要管理員權限。我所做的是我創建了一個批處理文件來啓動應用程序。它包含以下內容:

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

然後,我將此文件的位置傳遞給StartProcessAsCurrentUser()方法。訣竅了。

相關問題