【问题标题】:Can I get command line arguments of other processes from .NET/C#?我可以从 .NET/C# 获取其他进程的命令行参数吗?
【发布时间】:2010-04-13 22:24:38
【问题描述】:

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

我可以通过Process.GetProcessesByName() 轻松获取进程本身,但无论何时,StartInfo.Arguments 属性始终是一个空字符串。看起来该属性可能仅在启动进程之前才有效。

This question 有一些建议,但它们都是本机代码,我想直接从 .NET 执行此操作。有什么建议吗?

【问题讨论】:

  • 您是否可以控制要重启的应用程序?
  • 是的,我可以完全控制我正在尝试重新启动的应用程序的代码 - 它始终是我正在运行的同一应用程序的另一个实例。这是一个 WPF 应用程序,如果这有什么不同的话,但我认为它不应该。
  • 根据关于 StartInfo (msdn.microsoft.com/en-us/library/…) 的 MSDN 文章,StartInfo 对象仅包含使用 Process.Start 启动进程的信息。它还表示使用 GetProcesses* 函数时 StartInfo 将为空。
  • 那么下面的MusiGenesis解决方案可能会做。

标签: c# .net


【解决方案1】:

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

}

【讨论】:

  • 唯一需要注意的是某些进程的 AccessDenied
  • 小注;在我的计算机(Win 10)上,WMI 返回的命令行包含正在运行的程序的名称,因此无需使用 process.MainModule.FileName 初始化 StringBuilder。仍然是一段很好的代码,它现在在我的项目中......谢谢!
  • searcher.Get() 何时返回包含多个元素的集合?当它发生时它意味着什么?
  • 我测试了它工作的代码。但是,在我的盒子里花了 8 秒钟。我们可以加快查询过程吗?
  • 尽我所能,WMI 只是通过ManagementObjectSearcher 慢的狗。
【解决方案2】:

如果您不想使用 WMI 而是使用本机方式来执行此操作,我编写了一个 DLL,它利用 NTDLL.DLL 的 NtQueryInformationProcess() 导出并从返回的信息中派生命令行。

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

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

[DllImport("ProcCmdLine32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetProcCmdLineW")]
public extern static int GetProcCmdLine32W(uint nProcId, StringBuilder sb, uint dwSizeBuf);

[DllImport("ProcCmdLine64.dll", CharSet = CharSet.Unicode, EntryPoint = "GetProcCmdLineW")]
public extern static int GetProcCmdLine64W(uint nProcId, StringBuilder sb, uint dwSizeBuf);

然后这样称呼它:

public static string GetCommandLineOfProcessW(Process proc)
{
    var sb = new StringBuilder(capacity: 0xFFFF);
    var rc = -1;
    switch (IntPtr.Size)
    {
        case 4:
            rc = Win32Native.GetProcCmdLine32W((uint)proc.Id, sb, (uint)sb.Capacity);
            break;
        case 8:
            rc = Win32Native.GetProcCmdLine64W((uint)proc.Id, sb, (uint)sb.Capacity);
            break;
    }
    return (0 == rc) ? sb.ToString() : throw new Win32Exception(rc, ErrorToString(rc));
}

带有示例 .NET 控制台应用程序的 DLL 的所有源代码都可以在 in this repo 获得。

如果您只想要带有一些示例代码的预编译 DLL,您可以下载zip package from here

编辑添加:

我已将 C++ 代码转换为 C#。现在您不需要ProcCmdLine.DLL,只需将this class 添加到您的代码中即可:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

public static class ProcessCommandLine
{
    private static class Win32Native
    {
        public const uint PROCESS_BASIC_INFORMATION = 0;

