2013-11-04 24 views
1

所以我檢查了很多很多網站,現在研究了幾天。而且我還沒有找到或提出我自己的解決方案來解決這個問題。C#Windows服務創建進程,但不執行它

我知道,自Windows Vista以來,Windows服務自從它在會話0中創建後就無法與什麼被認爲是可執行文件的GUI可執行文件進行交互,如控制檯應用程序和屬於非會話0的其他會話的其他軟件。

據微軟稱,這樣做的服務將是一個潛在的「病毒」。我理解他們思考的理由。但這是我們問題的唯一解決方案。

//This is how I am calling the process. 
public void startVM(string vmname) { 

    string cmdline = startvm --type headless VM2000"; 
    ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe"); 
    startInfo.WindowStyle = ProcessWindowStyle.Minimized; 
    startInfo.Arguments = string.Format(@"c:\vms\vboxmanage startvm {0}",vmname); 
    Process.Start(startInfo); 

} 

因此,這是發生了什麼:

我創建一個Windows服務,在啓動該服務將啓動一個進程。在這種情況下,「cmd.exe」。我已經檢查了很多次,我確定這個過程實際上是創建的。但是我想要cmd.exe執行的參數,實際命令......它們被忽略。他們永遠不會發生。我在別處測試了代碼,作爲一個庫,作爲一個Windows Form應用程序,它像鐘錶一樣工作。但是,作爲一項服務,它將無法正常工作。

我曾嘗試解決方案,如啓用與桌面交互。即使從註冊表項。我曾嘗試調用不同的可執行文件,它發生的是同樣的事情:它創建進程,但不執行命令或參數。

我讀過很多有這個問題...但是沒有解決方案已經找到所有這些網站,我看到了這個問題。即使是來自StackOverflow的用戶。

//Located in the service class inheriting from ServiceBase 
    protected override void OnStart(string[] args) 
    { 
     //System.Diagnostics.Debugger.Launch(); 
     IVBoxCom vBox = new VBoxCom(); 
     //This method calls the method you see above. 
     vBox.StartVM("WIN2K"); 

    } 

這是服務安裝程序類:

 ServiceInstaller installer = new ServiceInstaller(); 
     installer.ServiceName = "Steven-VBoxService"; //This has to be the exact Name of the Service that has ServiceBase Class 
     installer.DisplayName = "Steven-VBoxService"; 
     installer.StartType = ServiceStartMode.Manual; 
     base.Installers.Add(installer); 

     //Creates an Executable that convokes the Service previously installed. 
     //Note: In theory, I can create 10 Services, and run them in a single Service Process 
     ServiceProcessInstaller installer2 = new ServiceProcessInstaller(); 
     installer2.Account = ServiceAccount.LocalSystem; //Windows service. 
     //installer2.Password = "sh9852"; //Why would I used these options? 
     //installer2.Username = @"FITZMALL\hernandezs"; 
     installer2.Password = null; 
     installer2.Username = null; 
     base.Installers.Add(installer2); 

我注意到,當我要啓動該服務時,它會停留在「正在」,那麼它只是停止。 但過程cmd.exe或VBoxManage.exe得到創建,但從來沒有實際做任何事情。

+1

當你可以這樣做時,這從來就不是一個好主意。你應該這樣做的方式是編寫一些在登錄時運行並從服務請求它需要的東西,在該用戶的權限下運行,或者以管理員身份運行,因爲你需要處理該問題。 –

+0

根據白皮書,通過使用客戶機/服務器機制(如RPC或命名管道),仍可以從會話0中的服務與用戶會話中的UI機制進行通信。我解釋這意味着你的情況你需要爲cmd創建一個包裝器(用戶代理),它只需傳遞需要通過命名管道以字符串形式運行的命令。請參閱第6頁:http://msdn.microsoft.com/en-us/windows/hardware/gg463353.aspx –

+0

我閱讀了有關命名管道的工作...但未找到具體的解決方案。 我需要它沒有用戶,因爲我無法隨時瞭解所有管理員用戶。因此,本地系統是完美的 –

回答

2

所以唯一的選擇是欺騙操作系統。從Kernel創建一個Process實例,但改變誰是創建者。讓我詳細說明一下。

