2015-05-21 14 views
5

Chcę uruchomić zewnętrzny program z C#, aby całkowicie się odłączyć. Używam CreateProcess przez pinvoke, ponieważ Process.Start nie pozwala mi używać DETACHED_PROCESS. Również chcę, aby ta aplikacja przekierowywała wyjście do jakiegoś pliku.Jak uruchomić odłączony proces za pomocą programu cmd.exe bez konsoli?

Oto przykładowy kod:

  var processInformation = new ProcessUtility.PROCESS_INFORMATION(); 
      var securityInfo = new ProcessUtility.STARTUPINFO(); 
      var sa = new ProcessUtility.SECURITY_ATTRIBUTES(); 
      sa.Length = Marshal.SizeOf(sa); 

      // Create process with no window and totally detached 
      var result = ProcessUtility.CreateProcess(Path.Combine(Environment.SystemDirectory, "cmd.exe"), commandLineArguments, ref sa, ref sa, false, 
       ProcessUtility.DETACHED_PROCESS, IntPtr.Zero, null, ref securityInfo, out processInformation); 
  1. CommandLineArguments są mniej więcej tak: "/ c Foo.bat> Foo.log 2> & 1" Wszystko działa poprawnie i Foo.log jest zaludniony autor: Foo.bat. Żadne dodatkowe okno konsoli nie jest widoczne. IDEALNY.

  2. CommandLineArguments są mniej więcej tak: "/ c foo.exe> ​​Foo.log 2> & 1" foo.exe jest .NET Console Application. Foo.log nie jest zapełniony i Foo.exe jest uruchamiany w widocznym oknie konsoli. DZIWNE. Dlaczego zachowanie różni się od 1.?

  3. Tylko dla twoich informacji. CommandLineArguments są następujące: "/ c Foo.exe> ​​Foo.log 2> & 1" Foo.exe to aplikacja .NET dla systemu Windows. Wszystko działa dobrze, ale kiedy uruchamiam tę aplikację, po prostu z wiersza polecenia nie widzę wyjścia, ponieważ nie jest przydzielana żadna konsola.

Chcę 2. pracować tak samo jak 1. Dlaczego jest różnica?

AKTUALIZACJA: Nie chcę samodzielnie pisać Foo.log, ponieważ uruchomienie aplikacji zostanie zabite.

AKTUALIZACJA: Ok, napisałem kod, aby określić, że tylko jeden uchwyt jest dziedziczony, ale CreateProcess daje mi błąd 87 podczas wywoływania z EXTENDED_STARTUPINFO_PRESENT (nawet jeśli jest obecny i pusty).

Czy możesz mi pomóc, dlaczego?

public class ProcessUtility 
{ 
    // Process creation flags 
    const uint ZERO_FLAG = 0x00000000; 
    const uint CREATE_BREAKAWAY_FROM_JOB = 0x01000000; 
    const uint CREATE_DEFAULT_ERROR_MODE = 0x04000000; 
    const uint CREATE_NEW_CONSOLE = 0x00000010; 
    const uint CREATE_NEW_PROCESS_GROUP = 0x00000200; 
    const uint CREATE_NO_WINDOW = 0x08000000; 
    const uint CREATE_PROTECTED_PROCESS = 0x00040000; 
    const uint CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000; 
    const uint CREATE_SEPARATE_WOW_VDM = 0x00001000; 
    const uint CREATE_SHARED_WOW_VDM = 0x00001000; 
    const uint CREATE_SUSPENDED = 0x00000004; 
    const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400; 
    const uint DEBUG_ONLY_THIS_PROCESS = 0x00000002; 
    const uint DEBUG_PROCESS = 0x00000001; 
    const uint DETACHED_PROCESS = 0x00000008; 
    const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000; 
    const uint INHERIT_PARENT_AFFINITY = 0x00010000; 

    // Thread attributes flags 
    const uint PROC_THREAD_ATTRIBUTE_HANDLE_LIST = 0x00020002; 
    const uint PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000; 

    // File creation flags 
    const uint FILE_ACCESS_WRITE = 0x40000000; 

    // StartupInfo flags 
    const int STARTF_USESTDHANDLES = 0x00000100; 