        [Flags]
        public enum OpenProcessDesiredAccessFlags : uint
        {
            PROCESS_VM_READ = 0x0010,
            PROCESS_QUERY_INFORMATION = 0x0400,
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ProcessBasicInformation
        {
            public IntPtr Reserved1;
            public IntPtr PebBaseAddress;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public IntPtr[] Reserved2;
            public IntPtr UniqueProcessId;
            public IntPtr Reserved3;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct UnicodeString
        {
            public ushort Length;
            public ushort MaximumLength;
            public IntPtr Buffer;
        }

        // This is not the real struct!
        // I faked it to get ProcessParameters address.
        // Actual struct definition:
        // https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
        [StructLayout(LayoutKind.Sequential)]
        public struct PEB
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public IntPtr[] Reserved;
            public IntPtr ProcessParameters;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RtlUserProcessParameters
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public byte[] Reserved1;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
            public IntPtr[] Reserved2;
            public UnicodeString ImagePathName;
            public UnicodeString CommandLine;
        }

        [DllImport("ntdll.dll")]
        public static extern uint NtQueryInformationProcess(
            IntPtr ProcessHandle,
            uint ProcessInformationClass,
            IntPtr ProcessInformation,
            uint ProcessInformationLength,
            out uint ReturnLength);

        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(
            OpenProcessDesiredAccessFlags dwDesiredAccess,
            [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
            uint dwProcessId);

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool ReadProcessMemory(
            IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer,
            uint nSize, out uint lpNumberOfBytesRead);

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

        [DllImport("shell32.dll", SetLastError = true,
            CharSet = CharSet.Unicode, EntryPoint = "CommandLineToArgvW")]
        public static extern IntPtr CommandLineToArgv(string lpCmdLine, out int pNumArgs);
    }

    private static bool ReadStructFromProcessMemory<TStruct>(
        IntPtr hProcess, IntPtr lpBaseAddress, out TStruct val)
    {
        val = default;
        var structSize = Marshal.SizeOf<TStruct>();
        var mem = Marshal.AllocHGlobal(structSize);
        try
        {
            if (Win32Native.ReadProcessMemory(
                hProcess, lpBaseAddress, mem, (uint)structSize, out var len) &&
                (len == structSize))
            {
                val = Marshal.PtrToStructure<TStruct>(mem);
                return true;
            }
        }
        finally
        {
            Marshal.FreeHGlobal(mem);
        }
        return false;
    }

    public static string ErrorToString(int error) =>
        new string[]
        {
            "Success",
            "Failed to open process for reading",
            "Failed to query process information",
            "PEB address was null",
            "Failed to read PEB information",
            "Failed to read process parameters",
            "Failed to read command line from process"
        }[Math.Abs(error)];

