我有一个项目,其中有一个正在运行的应用程序的多个实例,每个实例都使用不同的命令行参数启动。我希望有一种方法可以从这些实例之一中单击一个按钮,然后关闭所有实例,然后使用相同的命令行参数再次启动它们。
我可以通过轻松地获取流程本身Process.GetProcessesByName(),但是无论何时,该StartInfo.Arguments属性始终是一个空字符串。似乎该属性仅在启动进程之前才有效。
这个问题有一些建议,但是它们全都在本机代码中,我想直接从.NET中做到这一点。有什么建议?
我有一个项目,其中有一个正在运行的应用程序的多个实例,每个实例都使用不同的命令行参数启动。我希望有一种方法可以从这些实例之一中单击一个按钮,然后关闭所有实例,然后使用相同的命令行参数再次启动它们。
我可以通过轻松地获取流程本身Process.GetProcessesByName(),但是无论何时,该StartInfo.Arguments属性始终是一个空字符串。似乎该属性仅在启动进程之前才有效。
这个问题有一些建议,但是它们全都在本机代码中,我想直接从.NET中做到这一点。有什么建议?
Answers:
这正在使用所有托管对象,但确实涉及到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();
}
}
ManagementObjectSearcher。
如果您不想使用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中的源代码可在此回购。
如果您只需要带有一些示例代码的预编译DLL,则可以从此处下载一个zip软件包。
我已经将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://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);
}
}
}
ProcCmdLine64.dll
AC#v6 +对Jesse C. Slicer的出色回答是:
一旦您添加了对程序集的引用System.Management.dll(WMISystem.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;
}
}
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();
}
希望这可以帮助。祝好运!
第一:谢谢杰西,您的出色解决方案。我的变化如下。注意:我喜欢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();
}
}
CommandLineUtilities是abstract不是static?
String commandLine = "";,例如,没有理由不使用var。