    [StructLayout(LayoutKind.Sequential)] 
    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)] 
    struct STARTUPINFOEX 
    { 
     public STARTUPINFO StartupInfo; 
     public IntPtr lpAttributeList; 
    }; 

    [StructLayout(LayoutKind.Sequential)] 
    struct PROCESS_INFORMATION 
    { 
     public IntPtr hProcess; 
     public IntPtr hThread; 
     public Int32 dwProcessID; 
     public Int32 dwThreadID; 
    } 

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

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    static extern bool CreateProcess(
     string lpApplicationName, 
     string lpCommandLine, 
     ref SECURITY_ATTRIBUTES lpProcessAttributes, 
     ref SECURITY_ATTRIBUTES lpThreadAttributes, 
     bool bInheritHandles, 
     uint dwCreationFlags, 
     IntPtr lpEnvironment, 
     string lpCurrentDirectory, 
     [In] ref STARTUPINFO lpStartupInfo, 
     out PROCESS_INFORMATION lpProcessInformation); 

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    static extern bool CreateProcess(
     string lpApplicationName, 
     string lpCommandLine, 
     ref SECURITY_ATTRIBUTES lpProcessAttributes, 
     ref SECURITY_ATTRIBUTES lpThreadAttributes, 
     bool bInheritHandles, 
     uint dwCreationFlags, 
     IntPtr lpEnvironment, 
     string lpCurrentDirectory, 
     [In] ref STARTUPINFOEX lpStartupInfo, 
     out PROCESS_INFORMATION lpProcessInformation); 

    [DllImport("kernel32.dll")] 
    public static extern uint GetLastError(); 

    [DllImport("kernel32.dll", SetLastError = true)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool UpdateProcThreadAttribute(
     IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, 
     IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); 

    [DllImport("kernel32.dll", SetLastError = true)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool InitializeProcThreadAttributeList(
     IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); 

    [DllImport("kernel32.dll", SetLastError = true)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList); 

    [DllImport("kernel32.dll", SetLastError = true)] 
    static extern bool CloseHandle(IntPtr hObject); 

    [DllImport("kernel32.dll", SetLastError = true)] 
    static extern SafeFileHandle CreateFile(
     string lpFileName, 
     uint fileAccess, 
     [MarshalAs(UnmanagedType.U4)] FileShare fileShare, 
     SECURITY_ATTRIBUTES securityAttributes, 
     [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, 
     uint dwFlagsAndAttributes, 
     IntPtr hTemplateFile); 

    public static bool CreateProcessWithStdHandlesRedirect(string lpApplicationName, string lpCommandLine, string logFilename) 
    { 
     var startupInfo = new STARTUPINFOEX(); 
     startupInfo.StartupInfo.cb = Marshal.SizeOf(startupInfo); 

     try 
     { 
      var lpSize = IntPtr.Zero; 
      if (InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize) || lpSize == IntPtr.Zero) 
       return false; 
      startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize); 

      // Here startupInfo.lpAttributeList is initialized to hold 1 value 
      if (!InitializeProcThreadAttributeList(startupInfo.lpAttributeList, 1, 0, ref lpSize)) 
       return false; 

      var fileSecurityAttributes = new SECURITY_ATTRIBUTES(); 
      fileSecurityAttributes.Length = Marshal.SizeOf(fileSecurityAttributes); 
      // Create inheritable file handle 
      fileSecurityAttributes.bInheritHandle = true; 

      // Open log file for writing 
      using (var handle = CreateFile(logFilename, FILE_ACCESS_WRITE, FileShare.ReadWrite, 
       fileSecurityAttributes, FileMode.Create, 0, IntPtr.Zero)) 
      { 
       var fileHandle = handle.DangerousGetHandle(); 

       // Add filehandle to proc thread attribute list 
       if (!UpdateProcThreadAttribute(startupInfo.lpAttributeList, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_HANDLE_LIST, fileHandle, 
        (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero)) 
        return false; 

       startupInfo.StartupInfo.hStdError = fileHandle; 
       startupInfo.StartupInfo.hStdOutput = fileHandle; 
       // startupInfo.StartupInfo.hStdInput = ?; 
       startupInfo.StartupInfo.dwFlags = STARTF_USESTDHANDLES; 

       var processInformation = new PROCESS_INFORMATION(); 
       var securityAttributes = new SECURITY_ATTRIBUTES(); 
       securityAttributes.Length = Marshal.SizeOf(securityAttributes); 
       securityAttributes.bInheritHandle = true; 

       // Create process with no window and totally detached 
       return ProcessUtility.CreateProcess(lpApplicationName, lpCommandLine, ref securityAttributes, ref securityAttributes, true, 
        DETACHED_PROCESS | EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref startupInfo, out processInformation); 
      } 
     } 
     finally 
     { 
      if (startupInfo.lpAttributeList != IntPtr.Zero) 
      { 
       DeleteProcThreadAttributeList(startupInfo.lpAttributeList); 
       Marshal.FreeHGlobal(startupInfo.lpAttributeList); 
      } 
     } 
    } 
} 
+0

Jeśli zabijesz aplikację do uruchamiania, dlaczego nie użyć opcji 'Process.Start'? Ponieważ AFAIK (i jeśli dostaję ciebie poprawnie) rozpoczęty proces zostanie odłączony, gdy tylko "rodzic" wyjdzie ... – ChrFin

+0

Użyj metody '//Foo.bat> Foo.log 2> & 1" 'i włóż' Foo.exe 'wewnątrz' Foo.bat' Nie odpowiada na pytanie, ale rozwiązuje problem! ';-)' – Aacini

+0

Istnieje bardzo dziwne zachowanie podczas korzystania z Process.Start. Proces zostanie zabity, ale połączenie TCP pozostanie aktywne do momentu zakończenia ostatniego potomka z procesem Process.Start. To nie jest pożądane zachowanie. Nie ma problemu z używaniem flagi DETACHED_PROCESS – norekhov

Odpowiedz

3

W przypadku 1, wystąpienie cmd.exe że Ponowne uruchomienie może uruchomić sam plik wsadowy. Nie utworzono procesu potomnego.

W przypadku 2, uruchamiana instancja cmd.exe musi uruchomić aplikację konsoli jako proces potomny. Nie ma sposobu, aby wiedzieć, że chcesz, aby aplikacja nie otrzymała okna konsoli, więc kiedy wywołuje CreateProcess, nie używa ona flagi DETACHED_PROCESS, a Windows tworzy normalnie nową konsolę.

W przypadku 3 proces potomny nie jest aplikacją konsolową, więc system Windows nie tworzy dla niej konsoli, mimo że nie określono parametru DETACHED_PROCESS.

Typowym rozwiązaniem jest otwarcie foo.log złożyć samemu, uruchomić aplikację konsoli bezpośrednio (zamiast poprzez cmd.exe) i użyć struktury STARTUP_INFO przekazać uchwytu pliku dziennika jako standardowe wyjście i błąd standardowy dla nowego procesu. Po zwróceniu CreateProcess możesz zamknąć uchwyt pliku. Powielony uchwyt procesu potomnego nie zostanie zmieniony po zamknięciu procesu.

Jednak nie jestem pewien, w jaki sposób poprawnie byłoby o tym w .NET. W najlepszym wypadku jest to trochę trudne, ponieważ musisz sprawić, aby proces potomny dziedziczył uchwyt pliku dziennika bez konieczności dziedziczenia innych uchwytów w niewłaściwy sposób - prawdopodobnie jest to powodem, dla którego Process.Start powoduje problemy. Zalecaną praktyką jest użycie listy atrybutów procesów/wątków (InitializeProcThreadAttributeList) z wpisem PROC_THREAD_ATTRIBUTE_HANDLE_LIST. (Ale uchwyt dziennika nadal musi być dziedziczone.)

+0

Nie rozumiem, w jaki sposób 'DETACHED_PROCESS' wpływa na dziedziczenie uchwytów gniazd. W przeciwnym razie może użyć zamiast tego polecenia "CREATE_NO_WINDOW", więc dziecko odziedziczy konsolę z programu cmd.exe, który nie ma okna. – eryksun

+0

@eryksun: Nie wierzę, że 'DETACHED_PROCESS' wpływa na dziedziczenie. Domyślam się, że OP zaobserwował, że użycie 'CreateProcess' nie spowodowało problemu z gniazdem i założyło, że było to spowodowane flagą' DETACHED_PROCESS' kiedy to było faktycznie ponieważ ustawił 'bInheritHandles' na' FALSE'. Jednak nie próbowałem replikować problemu, aby to zweryfikować, więc jest to tylko domysły. –

+0

@eryksun: [Według Hansa] (http://stackoverflow.com/a/10753521/886887) opcja 'CREATE_NO_WINDOW' nie tworzy ukrytej konsoli, więc to nie pomogłoby. Ponownie, nie próbowałem tego sam. –

1

Utwórz skrypt VB, NoWindow.vbs, być może programowo na bieżąco, co następuje

CreateObject("Wscript.Shell").Run WScript.Arguments(0), 0, False 

od ciebie głównym aplikacji, zadzwoń cscript prosto z procesem .Start

Process.Start("cscript", "NoWindow.vbs \"cmd /c Foo.exe > Foo.log 2>&1 \" "); 

Sam skrypt vbs odłączy ten proces. Brak okna.

Moje testowanie ograniczało się do korzystania z Procexp w celu potwierdzenia, że ​​procesem Foo.exe został odłączony - Win7.

+0

Nie sądzisz, że to zbyt skomplikowane na takie łatwe zadanie? Myślę, że określenie dziedziczonych uchwytów jest znacznie prostszym rozwiązaniem. – norekhov

+0

Podobają mi się pomysły na skrypty nietoperzy i vbs w locie, nie sądzę, że to zbyt skomplikowane. To znaczy, że możesz mieć powód do aplikacji lub usługi, aby zrestartować ją samodzielnie - być może w celu automatycznej aktualizacji - po prostu wygeneruj plik nietoperza i uruchom go – Patrick

Powiązane problemy