    public static int Retrieve(Process process, out string commandLine)
    {
        int rc = 0;
        commandLine = null;
        var hProcess = Win32Native.OpenProcess(
            Win32Native.OpenProcessDesiredAccessFlags.PROCESS_QUERY_INFORMATION |
            Win32Native.OpenProcessDesiredAccessFlags.PROCESS_VM_READ, false, (uint)process.Id);
        if (hProcess != IntPtr.Zero)
        {
            try
            {
                var sizePBI = Marshal.SizeOf<Win32Native.ProcessBasicInformation>();
                var memPBI = Marshal.AllocHGlobal(sizePBI);
                try
                {
                    var ret = Win32Native.NtQueryInformationProcess(
                        hProcess, Win32Native.PROCESS_BASIC_INFORMATION, memPBI,
                        (uint)sizePBI, out var len);
                    if (0 == ret)
                    {
                        var pbiInfo = Marshal.PtrToStructure<Win32Native.ProcessBasicInformation>(memPBI);
                        if (pbiInfo.PebBaseAddress != IntPtr.Zero)
                        {
                            if (ReadStructFromProcessMemory<Win32Native.PEB>(hProcess,
                                pbiInfo.PebBaseAddress, out var pebInfo))
                            {
                                if (ReadStructFromProcessMemory<Win32Native.RtlUserProcessParameters>(
                                    hProcess, pebInfo.ProcessParameters, out var ruppInfo))
                                {
                                    var clLen = ruppInfo.CommandLine.MaximumLength;
                                    var memCL = Marshal.AllocHGlobal(clLen);
                                    try
                                    {
                                        if (Win32Native.ReadProcessMemory(hProcess,
                                            ruppInfo.CommandLine.Buffer, memCL, clLen, out len))
                                        {
                                            commandLine = Marshal.PtrToStringUni(memCL);
                                            rc = 0;
                                        }
                                        else
                                        {
                                            // couldn't read command line buffer
                                            rc = -6;
                                        }
                                    }
                                    finally
                                    {
                                        Marshal.FreeHGlobal(memCL);
                                    }
                                }
                                else
                                {
                                    // couldn't read ProcessParameters
                                    rc = -5;
                                }
                            }
                            else
                            {
                                // couldn't read PEB information
                                rc = -4;
                            }
                        }
                        else
                        {
                            // PebBaseAddress is null
                            rc = -3;
                        }
                    }
                    else
                    {
                        // NtQueryInformationProcess failed
                        rc = -2;
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(memPBI);
                }
            }
            finally
            {
                Win32Native.CloseHandle(hProcess);
            }
        }
        else
        {
            // couldn't open process for VM read
            rc = -1;
        }
        return rc;
    }

    public static IReadOnlyList<string> CommandLineToArgs(string commandLine)
    {
        if (string.IsNullOrEmpty(commandLine)) { return Array.Empty<string>(); }

        var argv = Win32Native.CommandLineToArgv(commandLine, out var argc);
        if (argv == IntPtr.Zero)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        try
        {
            var args = new string[argc];
            for (var i = 0; i < args.Length; ++i)
            {
                var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
                args[i] = Marshal.PtrToStringUni(p);
            }
            return args.ToList().AsReadOnly();
        }
        finally
        {
            Marshal.FreeHGlobal(argv);
        }
    }
}

【讨论】:

  • 谢谢!这比 WMI 解决方案要好得多。
  • 应该是公认的答案,比 WMI 查询快得多。感谢您努力使这项工作:)
  • 这似乎不起作用,找不到ProcCmdLine64.dll
  • 将 DLL 放在与您的应用程序相同的目录中。 DLL 位于答案中链接的 ZIP 文件中。
  • 对我来说最安全的解决方案
【解决方案3】:

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

  • 一旦添加对程序集 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;
    }
}

