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 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.
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.
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.
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.
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.
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.
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.
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