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

时间:2022-09-20 20:57:29

I have a project where I have multiple instances of an app running, each of which was started with different command line arguments. I'd like to have a way to click a button from one of those instances which then shuts down all of the instances and starts them back up again with the same command line arguments.

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

I can get the processes themselves easily enough through Process.GetProcessesByName(), but whenever I do, the StartInfo.Arguments property is always an empty string. It looks like maybe that property is only valid before starting a process.

通过Process.GetProcessesByName(),我可以很容易地获得进程本身,但是无论何时,我都使用StartInfo。参数属性总是一个空字符串。看起来这个属性只在开始一个进程之前有效。

This question had some suggestions, but they're all in native code, and I'd like to do this directly from .NET. Any suggestions?

这个问题有一些建议,但是它们都是本地代码,我想直接从。net来做。有什么建议吗?

5 个解决方案

#1


61  

This is using all managed objects, but it does dip down into the WMI realm:

这使用了所有托管对象,但它确实深入到了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();
    }

}

#2


4  

A C# 6 adaption of Jesse C. Slicer's excellent answer that:

c# 6改编自Jesse C. Slicer的绝妙回答:

  • is complete and should run as-is, once you add a reference to assembly System.Management.dll (needed for the WMI System.Management.ManagementSearcher class).

    完成并应按原样运行,一旦您添加了对程序集系统的引用。dll(用于WMI系统. management)。ManagementSearcher类)。

  • streamlines the original code and fixes a few problems

    简化原始代码并修复一些问题

  • handles an additional exception that can occur if a process being examined has already exited.

    处理在被检查的进程已经退出时可能发生的附加异常。

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

#3


2  

If you don't want to use WMI and rather have a native way of doing this, I wrote a DLL that basically calls NtQueryInformationProcess() and derives the command line from the information returned.

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

It's written in C++ and has no dependencies so it should work on any Windows system.

它是用c++编写的,没有依赖项,所以应该可以在任何Windows系统上运行。

To use it, just add these imports:

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

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

Then call it as so:

然后这样称呼它:

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

The source code/DLLs are available here.

这里有源代码/ dll。

#4


1  

First: Thank you Jesse, for your excellent solution. My variation is below. Note: One of the things I like about C# is that it is a strongly typed language. Therefore I eschew the use of var type. I feel that a little clarity is worth a few casts.

首先,谢谢你,杰西,谢谢你出色的解决方案。下面的我的变化。注意:我喜欢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();

    }

}

#5


0  

The StartInfo.Arguments is only used when you start the app, it is not a record of the command line arguments. If you start the applications with command line arguments, then store the arguments when they come into your application. In the simplest case, you could store them in a text file, then when you hit the button, shut down all the processes except the one with the button press event. Fire off a new application, and feed it that file in a new command line arg. While the old app shuts down, the new app fires off all the new processes (one for each line in the file) and shuts down. Psuedocode below:

StartInfo。参数只在启动应用程序时使用,它不是命令行参数的记录。如果您使用命令行参数启动应用程序,那么在它们进入应用程序时存储参数。在最简单的情况下,您可以将它们存储在一个文本文件中,然后当您按下按钮时,关闭所有的进程,除了按钮按下事件。启动一个新的应用程序,并将该文件输入到一个新的命令行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();
}

Hope this helps. Good luck!

希望这个有帮助。好运!

#1


61  

This is using all managed objects, but it does dip down into the WMI realm:

这使用了所有托管对象,但它确实深入到了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();
    }

}

#2


4  

A C# 6 adaption of Jesse C. Slicer's excellent answer that:

c# 6改编自Jesse C. Slicer的绝妙回答:

  • is complete and should run as-is, once you add a reference to assembly System.Management.dll (needed for the WMI System.Management.ManagementSearcher class).

    完成并应按原样运行,一旦您添加了对程序集系统的引用。dll(用于WMI系统. management)。ManagementSearcher类)。

  • streamlines the original code and fixes a few problems

    简化原始代码并修复一些问题

  • handles an additional exception that can occur if a process being examined has already exited.

    处理在被检查的进程已经退出时可能发生的附加异常。

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

#3


2  

If you don't want to use WMI and rather have a native way of doing this, I wrote a DLL that basically calls NtQueryInformationProcess() and derives the command line from the information returned.

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

It's written in C++ and has no dependencies so it should work on any Windows system.

它是用c++编写的,没有依赖项,所以应该可以在任何Windows系统上运行。

To use it, just add these imports:

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

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

Then call it as so:

然后这样称呼它:

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

The source code/DLLs are available here.

这里有源代码/ dll。

#4


1  

First: Thank you Jesse, for your excellent solution. My variation is below. Note: One of the things I like about C# is that it is a strongly typed language. Therefore I eschew the use of var type. I feel that a little clarity is worth a few casts.

首先,谢谢你,杰西,谢谢你出色的解决方案。下面的我的变化。注意:我喜欢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();

    }

}

#5


0  

The StartInfo.Arguments is only used when you start the app, it is not a record of the command line arguments. If you start the applications with command line arguments, then store the arguments when they come into your application. In the simplest case, you could store them in a text file, then when you hit the button, shut down all the processes except the one with the button press event. Fire off a new application, and feed it that file in a new command line arg. While the old app shuts down, the new app fires off all the new processes (one for each line in the file) and shuts down. Psuedocode below:

StartInfo。参数只在启动应用程序时使用,它不是命令行参数的记录。如果您使用命令行参数启动应用程序,那么在它们进入应用程序时存储参数。在最简单的情况下,您可以将它们存储在一个文本文件中,然后当您按下按钮时,关闭所有的进程,除了按钮按下事件。启动一个新的应用程序,并将该文件输入到一个新的命令行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();
}

Hope this helps. Good luck!

希望这个有帮助。好运!