Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

.net

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.

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

like image 972
Jonathan Schuster Avatar asked Apr 13 '10 22:04

Jonathan Schuster


People also ask

How do I grab command line arguments?

To pass command line arguments, we typically define main() with two arguments : first argument is the number of command line arguments and second is list of command-line arguments. The value of argc should be non negative. argv(ARGument Vector) is array of character pointers listing all the arguments.

What is the source of command line arguments in C?

In C it is possible to accept command line arguments. Command-line arguments are given after the name of a program in command-line operating systems like DOS or Linux, and are passed in to the program from the operating system.

How do I find command line arguments in Visual Studio?

To set command-line arguments in Visual Studio, right click on the project name, then go to Properties. In the Properties Pane, go to "Debugging", and in this pane is a line for "Command-line arguments." Add the values you would like to use on this line.

Where are command line arguments stored C?

All the command line arguments are stored in a character pointer array called argv[ ].


2 Answers

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

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();     }  } 
like image 58
Jesse C. Slicer Avatar answered Oct 13 '22 21:10

Jesse C. Slicer


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.

The DLL was written in C++ and has no dependencies so it will work on any Windows system.

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.

If you just want the pre-compiled DLLs with some sample code, you can download a zip package from here.

Edited To Add:

I have converted the C++ code to C#. Now you don't need the ProcCmdLine.DLL, you can simply just add this class to your 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);         }     } } 
like image 44
Andy Avatar answered Oct 13 '22 21:10

Andy