2010-04-13 7 views
41

Mam projekt, w którym mam wiele wystąpień uruchomionej aplikacji, z których każda została uruchomiona z różnymi argumentami wiersza polecenia. Chciałbym mieć sposób na kliknięcie przycisku z jednej z tych instancji, które następnie zamyka wszystkie wystąpienia i uruchamia je ponownie z tymi samymi argumentami wiersza poleceń.Czy można uzyskać argumenty wiersza poleceń innych procesów z .NET/C#?

Mogę uzyskać same procesy z łatwością przez Process.GetProcessesByName(), ale ilekroć to zrobię, właściwość StartInfo.Arguments jest zawsze pustym ciągiem. Wygląda na to, że ta właściwość jest ważna tylko przed rozpoczęciem procesu.

This question ma pewne sugestie, ale wszystkie są w kodzie natywnym i chciałbym to zrobić bezpośrednio z .NET. Jakieś sugestie?

+1

Czy masz kontrolę nad aplikacją, którą próbujesz ponownie uruchomić? –

+0

Tak, mam pełną kontrolę nad kodem aplikacji, którą próbuję ponownie uruchomić - zawsze będzie to kolejne wystąpienie tej samej aplikacji, z której korzystam. Jest to aplikacja WPF, jeśli to robi różnicę, ale nie sądzę, że powinna. –

+2

Zgodnie z artykułem MSDN na temat StartInfo (http://msdn.microsoft.com/en-us/library/system.diagnostics.process.startinfo.aspx), obiekt StartInfo zawiera tylko informacje, jeśli proces został uruchomiony przy użyciu procesu. Początek. Wskazuje również, że funkcja StartInfo będzie pusta podczas korzystania z funkcji GetProcesses *. – Corin

Odpowiedz

60

to przy użyciu wszystkich zarządzanych obiektów, ale robi zanurzyć w dół do sfery WMI:

private static void Main() 
{ 
    foreach (var process in Process.GetProcesses()) 
    { 
     try 
     { 
      Console.WriteLine(process.GetCommandLine()); 
     } 
     catch (Win32Exception ex) when ((uint)ex.ErrorCode == 0x80004005) 
     { 
      // Intentionally empty. 
     } 
    } 
} 

private static string GetCommandLine(this Process process) 
{ 
    var commandLine = new StringBuilder(process.MainModule.FileName); 

    commandLine.Append(" "); 
    using (var searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id)) 
    { 
     foreach (var @object in searcher.Get()) 
     { 
      commandLine.Append(@object["CommandLine"]); 
      commandLine.Append(" "); 
     } 
    } 

    return commandLine.ToString(); 
} 
+1

Jedyną rzeczą, na którą należy zwrócić uwagę, jest AccessDenied w niektórych procesach. –

+2

Mała nuta; Na moim komputerze (Win 10) linia poleceń zwrócona przez WMI zawiera nazwę uruchomionego programu, więc nie ma potrzeby inicjalizowania StringBuilder za pomocą process.MainModule.FileName. Nadal dobry kawałek kodu, jest w moim projekcie teraz .. Thanx! –

+0

Kiedy searcher.Get() zwróci kolekcję zawierającą wiele elementów? Co to oznacza, kiedy to się dzieje? – WawaBrother

1

The StartInfo.Arguments jest używany tylko podczas uruchamiania aplikacji, nie jest zapisem linii poleceń argumenty. Jeśli uruchomisz aplikacje za pomocą argumentów wiersza poleceń, zapisz argumenty, gdy wejdą one do twojej aplikacji. W najprostszym przypadku możesz zapisać je w pliku tekstowym, a kiedy naciśniesz przycisk, zamknij wszystkie procesy z wyjątkiem tego, które ma wydarzenie naciśnięcia przycisku. Odpal nową aplikację i podaj ją w nowym argumencie wiersza poleceń. Podczas gdy stara aplikacja zostaje zamknięta, nowa aplikacja odpala wszystkie nowe procesy (po jednym dla każdej linii w pliku) i wyłącza się. Psuedocode poniżej:

