简体   繁体   English

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

[英]Can I get command line arguments of other processes from .NET/C#?

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.我可以通过Process.GetProcessesByName()轻松获取进程本身,但是无论何时, StartInfo.Arguments属性始终为空字符串。 It looks like maybe that property is only valid before starting a process.看起来该属性可能仅在启动进程之前有效。

This question had some suggestions, but they're all in native code, and I'd like to do this directly from .NET. 这个问题有一些建议,但它们都在本机代码中,我想直接从 .NET 执行此操作。 Any suggestions?有什么建议?

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

}

If you don't want to use WMI and rather have a native way of doing this, I wrote a DLL that utilizes NTDLL.DLL's NtQueryInformationProcess() export and derives the command line from the information returned.如果您不想使用 WMI 而是使用本机方式来执行此操作,我编写了一个 DLL,它利用 NTDLL.DLL 的NtQueryInformationProcess()导出并从返回的信息中导出命令行。

The DLL was written in C++ and has no dependencies so it will work on any Windows system. DLL 是用 C++ 编写的,没有依赖项,因此它可以在任何 Windows 系统上运行。

To use it, just add these imports:要使用它,只需添加这些导入:

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

Then call it as so:然后这样称呼它:

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

All the source code for the DLL with an example .NET console application is available in this repo . DLL 的所有源代码和一个示例 .NET 控制台应用程序都可以在这个 repo 中找到

If you just want the pre-compiled DLLs with some sample code, you can download a zip package from here .如果您只想要带有一些示例代码的预编译 DLL,您可以从这里下载zip 包

Edited To Add:编辑添加:

I have converted the C++ code to C#.我已将 C++ 代码转换为 C#。 Now you don't need the ProcCmdLine.DLL , you can simply just add this class to your code:现在您不需要ProcCmdLine.DLL ,您只需将此类添加到您的代码中即可:

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

AC# v6+ adaption of Jesse C. Slicer's excellent answer that: AC# v6+ 改编自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).一旦您添加对程序集System.Management.dll (WMI System.Management.ManagementSearcher类所需)的引用, System.Management.dll完成并应按原样运行。

  • 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).
            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;
    }
}

如果你想在没有 WMI 或 ProcCmdLine32.dll 的 Linux 上做同样的事情,这里是代码:

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

The StartInfo.Arguments is only used when you start the app, it is not a record of the command line arguments. StartInfo.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.启动一个新的应用程序,并在一个新的命令行 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:伪代码如下:

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!祝你好运!

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.注意:我喜欢 C# 的一个原因是它是一种强类型语言。 Therefore I eschew the use of var type.因此我避免使用 var 类型。 I feel that a little clarity is worth a few casts.我觉得有点清晰值得几次演员。

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

    }

}

Here is my take without polluting your assembly with types and with performance tweaks.这是我的看法,不会因类型和性能调整而污染您的程序集。 It works on both x86&x64.它适用于 x86 和 x64。 Microsoft will unlikely to change internal apis/structures because a lot of consumer code depends of them. Microsoft 不太可能更改内部 apis/结构,因为很多消费者代码都依赖于它们。

If process is not elevated, it will return null for system processes like taskmanager etc. You might want to fallback to slow WMI solution for that case.如果进程没有提升,它将为任务管理器等系统进程返回 null。对于这种情况,您可能希望回退到慢速 WMI 解决方案。 Ive written WMI query without System.Management dependency and COM support but thats another story.我写过没有 System.Management 依赖和 COM 支持的 WMI 查询,但那是另一回事。

Actually if first pinvoke succeeds, its unlikely others will fail, but ive kept error checking code for sanity.实际上,如果第一个 pinvoke 成功,那么其他人不太可能会失败,但我保留了错误检查代码以保持理智。 You can remove if you want to.如果你愿意,你可以删除。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM