Getting the command line of a running process using C#

Every process on a Windows device features a ‘command line’ that may include one or more arguments or ‘switches’ that can affect the behaviour of the running program.

While it is possible in a .NET application to specify the arguments to use before starting a new process, currently there isn’t an API that allows the command line of a running process that contains those arguments to be retrieved.

If you’ve been searching for a way to get the command line of a running process using C#, this article will show you what you need to do.

What is WMI?

Before getting stuck into the code samples, first of all, let me provide a brief overview of WMI.

WMI stands for Windows Management Instrumentation. As the name suggests, it is a Windows-specific feature and allows various aspects of the Windows operating system to be managed and queried such as the registry, users, and processes. WMI is Microsoft’s implementation of Web-Based Enterprise Management (WBEM).

The ‘Windows Management Instrumentation’ Windows Service is the gateway through which all WMI queries are serviced and it should already be configured to start automatically on all Windows devices.

While WMI is known to be ‘slow’ it can provide us with a way of retrieving more information about running processes than .NET exposes within the framework. The apparent slowness of WMI isn’t usually a concern for the majority of applications and most of the time you’ll only perceive a noticeable delay if you are querying a lot of system processes at once.

While .NET doesn’t provide an API to get the command line of a running process, it does provide a useful API for working with WMI. We’ll see how WMI can be used to achieve what we need in the following section.

Command line code

Let’s take a look at how we can interact with WMI using C#.

In order for the code samples shown in the following sub-sections to compile in a .NET (.NET Core) project, you’ll need to install the System.Management NuGet package.

Starting off simple

The simplest thing we can do to retrieve the command line of a running process is as follows.

using System.Management;

string query =     @"SELECT CommandLine      FROM Win32_Process      WHERE Name = 'chrome.exe'"; using (var searcher   = new ManagementObjectSearcher(query)) using (var collection = searcher.Get()) {     foreach (var item in collection)     {         Console.WriteLine(item["CommandLine"]);     } }

The above code is looking up processes based on a process name of ‘chrome.exe’. On my machine, the above code outputs 16 different command lines. Yes, Google Chrome is famous for running many processes at once!

Below is an example of one of the Chrome command lines.

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --type=renderer --lang=en-GB ...other arguments omitted for brevity...

In addition to the path to the executable, the above command line features multiple arguments such as ‘type’ and ‘lang’. However, the main Chrome command line doesn’t feature any arguments.

Code breakdown

Let’s break down what the code sample further above is doing.

First of all, a WQL query is defined which selects the ‘CommandLine’ property from the ‘Win32_Process’ class for the ‘chrome.exe’ process name. As you would expect, in addition to the ‘CommandLine’ property there are many other properties that you can retrieve, such as ‘ProcessId’ and ‘Name’.

An instance of the ManagementObjectSearcher class is then created, passing the query into the class constructor.

After this, the Get method on the ManagementObjectSearcher object instance is called to execute the query.

The collection of results is then iterated over and the ‘CommandLine’ value is accessed via the collection indexer and output to the console.

Problems

If the code is building but you are getting a runtime error, I recommend checking that the ‘Windows Management Instrumentation’ Windows Service is currently running on your machine.

After checking that the service is running, if you are still having problems you might need to repair your WMI repository by running the following command.

winmgmt /resetrepository

You can see full instructions on the WMI repair process for Windows 10/11 at the following link.

https://www.thewindowsclub.com/how-to-repair-or-rebuild-the-wmi-repository-on-windows-10

If you are facing a particularly unusual issue, Microsoft’s in-depth WMI Isn’t Working! TechNet article may help.

Exploring use cases

How you want to use the WMI API to retrieve the details of processes is now down to you and will depend on your particular scenario and why you need to access the command line of a running process.

Extension method

If you have a Process object instance for the process that you need the command line for already, it might make sense for you to define and use an extension method.

For example, you might have already looked up the process by its ID using the Process.GetProcessById method, as follows.

int processId = Process.GetCurrentProcess().Id;
 
Process process = Process.GetProcessById(processId);

In this case, an extension method could be defined for the Process class that returns the command line related to the Process object instance.

/// <summary>
/// Gets the command line of the running process.
/// </summary>
/// <param name="process">The process to operate on</param>
/// <returns>The full command line of the running process</returns>
public static string GetCommandLine(this Process process)
{
if (process is null || process.Id < 1)
{
return "";
}
    if (!OperatingSystem.IsWindows())     {         throw new PlatformNotSupportedException("WMI is only supported on Windows.");     }
    string query =         $@"SELECT CommandLine            FROM Win32_Process            WHERE ProcessId = {process.Id}";     using (var searcher   = new ManagementObjectSearcher(query))     using (var collection = searcher.Get())     {         var managementObject = collection.OfType<ManagementObject>().FirstOrDefault();         return managementObject != null ? (string)managementObject["CommandLine"] : "";     } }

You could then call this method on your Process object instance to get its command line as shown below.

string commandLine = process.GetCommandLine();
 
Console.WriteLine("Command line: {0}", commandLine);

The extension method contains essentially the same code as the first code sample shown in this article. It just has a few additional checks, like checking that the Process object that was passed in is valid and checking that the code is running on a Windows operating system. The query is also slightly different as we are now looking up the process on its unique ID. Since there should be only one process with the specified ID, the FirstOrDefault method is used to see if there is an item in the collection and the method then returns either the command line or an empty string.

Multiple command lines

