Kan ik opdrachtregelargumenten van andere processen uit .NET/C# halen?

Ik heb een project waarin ik meerdere instanties van een app heb uitgevoerd, die elk zijn gestart met verschillende opdrachtregelargumenten. Ik zou graag een manier willen hebben om op een knop van een van die instanties te klikken, die vervolgens alle instanties afsluit en ze opnieuw opstart met dezelfde opdrachtregelargumenten.

Ik kan de processen zelf gemakkelijk genoeg krijgen via Process.GetProcessesByName(), maar wanneer ik dat doe, is de eigenschap StartInfo.Argumentsaltijd een lege tekenreeks. Het lijkt erop dat die eigenschap misschien alleen geldig is voordat een proces wordt gestart.

Deze vraaghad enkele suggesties , maar ze zijn allemaal in native code en ik zou dit rechtstreeks vanuit .NET willen doen. Suggesties?


Antwoord 1, autoriteit 100%

Dit gebruikt alle beheerde objecten, maar het duikt wel in het WMI-rijk:

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

Antwoord 2, autoriteit 24%

Als u geen WMI wilt gebruiken en liever een native manier hebt om dit te doen, heb ik een DLL geschreven die gebruikmaakt van NTDLL.DLL’s NtQueryInformationProcess()-export en de opdrachtregel afleidt van de informatie teruggestuurd.

De DLL is geschreven in C++ en heeft geen afhankelijkheden, dus het werkt op elk Windows-systeem.

Om het te gebruiken, voegt u deze importen toe:

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

Noem het dan zo:

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

Alle broncode voor de DLL met een voorbeeld van een .NET-consoletoepassing is beschikbaar in deze repo.

Als je alleen de vooraf gecompileerde DLL’s met wat voorbeeldcode wilt, kun je hier een zip-pakket downloaden .

Bewerkt om toe te voegen:

Ik heb de C++-code geconverteerd naar C#. Nu heb je de ProcCmdLine.DLLniet meer nodig, je kunt gewoon deze klasnaar uw code:

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

Antwoord 3, autoriteit 14%

Een C# v6+ aanpassing van Het uitstekende antwoord van Jesse C. Slicerdat:

  • is voltooid en zou moeten worden uitgevoerd zoals het is, zodra u een verwijzing naar assembly System.Management.dllheeft toegevoegd (nodig voor de WMI System.Management.ManagementSearcherklas).

  • stroomlijnt de originele code en lost een aantal problemen op

  • verwerkt een extra uitzondering die kan optreden als een proces dat wordt onderzocht al is afgesloten.

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

Antwoord 4, autoriteit 5%

Als je hetzelfde wilt doen op Linux waar geen WMI of ProcCmdLine32.dll is, is hier de code:

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

Antwoord 5

De StartInfo.Arguments wordt alleen gebruikt wanneer u de app start, het is geen record van de opdrachtregelargumenten. Als u de toepassingen start met opdrachtregelargumenten, slaat u de argumenten op wanneer ze in uw toepassing komen. In het eenvoudigste geval zou je ze in een tekstbestand kunnen opslaan, en als je op de knop drukt, alle processen afsluiten behalve die met de knopdrukgebeurtenis. Start een nieuwe toepassing en voer dat bestand in een nieuwe opdrachtregel arg. Terwijl de oude app wordt afgesloten, start de nieuwe app alle nieuwe processen (één voor elke regel in het bestand) en wordt afgesloten. Psuedocode hieronder:

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

Ik hoop dat dit helpt. Veel succes!


Antwoord 6

Ten eerste: bedankt Jesse, voor je uitstekende oplossing. Mijn variatie staat hieronder. Opmerking: een van de dingen die ik leuk vind aan C# is dat het een sterk getypte taal is. Daarom schuw ik het gebruik van var type. Ik heb het gevoel dat een beetje duidelijkheid een paar worpen waard is.

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

Other episodes