static void Main(string[] args) 
{ 
    if (args.Contains(StartProcessesSwitch)) 
     StartProcesses(GetFileWithArgs(args)) 
    else 
     WriteArgsToFile(); 
     //Run Program normally 
} 

void button_click(object sender, ButtonClickEventArgs e) 
{ 
    ShutDownAllMyProcesses() 
} 

void ShutDownAllMyProcesses() 
{ 
    List<Process> processes = GetMyProcesses(); 
    foreach (Process p in processes) 
    { 
     if (p != Process.GetCurrentProcess()) 
     p.Kill(); //or whatever you need to do to close 
    } 
    ProcessStartInfo psi = new ProcessStartInfo(); 
    psi.Arguments = CreateArgsWithFile(); 
    psi.FileName = "<your application here>"; 
    Process p = new Process(); 
    p.StartInfo = psi; 
    p.Start(); 
    CloseAppplication(); 
} 

Mam nadzieję, że to pomoże. Powodzenia!

1

Po pierwsze: Dziękuję Jesse, za doskonałe rozwiązanie. Moja odmiana znajduje się poniżej. Uwaga: Jedną z rzeczy, które lubię w C# jest to, że jest to język mocno napisany. Dlatego też unikam używania typu var. Czuję, że odrobina jasności jest warta kilku rzutów.

class Program 
{ 
    static void Main(string[] args) 
    { 


      Process[] processes = Process.GetProcessesByName("job Test"); 
      for (int p = 0; p < processes.Length; p++) 
      { 
       String[] arguments = CommandLineUtilities.getCommandLinesParsed(processes[p]); 
      } 
      System.Threading.Thread.Sleep(10000); 
    } 
} 



public abstract class CommandLineUtilities 
{ 
    public static String getCommandLines(Process processs) 
    { 
     ManagementObjectSearcher commandLineSearcher = new ManagementObjectSearcher(
      "SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + processs.Id); 
     String commandLine = ""; 
     foreach (ManagementObject commandLineObject in commandLineSearcher.Get()) 
     { 
      commandLine+= (String)commandLineObject["CommandLine"]; 
     } 

     return commandLine; 
    } 

    public static String[] getCommandLinesParsed(Process process) 
    { 
     return (parseCommandLine(getCommandLines(process))); 
    } 

    /// <summary> 
    /// This routine parses a command line to an array of strings 
    /// Element zero is the program name 
    /// Command line arguments fill the remainder of the array 
    /// In all cases the values are stripped of the enclosing quotation marks 
    /// </summary> 
    /// <param name="commandLine"></param> 
    /// <returns>String array</returns> 
    public static String[] parseCommandLine(String commandLine) 
    { 
     List<String> arguments = new List<String>(); 

     Boolean stringIsQuoted = false; 
     String argString = ""; 
     for (int c = 0; c < commandLine.Length; c++) //process string one character at a tie 
     { 
      if (commandLine.Substring(c, 1) == "\"") 
      { 
       if (stringIsQuoted) //end quote so populate next element of list with constructed argument 
       { 
        arguments.Add(argString); 
        argString = ""; 
       } 
       else 
       { 
        stringIsQuoted = true; //beginning quote so flag and scip 
       } 
      } 
      else if (commandLine.Substring(c, 1) == "".PadRight(1)) 
      { 
       if (stringIsQuoted) 
       { 
        argString += commandLine.Substring(c, 1); //blank is embedded in quotes, so preserve it 
       } 
       else if (argString.Length > 0) 
       { 
        arguments.Add(argString); //non-quoted blank so add to list if the first consecutive blank 
       } 
      } 
      else 
      { 
       argString += commandLine.Substring(c, 1); //non-blan character: add it to the element being constructed 
      } 
     } 

     return arguments.ToArray(); 

    } 

} 
+8

Nie martw się, że "var" jest mniej bezpieczny, to nie jest VB6 ani Javascript. "var" oznacza po prostu "niech kompilator wykrywa typ z inicjalizacji, zamiast redundantnego dostarczania typu, jak również wartości początkowej." Od tego momentu kompilator upewnia się, że zmienna jest używana poprawnie w odniesieniu do jej " type. –

+0

Dowolny powód 'CommandLineUtilities' jest' abstrakcyjny' zamiast 'static'? –

+1

Uzgodniony, @ GöranRoseen, jednak czasami dodaje jasności, gdy nie jest jasne, co jest zwracane do zmiennej, ale dla' String commandLine = ""; ', na przykład, nie ma powodu, aby nie używać' var'. –

4

C# 6 adaptacja Jesse C. Slicer's excellent answer że:

