NTRemoteProcessControl – 通过 WMI 和 WinForms 枚举和控制 Windows 进程和服务
枚举本地管理员组中具有 Windows 身份的远程进程和服务。
引言
您是否曾遇到过一些应用程序以管理员特权身份运行的 Windows 服务器?有一天,该应用程序无法运行,您想重新启动它。您的域 AD 帐户被拒绝 RDP 或任何类型的访问这些 Windows 服务器的权限,但是用于在服务器上运行这些应用程序的帐户(身份)是一个域帐户,并且它已添加到本地管理员组,您知道其凭据。我猜这些被称为服务帐户(至少在企业用语中),它们通常没有交互式访问权限,也就是说,使用此服务帐户,您无法通过 RDP 登录;如果您能够使用服务帐户 RDP 到这些 Windows 服务器,也许您在不知不觉中违反了您的 IT 协议 ,因为服务帐户并非用于 RDP 会话。但情况很危急,业务用户一直在催促您更新,您必须尽快重新启动作业。
如果您管理的是属于本地管理员组的服务帐户(用作运行作业的身份)的凭据,那您很幸运。您有多种选择,可以使用 runas 命令,或者使用 Sysinternals 的 psexec,来模拟服务帐户并重新启动进程。如果您需要启动或停止 Windows 服务,您可以从 services.msc 连接到目标计算机并操作远程计算机上运行的服务。另一种选择是您可以编写一个简单的 WinForms 应用程序来为您完成所有这些工作,并将其命名为 NTRemoteProcessControl。
背景
要操作进程,我们将使用 WMI,而要操作 Windows 服务,我们将使用 ServiceController 类——通过模拟。代码也像说起来一样简单。Impersonator.cs 的原始源代码来自 http://platinumdogs.me/2008/10/30/net-c-impersonation-with-network-credentials/。
通过 System.Management
命名空间使用 WMI 进行的任何操作都涉及创建 ManagementScope
、ObjectQuery
、ManagementObjectSearcher
、ManagementObjectCollection
的标准过程。关于上述所有类,已有充足的文档。请允许我安抚一下,并参考 MSDN。我们将只关注获取几个进程的属性并将其绑定到 GridView。
进程管理
创建 ManagementScope 对象
ManagementScope
对象定义了我们要连接的计算机,以及在查询时应使用的该计算机上的用户帐户凭据。
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;
}
查询进程列表
Win32_Process 类——具有一组可以查询的属性。在本例中,我们将有选择地查询 Win32_Process
类的 Name
、CreationDate
、ProcessId
属性。当我们创建 ManagementScope
对象时,我们会传入一个 ConnectionOptions
对象,其中包含服务帐户的凭据。我们还会传入我们想要查询的服务器名称。一旦查询完成,ObjectQuery
的 select 子句中指定的属性将作为 ManagementObject
对象的索引属性可用。
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!");
}
}
}
生成进程
使用 Win32_Process
类的 Create
方法来创建进程。
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() + ".");
终止进程
使用 Win32_Process
类的 Terminate
方法来终止进程。
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);
}
服务管理
当我们向 WMI ManagementScope
对象传递计算机名称和用户凭据时,它们已经为我们处理了模拟。我不知道是我个人感觉还是 WMI 本身就比较慢。查看 ServiceController
类,它提供了对远程服务控制的相当好的控制,所以我想坚持使用 ServiceController
类及其方法,而不是 WMI 的 Win32_Services
类。
查询服务列表
使用我们的 Impersonator 类模拟服务帐户用户,然后在成功时调用 ServiceController
类的 GetServices
方法,并传入计算机名称。
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!");
}
}
}
启动服务
模拟 ServiceAccount 用户,创建一个 ServiceController
对象,将其构造函数传入服务名称和计算机名称。调用 ServiceController
对象的 Stop()
方法。
停止服务
模拟 ServiceAccount 用户,创建一个 ServiceController
对象,将其构造函数传入服务名称和计算机名称。调用 ServiceController
对象的 Start()
方法。
打开管理共享
让我们设想一下,我们的目标服务器有一个需要编辑、移动或替换的配置文件。幸运的是,我们的服务帐户对该文件具有写入或完全访问权限,并且远程计算机上启用了管理共享。由于管理共享(C$、D$ 等)已启用,并且我们的服务帐户是本地管理员组的成员,我们可以使用我们的模拟功能,并显示一个 OpenFileDialog
来允许文件编辑和移动。
模拟后,OpenFileDialog
将使用服务帐户的身份,您可以将 OpenFileDialog
控件的 InitalDirectory
设置为 \\machinename\C$ 或 \\machinename\D$ 等。
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();
}
就是这样;使用一些模拟和 WMI 魔法,如果您知道属于该远程计算机上管理员组的用户帐户的凭据,您就不需要 RDP 访问远程计算机了。
历史
- 初始版本 - 2013 年 2 月 19 日