2010-04-13 2 views
41

J'ai un projet où plusieurs instances d'une application sont exécutées, chacune ayant été démarrée avec des arguments de ligne de commande différents. J'aimerais avoir un moyen de cliquer sur un bouton à partir de l'une de ces instances qui ferme ensuite toutes les instances et les redémarre avec les mêmes arguments de ligne de commande.Puis-je obtenir des arguments de ligne de commande d'autres processus à partir de .NET/C#?

Je peux obtenir les processus eux-mêmes assez facilement par Process.GetProcessesByName(), mais chaque fois que je le fais, la propriété StartInfo.Arguments est toujours une chaîne vide. Il semble que cette propriété est peut-être valide avant de démarrer un processus.

This question avait quelques suggestions, mais ils sont tous en code natif, et je voudrais le faire directement à partir de .NET. Aucune suggestion?

+1

Avez-vous le contrôle sur l'application que vous essayez de redémarrer? –

+0

Oui, j'ai un contrôle complet sur le code de l'application que j'essaie de redémarrer - il va toujours y avoir une autre instance de la même application que je cours. C'est une application WPF, si cela fait une différence, mais je ne pense pas que ce devrait être le cas. –

+2

Selon l'article MSDN sur StartInfo (http://msdn.microsoft.com/en-us/library/system.diagnostics.process.startinfo.aspx), l'objet StartInfo contient uniquement des informations si le processus a été démarré à l'aide de Process. Début. Cela indique également que StartInfo sera vide lors de l'utilisation des fonctions GetProcesses *. – Corin

Répondre

60

est d'utiliser tous les objets gérés, mais il ne plonge vers le bas dans le domaine 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

seule chose à surveiller est AccessDenied sur certains des processus –

+2

Petite note; Sur mon ordinateur (Win 10) la ligne de commande renvoyée par WMI contient le nom du programme en cours d'exécution, il n'est donc pas nécessaire d'initialiser StringBuilder avec process.MainModule.FileName. Encore un bon morceau de code, c'est dans mon projet maintenant .. Merci! –

+0

Quand searcher.Get() renverra une collection contenant plusieurs éléments? Qu'est-ce que cela implique quand cela arrive? – WawaBrother

1

Les StartInfo.Arguments est utilisé uniquement lorsque vous démarrez l'application, il est un enregistrement de la ligne de commande arguments. Si vous démarrez les applications avec des arguments de ligne de commande, stockez les arguments lorsqu'ils entrent dans votre application. Dans le cas le plus simple, vous pouvez les stocker dans un fichier texte, puis lorsque vous cliquez sur le bouton, fermez tous les processus sauf celui avec l'événement de presse de bouton. Lancez une nouvelle application et alimentez ce fichier dans une nouvelle ligne de commande arg. Pendant que l'ancienne application s'arrête, la nouvelle application déclenche tous les nouveaux processus (un pour chaque ligne du fichier) et s'arrête. Code du produit ci-dessous:

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(); 
} 

Espérons que cela aide. Bonne chance!

1

Premièrement: Merci Jesse, pour votre excellente solution. Ma variation est ci-dessous. Note: Une des choses que j'aime à propos de C# est que c'est un langage fortement typé. Par conséquent, je évite l'utilisation de type var. Je pense qu'un peu de clarté vaut quelques moulages.

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

Ne vous inquiétez pas du fait que "var" soit moins sûr, ce n'est pas VB6 ou Javascript. "var" signifie simplement "laisser le compilateur déterminer le type à partir de l'initialisation, au lieu de fournir de façon redondante un type ainsi qu'une valeur initiale, à partir de là, le compilateur s'assure que la variable est correctement utilisée. –

+0

Une raison quelconque 'CommandLineUtilities' est' abstract' au lieu de 'static'? –

+1

D'accord, @ GöranRoseen, mais parfois, il ajoute de la clarté quand on ne sait pas ce qui est retourné dans la variable, mais pour' String commandLine = ""; ', par exemple, il n'y a aucune raison de ne pas utiliser' var'. –

4

A C# 6 adaptation de Jesse C. Slicer's excellent answer que:

  • est terminée et devrait fonctionner comme-est, une fois que vous ajoutez une référence à l'assemblage System.Management.dll (nécessaire pour la classe WMI System.Management.ManagementSearcher).

  • rationalise le code d'origine et corrige quelques problèmes

  • poignées une exception supplémentaire qui peut se produire si un processus en cours d'examen est déjà terminé.

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

Si vous ne souhaitez pas utiliser WMI et plutôt une manière native de le faire, j'ai écrit une DLL qui appelle essentiellement NtQueryInformationProcess() et tire la ligne de commande à partir des informations de retour.

Il est écrit en C++ et n'a pas de dépendances donc il devrait fonctionner sur n'importe quel système Windows.

Pour l'utiliser, il suffit d'ajouter ces importations:

[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); 

Ensuite, appelez comme si:

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(); 
} 

Le code source/DLL sont disponibles here.

Questions connexes