【讨论】:

    【解决方案4】:

    如果您想在没有 WMI 或 ProcCmdLine32.dll 的 Linux 上执行相同操作,代码如下:

    string cmdline = File.ReadAllText( $"/proc/{ process.Id }/cmdline" );
    

    【讨论】:

    • 哈。我喜欢这里微妙的 Linux 促销
    【解决方案5】:

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

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

    希望这会有所帮助。祝你好运!

    【讨论】:

      【解决方案6】:

      首先:感谢 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();
      
          }
      
      }
      

      【讨论】:

      • 不要担心“var”的类型安全性较低,这不是 VB6 或 Javascript。 “var”只是意味着“让编译器从初始化中找出类型,而不是冗余地提供类型和初始值。从那里开始,编译器确保变量的使用正确与它的类型有关.
      • 任何原因CommandLineUtilitiesabstract 而不是static
      • 同意,@GöranRoseen,但有时当不清楚返回到变量中的内容时,它确实会增加清晰度。但是以String commandLine = "";为例,没有理由不使用var
      【解决方案7】:

      这是我的看法,不会因类型和性能调整而污染您的程序集。它适用于 x86 和 x64。 Microsoft 不太可能更改内部 api/结构,因为很多消费者代码都依赖于它们。

      如果进程未提升,它将为系统进程(如 taskmanager 等)返回 null。对于这种情况,您可能希望回退到慢速 WMI 解决方案。我编写了没有 System.Management 依赖和 COM 支持的 WMI 查询,但那是另一回事。

      实际上,如果第一次 pinvoke 成功,其他人不太可能失败,但我保留了错误检查代码以保持理智。您可以使用第二个进一步简化的版本。

      public unsafe static string? GetCommandLine(int processId)
      {
          var processHadle = OpenProcess(0x410, 0, processId);
          if (processHadle == 0)
              goto error;
          var mem = stackalloc nint[sizeof(nint) * 16];
          int length;
          if (NtQueryInformationProcess(processHadle, 0, mem, sizeof(nint) * 6, &length) != 0)
              goto error;
          var pbiBaseAddress = mem[1];
          if (pbiBaseAddress == 0)
              goto error;
          if (ReadProcessMemory(processHadle, pbiBaseAddress, mem, sizeof(nint) * 5, &length) == 0
              || (length != sizeof(nint) * 5))
              goto error;
          var processParameters = mem[4];
          if (ReadProcessMemory(processHadle, processParameters, mem, sizeof(nint) * 16, &length) == 0
              || (length != sizeof(nint) * 16))
              goto error;
          var cmdLineUnicode = mem + 14;
          var cmdLineLength = ((short*)cmdLineUnicode)[1];
          var pStr = Marshal.AllocHGlobal(cmdLineLength);
          if (ReadProcessMemory(processHadle, *(IntPtr*)(cmdLineUnicode + 1), (void*)pStr, cmdLineLength, &length) == 0)
              goto error;
          var str = new string((char*)pStr);
          Marshal.FreeHGlobal(pStr);
          return str;
          error:
          if (processHadle != 0)
              CloseHandle(processHadle);
          if (pStr != IntPtr.Zero)
              Marshal.FreeHGlobal(pStr);
          return null;
      
          [DllImport("ntdll.dll")]
          static extern int NtQueryInformationProcess(nint ProcessHandle, int ProcessInformationClass, void* ProcessInformation, int ProcessInformationLength, int* ReturnLength);
          [DllImport("kernel32.dll")]
          static extern nint OpenProcess(int dwDesiredAccess, int bInheritHandle, int dwProcessId);
          [DllImport("kernel32.dll")]
          static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, void* lpBuffer, int nSize, int* lpNumberOfBytesRead);
          [DllImport("kernel32.dll")]
          static extern int CloseHandle(nint hObject);
      }
      

      这是第二个:

      public unsafe static string GetCommandLineByPEB(int processId)
      {
          var processHadle = OpenProcess(0x410, 0, processId);
          if (processHadle == 0)
              return null;
          var mem = stackalloc nint[sizeof(nint) * 16];
          int len;
          NtQueryInformationProcess(processHadle, 0, mem, sizeof(nint) * 6, &len);
          ReadProcessMemory(processHadle, mem[1], mem, sizeof(nint) * 5, &len);
          ReadProcessMemory(processHadle, mem[4], mem, sizeof(nint) * 16, &len);
          var cmdLineUnicode = mem + 14;
          length = ((short*)cmdLineUnicode)[1];
          var pStr = (char*)Marshal.AllocHGlobal(length);
          ReadProcessMemory(processHadle, cmdLineUnicode[1], pStr, length, &len);
          CloseHandle(processHadle);
          var str = new string(pStr);
          Marshal.FreeHGlobal((nint)pStr);
          Marshal.FreeHGlobal((nint)mem);
          return str;
      
          [DllImport("ntdll.dll")] static extern int NtQueryInformationProcess(nint ProcessHandle, int ProcessInformationClass, void* ProcessInformation, int ProcessInformationLength, int* ReturnLength);
          [DllImport("kernel32.dll")] static extern nint OpenProcess(int dwDesiredAccess, int bInheritHandle, int dwProcessId);
          [DllImport("kernel32.dll")] static extern int ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, void* lpBuffer, int nSize, int* lpNumberOfBytesRead);
          [DllImport("kernel32.dll")] static extern int CloseHandle(nint hObject);
      }
      

      【讨论】:

        猜你喜欢
        • 2022-01-22
        • 1970-01-01
        • 2013-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-12
        • 2011-08-17
        相关资源
        最近更新 更多