  • jest kompletna i powinna działać jak jest, po dodaniu odwołanie do montażu System.Management.dll (konieczna dla klasy WMI System.Management.ManagementSearcher).

  • usprawnia oryginalny kod i rozwiązuje kilka problemów

  • uchwyty dodatkowy wyjątek, który może wystąpić, jeśli proces badane już zakończony.

using System.Management; 
using System.ComponentModel; 

// Note: The class must be static in order to be able to define an extension method. 
static class Progam 
{ 
    private static void Main() 
    { 
     foreach (var process in Process.GetProcesses()) 
     { 
      try 
      { 
       Console.WriteLine($"PID: {process.Id}; cmd: {process.GetCommandLine()}"); 
      } 
      // Catch and ignore "access denied" exceptions. 
      catch (Win32Exception ex) when (ex.HResult == -2147467259) {} 
      // Catch and ignore "Cannot process request because the process (<pid>) has 
      // exited." exceptions. 
      // These can happen if a process was initially included in 
      // Process.GetProcesses(), but has terminated before it can be 
      // examined below. 
      catch (InvalidOperationException ex) when (ex.HResult == -2146233079) {} 
     } 
    } 

    // Define an extension method for type System.Process that returns the command 
    // line via WMI. 
    private static string GetCommandLine(this Process process) 
    { 
     string cmdLine = null; 
     using (var searcher = new ManagementObjectSearcher(
      $"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}")) 
     { 
      // By definition, the query returns at most 1 match, because the process 
      // is looked up by ID (which is unique by definition). 
      var matchEnum = searcher.Get().GetEnumerator(); 
      if (matchEnum.MoveNext()) // Move to the 1st item. 
      { 
       cmdLine = matchEnum.Current["CommandLine"]?.ToString(); 
      } 
     } 
     if (cmdLine == null) 
     { 
      // Not having found a command line implies 1 of 2 exceptions, which the 
      // WMI query masked: 
      // An "Access denied" exception due to lack of privileges. 
      // A "Cannot process request because the process (<pid>) has exited." 
      // exception due to the process having terminated. 
      // We provoke the same exception again simply by accessing process.MainModule. 
      var dummy = process.MainModule; // Provoke exception. 
     } 
     return cmdLine; 
    } 
} 
2

Jeśli nie chcesz korzystać z WMI i raczej mieć natywną sposób to zrobić, napisałem DLL, że w zasadzie wywołuje NtQueryInformationProcess() i wywodzi się z linii poleceń z informacji zwrócone.

Jest napisany w C++ i nie ma zależności, więc powinien działać w każdym systemie Windows.

Aby go użyć, wystarczy dodać ten przywóz:

[DllImport("ProcCmdLine32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetProcCmdLine")] 
public extern static bool GetProcCmdLine32(uint nProcId, StringBuilder sb, uint dwSizeBuf); 

[DllImport("ProcCmdLine64.dll", CharSet = CharSet.Unicode, EntryPoint = "GetProcCmdLine")] 
public extern static bool GetProcCmdLine64(uint nProcId, StringBuilder sb, uint dwSizeBuf); 

Następnie nazwać to jako tak:

public static string GetCommandLineOfProcess(Process proc) 
{ 
    // max size of a command line is USHORT/sizeof(WCHAR), so we are going 
    // just allocate max USHORT for sanity's sake. 
    var sb = new StringBuilder(0xFFFF); 
    switch (IntPtr.Size) 
    { 
     case 4: GetProcCmdLine32((uint)proc.Id, sb, (uint)sb.Capacity); break; 
     case 8: GetProcCmdLine64((uint)proc.Id, sb, (uint)sb.Capacity); break; 
    } 
    return sb.ToString(); 
} 

kod źródłowy/DLL są dostępne here.

Powiązane problemy