As you saw in the first code sample in this article, since it’s possible to have more than one instance of a program running, there can be many processes with the same name and therefore there will be multiple command lines to consider.

In this case, we can write a method that looks up a process by its name and returns a collection containing the command line of each running process.

/// <summary>
/// Gets a collection of command lines for the specified process name.
/// </summary>
/// <param name="processName">The name of the process to get command lines for</param>
/// <returns>A collection of process command lines</returns>
public IEnumerable<stringGetProcessCommandLines(string processName)
{
    if (!OperatingSystem.IsWindows())
    {
        throw new PlatformNotSupportedException("WMI is only supported on Windows.");
    }
 
    // Make sure the process name has an extension.
    string processNameWithExtension = processName;
 
    if (!Path.HasExtension(processNameWithExtension))
    {
        processNameWithExtension += ".exe";
    }
 
    string query =
        @"SELECT CommandLine
          FROM Win32_Process
          WHERE Name = '{processNameWithExtension}'";
 
    using (var searcher   = new ManagementObjectSearcher(query))
    using (var collection = searcher.Get())
    {
        return collection
            .OfType<ManagementObject>()
            .Select(o => (string)o["Commandline"])
            .ToList();
    }
}

The above code is very similar to the previous sample, however, this time a process name should be passed in. The code checks that the specified process name has an extension (i.e. ‘.exe’) so that the WMI provider can find it and then executes a query that looks up the process based on the ‘Name’ property. LINQ is then used to transform the results into a list of strings that can be returned to the caller.

Of course, calling the GetProcessCommandLines method and outputting the results is a straightforward affair.

var commandLines = GetProcessCommandLines("chrome");
 
foreach (string commandLine in commandLines)
{
    Console.WriteLine(commandLine);
}

In the case of the above code, the process name of ‘chrome’ will be converted to ‘chrome.exe’ automatically so that the process/processes can be located by WMI.

Relaunching programs

Now, let’s say that you need to close all of the running processes for which you have retrieved command lines and relaunch them later.

Assuming you already know how to close processes via the Kill method, you can relaunch each process via a method that creates a new Process object based on the specified command line. This method is defined below for reference.

/// <summary>
/// Creates a process from the specified command line.
/// </summary>
/// <param name="commandLine">The full process command line</param>
/// <param name="workingDirectory">The process working directory</param>
/// <returns><see cref="Process"/></returns>
public Process CreateProcessFromCommandLine(string commandLinestring workingDirectory = "")
{
    string fileName  = commandLine;
    string arguments = "";
 
    // Check that the command line isn't a full path already.
    if (!File.Exists(commandLine) && !Directory.Exists(commandLine))
    {
        if (!string.IsNullOrEmpty(commandLine))
        {
            commandLine = commandLine.Trim();
        }
 
        if (commandLine?.Length > 2)
        {
            if (commandLine.StartsWith(@""""))
            {
                // The path to the file is enclosed in double quotes.
                // e.g. "C:\Program Files (x86)\My Company\My App\MyApp.exe" --argument=value
                int closingDoubleQuotePosition = commandLine.IndexOf(@""""1);
 
                if (closingDoubleQuotePosition > 0 && commandLine.Length > closingDoubleQuotePosition + 1)
                {
                    fileName  = commandLine.Substring(0, closingDoubleQuotePosition + 1).Trim();
                    arguments = commandLine.Substring(closingDoubleQuotePosition + 1).Trim();
                }
            }
            else
            {
                // The path to the file is NOT enclosed in double quotes.
                // e.g. C:\MyApp\MyApp.exe --argument=value
                int firstSpacePosition = commandLine.IndexOf(@" ");
 
                if (firstSpacePosition > 0 && commandLine.Length > firstSpacePosition + 1)
                {
                    fileName  = commandLine.Substring(0, firstSpacePosition + 1).Trim();
                    arguments = commandLine.Substring(firstSpacePosition + 1).Trim();
                }
            }
        }
    }
 
    var process = new Process();
    process.StartInfo.UseShellExecute  = false;     process.StartInfo.FileName         = fileName;    process.StartInfo.Arguments        = arguments;    process.StartInfo.WorkingDirectory = workingDirectory;     return process; }

The above method takes care of correctly dividing the filename from the arguments by splitting the command line string either on a closing double quote or on the first space found in the string. The ‘StartInfo’ properties are then configured accordingly, with the working directory being set based on an optional method parameter.

Now that you have a method that can convert a command line into a Process object, you can write some simple code that iterates over each previously running process and starts it again.

foreach (string commandLine in commandLines)
{
    Process process = CreateProcessFromCommandLine(commandLine);
    process.Start();
}

After creating the Process object the above code simply calls the Start method to launch the process with the original arguments from the command line preserved.

I’m sure there are many other use cases for retrieving the command line for a running process, but hopefully, I’ve provided you with enough material at this point so that you get the idea.

Summary

In this article, you’ve seen how WMI can be used via .NET in a C# program to retrieve details about processes that can’t usually be inspected, in particular the full command line of a running process.

I’ve shown you the basics of how to retrieve the command line for a running process and walked through several use cases, presenting code samples of C# methods and how to use them. I trust that the samples will help you out and save some time.

In closing, it’s worth reminding you that WMI can be used for lots of other things like listening for events, such as events that fire when processes start or stop, or for scheduling processes or code to run at specific times. You can even connect to remote computers and execute WMI queries against these. I encourage you to check out the official WMI documentation if you’d like to learn more about WMI.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments