Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

NTRemoteProcessControl – Enumerate and control Windows processes and services through WMI and WinForms

4.83/5 (4 votes)
19 Feb 2013CPOL4 min read 34.7K   959  
Enumerate remote processes and services with a Windows Identity that is part of the local Administrator’s group.

Introduction

Ever had Windows servers that ran some applications under an Identity with Administrator privileges? One day that application did not run and you wanted to restart it. Your AD account on the domain was denied RDP or any kind of access to those Windows servers, however the Account (Identity) used to run those applications on the server was a domain account and it was added to the local Administrators group whose credentials were known to you. I guess these are called Service Accounts (at least in the enterprise parlance), that typically do not have Interactive Access i.e. using this Service Account, you can’t log in through an RDP; if you were able to RDP to those Windows servers using a Service Account, may be you are unknowingly violating your IT protocols Wink | ;) because a Service Account wasn’t meant to be used for an RDP session. But the situation is critical, the Business users are nagging you for an update, you had to restart the job as soon as possible.

You are lucky if you manage the credentials of the Service Account (that is used as an Identity to run the job) that is part of the local Administrators group. Options are there, you could use the runas command, or the psexec from Sysinternals, to impersonate the Service Account and restart the process. In case you had to start or stop a Windows service, you could connect to the target computer from services.msc and manipulate the services running on the remote computer. Another option is you could write a little WinForms applications to do all these things for you and call it NTRemoteProcessControl.

Image 2

Background

For manipulating the processes, we are going to use WMI, and for manipulating the Windows services - , we are going to use the ServiceController  class - with Impersonation. Simple as saying that, so is the code. The original source code for Impersonator.cs is borrowed from http://platinumdogs.me/2008/10/30/net-c-impersonation-with-network-credentials/.

Anything you do with WMI via the System.Management namespace involves the standard procedure of creating a ManagementScope, an ObjectQuery, ManagementObjectSearcher, ManagementObjectCollection. Enough documentation is available already for all the above classes. Allow me to placate and please refer to MSDN. We are just going to concentrate on obtaining a couple of process’s properties and bind them to a GridView. 

Process Management

Creating the ManagementScope Object

The ManagementScope object defines the computer that we want to connect to, and the user account credentials on the computer, that should be used while querying.

C#
private static ManagementScope GetManagementScope(string pComputerName, string pAccountName, string pAccountDomain, string pAccountPassword)
{
    ManagementScope managementScope = default(ManagementScope);

    if (Utilities.IsLocalHost(pComputerName))
    {
        managementScope = new ManagementScope("\\\\" + pComputerName + "\\root\\cimv2");
    }
    else
    {
        ConnectionOptions connectionOptions = new ConnectionOptions();
        connectionOptions.Username = pAccountDomain + "\\" + pAccountName;
        connectionOptions.Password = pAccountPassword;
        managementScope = new ManagementScope("\\\\" + pComputerName + "\\root\\cimv2", connectionOptions);
    }

    return managementScope;
}

Query the list of processes

Win32_Process  class - has a set of properties you can query. In our case we are going to selectively query the Name, CreationDate, ProcessId properties of the Win32_Process class. When we create the ManagementScope 
object, we pass in a ConnectionOptions  object that would contain the credentials of the Service Account. We also pass in the server name to which we want to query. Once we have queried, the properties specified in the select clause of the ObjectQuery are available as indexed properties of the ManagementObject object.

C#
private void GetProcessList()
{
    try
    {
        ManagementScope managementScope = GetManagementScope(txtComputerName.Text.Trim(), 
            txtUserAccountName.Text.Trim(), txtUserAccountDomain.Text.Trim(), 
            txtUserAccountPassword.Text.Trim());
        managementScope.Connect();

        ObjectQuery objectQuery = new ObjectQuery("SELECT Name, ProcessId, CreationDate FROM Win32_Process");
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(managementScope, objectQuery);
        ManagementObjectCollection managementObjectCollection = searcher.Get();

        List<Win32_Process_Subset> processList = new List<Win32_Process_Subset>();
        foreach (ManagementObject mo in managementObjectCollection)
        {
            Win32_Process_Subset process = new Win32_Process_Subset();
            process.Name = Convert.ToString(mo["Name"]);
            process.ProcessId = Convert.ToString(mo["ProcessId"]);
            process.CreationDate = Convert.ToString(mo["CreationDate"]) == string.Empty ? 
              "" : Convert.ToString(ManagementDateTimeConverter.ToDateTime(Convert.ToString(mo["CreationDate"])));

            processList.Add(process);
        }

        DisplayGridView(ref processList);
    }
    catch (Exception eX)
    {
        if (eX.InnerException != null)
        {
            MessageBox.Show(eX.Message + "\n" + eX.InnerException.Message, "An Exception Occurred!");
        }
        else
        {
            MessageBox.Show(eX.Message, "An Exception Occurred!");
        }
    }
}

