我可以从.NET / C#获取其他进程的命令行参数吗?

问题描述 投票:52回答:5

我有一个项目,我有一个应用程序的多个实例运行,每个实例都使用不同的命令行参数启动。我希望有一种方法可以从其中一个实例中单击一个按钮,然后关闭所有实例并使用相同的命令行参数重新启动它们。

我可以通过Process.GetProcessesByName()轻松地完成这些过程,但每当我这样做时,StartInfo.Arguments属性总是一个空字符串。看起来这个属性可能只在开始一个进程之前有效。

This question有一些建议,但它们都是本机代码,我想直接从.NET中做到这一点。有什么建议?

c# .net
5个回答
71
投票

这是使用所有托管对象,但它确实深入到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 - no security access to the process.
        }
        catch (InvalidOperationException)
        {
            // Intentionally empty - the process exited before getting details.
        }

    }
}

private static string GetCommandLine(this Process process)
{
    using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id))
    using (ManagementObjectCollection objects = searcher.Get())
    {
        return objects.Cast<ManagementBaseObject>().SingleOrDefault()?["CommandLine"]?.ToString();
    }

}

8
投票

C#v6 + Jesse C. Slicer's excellent answer的改编:

  • 已完成并应按原样运行,一旦添加对程序集System.Management.dll的引用(WMI System.Management.ManagementSearcher类需要)。
  • 简化原始代码并修复一些问题
  • 处理正在检查的进程已经退出时可能发生的其他异常。
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).
            using (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;
    }
}

5
投票

如果您不想使用WMI而不是本机方式,我编写了一个DLL,它基本上调用NtQueryInformationProcess()并从返回的信息派生命令行。

它是用C ++编写的,没有依赖关系,因此它可以在任何Windows系统上运行。

要使用它,只需添加这些导入:

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

然后将其称为:

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

源代码/ DLL可用here


1
投票

第一:谢谢Jesse,感谢您的出色解决方案。我的变化如下。注意:我喜欢C#的一个原因是它是一种强类型语言。因此我避免使用var类型。我觉得有点清晰度值得一些演员。

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

    }

}

0
投票

StartInfo.Arguments仅在启动应用程序时使用,它不是命令行参数的记录。如果使用命令行参数启动应用程序,则在参数进入应用程序时存储参数。在最简单的情况下,您可以将它们存储在文本文件中,然后当您按下按钮时,关闭除按钮按下事件之外的所有进程。触发新应用程序,并将该文件以新命令行arg提供。当旧应用程序关闭时,新应用程序会触发所有新进程(文件中每行一个)并关闭。 Psuedocode如下:

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

希望这可以帮助。祝好运!

© www.soinside.com 2019 - 2024. All rights reserved.