由於Windows Vista及更高版本......微軟認爲將Windows服務作爲服務與用戶GUI進行交互是一個壞主意(我同意在某些時候),因爲它可能會運行一種病毒每次在啓動。所以他們創建了一個叫做Session 0的東西。所有的服務都在這個Session中,這樣他們就不能和你的用戶(或Session 1 +)GUI交互。這意味着Windows服務無法訪問cmd.exe,VBoxManage.exe以及任何其他具有GUI交互功能的應用程序。

因此...解決該問題的方法是操縱操作系統,從平臺調用(Win 32 API)的內核創建進程,這對於C#中的日常開發人員來說並不常見。 從KernelDLL創建進程時,您可以訪問更改用戶或創建者。在這種情況下,我沒有將會話0創建爲進程,而是將其更改爲當前會話ID或當前用戶。這使得我的Windows服務工作成爲可能,就像我想要的一樣。

對於這個想法的工作,你必須閱讀很多關於KernelDll,advapi32.dll,主要是他們的方法和枚舉聲明,因爲它不是你可以引用到你的項目。這兩個需要進行P/Invoke才能使用它們。

我創建的以下類使您可以創建一個作爲當前用戶而不是作爲會話0的進程。因此解決了我原來的問題。

//Just use the Class Method no need to instantiate it: 
ApplicationLoader.CreateProcessAsUser(string filename, string args) 




[SuppressUnmanagedCodeSecurity] 
class ApplicationLoader 
{ 
    /// <summary> 
    /// No Need to create the class. 
    /// </summary> 
    private ApplicationLoader() { } 




    enum TOKEN_INFORMATION_CLASS 
    { 

     TokenUser = 1, 
     TokenGroups, 
     TokenPrivileges, 
     TokenOwner, 
     TokenPrimaryGroup, 
     TokenDefaultDacl, 
     TokenSource, 
     TokenType, 
     TokenImpersonationLevel, 
     TokenStatistics, 
     TokenRestrictedSids, 
     TokenSessionId, 
     TokenGroupsAndPrivileges, 
     TokenSessionReference, 
     TokenSandBoxInert, 
     TokenAuditPolicy, 
     TokenOrigin, 
     TokenElevationType, 
     TokenLinkedToken, 
     TokenElevation, 
     TokenHasRestrictions, 
     TokenAccessInformation, 
     TokenVirtualizationAllowed, 
     TokenVirtualizationEnabled, 
     TokenIntegrityLevel, 
     TokenUIAccess, 
     TokenMandatoryPolicy, 
     TokenLogonSid, 
     MaxTokenInfoClass 
    } 



