Могу ли я получить аргументы командной строки других процессов из .NET/С#?
У меня есть проект, в котором у меня есть несколько экземпляров приложения, каждый из которых был запущен с различными аргументами командной строки. Я хотел бы иметь возможность щелкнуть кнопку из одного из этих экземпляров, который затем выключит все экземпляры и снова запустит их обратно с теми же аргументами командной строки.
Я могу легко получить процессы через Process.GetProcessesByName()
, но всякий раз, когда я это делаю, свойство StartInfo.Arguments
всегда является пустой строкой. Похоже, что это свойство действительно только до начала процесса.
В этом вопросе были некоторые предложения, но они все в собственном коде, и я хотел бы сделать это прямо из .NET. Любые предложения?
Ответы
Ответ 1
Это использует все управляемые объекты, но это действительно углубляется в область 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();
}
}
Ответ 2
Адаптация AС# 6 Jesse C. Slicer - отличный ответ, который:
-
завершена и должна запускаться как есть после добавления ссылки на сборку 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;
}
}
Ответ 3
Если вы не хотите использовать WMI и, скорее, имеете собственный способ сделать это, я написал DLL, которая в основном вызывает NtQueryInformationProcess()
и выводит командную строку из возвращаемой информации.
Он написан на С++ и не имеет зависимостей, поэтому он должен работать в любой системе Windows.
Чтобы использовать его, просто добавьте эти импорты:
[DllImport("ProcCmdLine32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetProcCmdLine")]
public extern static bool GetProcCmdLine32(uint nProcId, StringBuilder sb, uint dwSizeBuf);
[DllImport("ProcCmdLine64.dll", CharSet = CharSet.Unicode, EntryPoint = "GetProcCmdLine")]
public extern static bool GetProcCmdLine64(uint nProcId, StringBuilder sb, uint dwSizeBuf);
Затем назовите его так:
public static string GetCommandLineOfProcess(Process proc)
{
// max size of a command line is USHORT/sizeof(WCHAR), so we are going
// just allocate max USHORT for sanity sake.
var sb = new StringBuilder(0xFFFF);
switch (IntPtr.Size)
{
case 4: GetProcCmdLine32((uint)proc.Id, sb, (uint)sb.Capacity); break;
case 8: GetProcCmdLine64((uint)proc.Id, sb, (uint)sb.Capacity); break;
}
return sb.ToString();
}
Исходный код/библиотеки DLL доступны здесь.
Ответ 4
Во-первых: Спасибо, Джесси, за отличное решение. Мое изменение ниже. Примечание. Одна из вещей, которые мне нравятся в С#, - это строго типизированный язык. Поэтому я избегаю использования типа 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();
}
}
Ответ 5
StartInfo.Arguments используется только при запуске приложения, это не запись аргументов командной строки. Если вы запускаете приложения с аргументами командной строки, то храните аргументы, когда они входят в ваше приложение. В простейшем случае вы можете сохранить их в текстовом файле, а затем, когда вы нажмете кнопку, закройте все процессы, кроме тех, которые были нажаты на кнопку. Запустите новое приложение и отправьте его этому файлу в новой командной строке arg. Пока старое приложение отключается, новое приложение запускает все новые процессы (по одному для каждой строки в файле) и выключается. Psedocode ниже:
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();
}
Надеюсь, это поможет. Удачи!