Spawn a process

Use the Create  method of the Win32_Process class to create a process.

C#
ManagementScope managementScope = GetManagementScope(txtComputerName.Text.Trim(), txtUserAccountName.Text.Trim(), 
         txtUserAccountDomain.Text.Trim(), txtUserAccountPassword.Text.Trim());
 
managementScope.Connect();

ObjectGetOptions objectGetOptions = new ObjectGetOptions();
ManagementPath managementPath = new ManagementPath("Win32_Process");
ManagementClass processClass = new ManagementClass(managementScope, managementPath, objectGetOptions);
ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
inParams["CommandLine"] = txtNewProcessName.Text.Trim();
ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null);

MessageBox.Show("Create process command issued successfully for the name " + 
  txtNewProcessName.Text.Trim() + ".");

Terminate a process

Use the Terminate  method of the Win32_Process class to terminate a process.

C#
ManagementScope managementScope = GetManagementScope(txtComputerName.Text.Trim(), txtUserAccountName.Text.Trim(), 
txtUserAccountDomain.Text.Trim(), txtUserAccountPassword.Text.Trim());
 
managementScope.Connect();

ObjectQuery objectQuery = new ObjectQuery("SELECT * FROM Win32_Process WHERE Name = '" + 
  txtProcessNameToKill.Text.Trim() + "'");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(managementScope, objectQuery);
ManagementObjectCollection managementObjectCollection = searcher.Get();

foreach (ManagementObject mo in managementObjectCollection)
{
    mo.InvokeMethod("Terminate", null);
}

Service Management

When we passed the computer name, user credentials to the WMI ManagementScope object, they managed the Impersonation for us. I don’t know if it is just me or WMI is inherently slower. Looking at the 

ServiceController 
- class, it offers pretty good control over remote Service Control, so I thought of sticking of ServiceController class and its methods instead of Win32_Services class - of the WMI.

Querying the list of services

Impersonate the Service Account user using our Impersonator class, then on success call the GetServices method of the ServiceController class passing in the computer name.

C#
private void GetServiceList()
{
    try
    {
        if (Impersonator.AreValidCredentils(txtUserAccountName.Text.Trim(), 
            txtUserAccountDomain.Text.Trim(), txtUserAccountPassword.Text.Trim(), 
            LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50))
        {
            using (new Impersonator(txtUserAccountName.Text.Trim(), 
                   txtUserAccountDomain.Text.Trim(), txtUserAccountPassword.Text.Trim(), 
                   LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50))
            {
                ServiceController[] serviceList = ServiceController.GetServices(txtComputerName.Text.Trim());
                DisplayGridView(ref serviceList);
            }
        }
    }
    catch (Exception eX)
    {
        if (eX.InnerException != null)
        {
            MessageBox.Show(eX.Message + "\n" + eX.InnerException.Message, "An Exception Occurred!");
        }
        else
        {
            MessageBox.Show(eX.Message, "An Exception Occurred!");
        }
    }
}

Starting a service

Impersonate the ServiceAccount user, create a ServiceController object passing to its constructor the Service Name and Computer Name. Call the ServiceController object’s Stop() method.

Stopping a service

Impersonate the ServiceAccount user, create a ServiceController object passing to its constructor the Service Name and Computer Name. Call the ServiceController object’s Start() method.

Open Administrative Shares

Let’s imagine that our target server has a configuration file that needs to be edited or moved or replaced. Luckily, our Service Account has Write or Full Access on that file and Administrative Shares are enabled on that remote computer. Since the Administrative Shares (C$, D$, …) are enabled and our Service Account is part of the local Administrators group, we could use our impersonation and show a OpenFileDialog to allow file editing and moving.

After impersonation, the OpenFileDialog will be using the Identity of the Service Account, and you can force the InitalDirectory of the OpenFileDialog control to \\machinename\C$  or \\machinename\D$ and so on.

C++
using (new Impersonator(txtUserAccountName.Text.Trim(), txtUserAccountDomain.Text.Trim(), 
txtUserAccountPassword.Text.Trim(), LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50))
{
    OpenFileDialog openFileDialog = new OpenFileDialog();
    if (!String.IsNullOrEmpty(txtOpenFileLocation.Text))
    {
        openFileDialog.InitialDirectory = txtOpenFileLocation.Text;
    }
    openFileDialog.Multiselect = true;
    openFileDialog.ShowDialog();
}

That’s it; use some Impersonation, and WMI magic, you wouldn’t need an RDP access for a remote computer, if you know the credentials of a User account that is part of the Administrators group on that remote computer.

History

  • Initial version - Feb 19 2013

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)