    [StructLayout(LayoutKind.Sequential)] 
    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 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)] 
    public struct PROCESS_INFORMATION 
    { 
     public IntPtr hProcess; 
     public IntPtr hThread; 
     public Int32 dwProcessID; 
     public Int32 dwThreadID; 
    } 

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

    public enum SECURITY_IMPERSONATION_LEVEL 
    { 
     SecurityAnonymous, 
     SecurityIdentification, 
     SecurityImpersonation, 
     SecurityDelegation 
    } 

    public enum TOKEN_TYPE 
    { 
     TokenPrimary = 1, 
     TokenImpersonation 
    } 

    public const int GENERIC_ALL_ACCESS = 0x10000000; 
    public const int CREATE_NO_WINDOW = 0x08000000; 


    [DllImport("advapi32.dll", EntryPoint = "ImpersonateLoggedOnUser", SetLastError = true, 
      CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 
    public static extern IntPtr ImpersonateLoggedOnUser(IntPtr hToken); 

    [ 
     DllImport("kernel32.dll", 
      EntryPoint = "CloseHandle", SetLastError = true, 
      CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall) 
    ] 
    public static extern bool CloseHandle(IntPtr handle); 

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

    [ 
     DllImport("advapi32.dll", 
      EntryPoint = "DuplicateTokenEx") 
    ] 
    public static extern bool 
     DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess, 
         ref SECURITY_ATTRIBUTES lpThreadAttributes, 
         Int32 ImpersonationLevel, Int32 dwTokenType, 
         ref IntPtr phNewToken); 


    [DllImport("Kernel32.dll", SetLastError = true)] 
    //[return: MarshalAs(UnmanagedType.U4)] 
    public static extern IntPtr WTSGetActiveConsoleSessionId(); 

    [DllImport("advapi32.dll")] 
    public static extern IntPtr SetTokenInformation(IntPtr TokenHandle, IntPtr TokenInformationClass, IntPtr TokenInformation, IntPtr TokenInformationLength); 


    [DllImport("wtsapi32.dll", SetLastError = true)] 
    public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr Token); 

    private static int getCurrentUserSessionID() 
    { 
     uint dwSessionId = (uint)WTSGetActiveConsoleSessionId(); 

     //Gets the ID of the User logged in with WinLogOn 
     Process[] processes = Process.GetProcessesByName("winlogon"); 
     foreach (Process p in processes) 
     { 
      if ((uint)p.SessionId == dwSessionId) 
      { 

       //this is the process controlled by the same sessionID 
       return p.SessionId; 
      } 
     } 

     return -1; 
    } 



    /// <summary> 
    /// Actually calls and creates the application. 
    /// </summary> 
    /// <param name="filename"></param> 
    /// <param name="args"></param> 
    /// <returns></returns> 
    public static Process CreateProcessAsUser(string filename, string args) 
    { 
     //var replaces IntPtr 
     var hToken = WindowsIdentity.GetCurrent().Token; //gets Security Token of Current User. 


     var hDupedToken = IntPtr.Zero; 

     var pi = new PROCESS_INFORMATION(); 
     var sa = new SECURITY_ATTRIBUTES(); 
     sa.Length = Marshal.SizeOf(sa); 

     try 
     { 
      if (!DuplicateTokenEx(
        hToken, 
        GENERIC_ALL_ACCESS, 
        ref sa, 
        (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, 
        (int)TOKEN_TYPE.TokenPrimary, 
        ref hDupedToken 
       )) 
       throw new Win32Exception(Marshal.GetLastWin32Error()); 




      var si = new STARTUPINFO(); 
      si.cb = Marshal.SizeOf(si); 
      si.lpDesktop = ""; 

      var path = Path.GetFullPath(filename); 
      var dir = Path.GetDirectoryName(path); 

      //Testing 
      uint curSessionid = (uint)ApplicationLoader.getCurrentUserSessionID(); 

      if (!WTSQueryUserToken(curSessionid,out hDupedToken)) 
      { 
       throw new Win32Exception(Marshal.GetLastWin32Error()); 
      } 

      // Revert to self to create the entire process; not doing this might 
      // require that the currently impersonated user has "Replace a process 
      // level token" rights - we only want our service account to need 
      // that right. 
      using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero)) 
      { 
       if (!CreateProcessAsUser(
             hDupedToken, 
             path, 
             string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args), 
             ref sa, ref sa, 
             false, CREATE_NO_WINDOW, IntPtr.Zero, 
             dir, ref si, ref pi 
           )) 
        throw new Win32Exception(Marshal.GetLastWin32Error()); 
      } 

      return Process.GetProcessById(pi.dwProcessID); 
     } 
     finally 
     { 
      if (pi.hProcess != IntPtr.Zero) 
       CloseHandle(pi.hProcess); 
      if (pi.hThread != IntPtr.Zero) 
       CloseHandle(pi.hThread); 
      if (hDupedToken != IntPtr.Zero) 
       CloseHandle(hDupedToken); 
     } 
    } 
} 

按照您的意願修改班級。如果你不知道如何工作,請小心不要觸及很多最初的枚舉聲明或外部方法。

0

原始代碼的問題(如問題所示)非常簡單:您忽略了/c參數cmd.exe以指示它運行您的命令。

換句話說,你試圖做到這一點:

cmd c:\vms\vboxmanage startvm {0} 

,而你需要做的是:

cmd /c c:\vms\vboxmanage startvm {0} 

或本:

c:\vms\vboxmanage startvm {0} 

現在,那說,有某些不喜歡在服務競爭中運行的應用程序XT。請注意,這不是因爲它們顯示GUI,而是出於其他幾個原因之一。 (例如,某些應用程序只在Explorer在同一臺桌面上運行時纔可用)。

vboxmanage可能是這樣的應用程序,但如果您沒有忘記/c

+0

我確實使用/ c和/ k ...但是就像我說的那樣沒有用。我仍然沒有執行這個過程。 –

+0

我看到你說服務實際上並沒有正常啓動;也許'startVM'從來沒有真的被調用過?你有沒有試過運行一個比較簡單的命令,例如'cmd/c dir'來查看問題是否特定於'vboxmanage'?你可以發佈完整的代碼,或通過電子郵件發送給我? –

+0

另一種可能性是由於某些原因,.NET「Process」類在服務上下文中不起作用。我的代碼都是原生的Win32,所以如果有一個特定於.NET的問題,我可能沒有聽說過它。 –