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

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

我可以通过 Process.GetProcessesByName()轻松地获得进程本身,但是每当我这样做时,StartInfo.Arguments属性总是一个空字符串。似乎该属性只在启动进程之前有效。

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

58036 次浏览

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


}

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

希望这个有用,祝你好运!

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


}


}

杰西 · C · 斯莱瑟的绝妙回答的 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;
}
}

如果您不希望使用 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));
}

所有 DLL 的源代码,以及一个.NET 控制台应用,都可以在 在这个回购中找到。

如果您只想要带有一些示例代码的预编译 DLL,可以下载 拉链包裹

编辑添加:

我已经把 C + + 代码转换成了 C # 。现在你不需要 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://learn.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 和 ProcCmdLine32.dll 的 Linux 上做同样的事情,下面是代码:

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

这里是我的采取没有污染您的组装与类型和性能调整。它在 x86和 x64上都可以工作。微软不太可能改变内部的 API/结构,因为很多消费者代码都依赖于它们。

如果 process 没有升级,那么对于诸如 taskmanager 之类的系统进程,它将返回 null。对于这种情况,您可能需要回退到缓慢的 WMI 解决方案。我写的 WMI 查询没有系统。管理依赖和 COM 支持,但那是另一回事。

实际上,如果第一个 pcall 成功了,其他的不太可能会失败,但是我保留了错误检查代码以确保正常运行。您可以使用第二个进一步简化的版本。

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