介紹
這是我在繼上一篇文章"My Explorer"之後關於Windows Management Instrumentation(Windows管理規范)的又一新作。我將向你展示一些技巧,讓你可以在遠程地訪問網絡中其他計算機的操作系統、服務、當前運行著的進程等等信息,當然前提是你必須得擁有這些計算機的管理員權限。同時我也將向你展示如何利用WMI來啟動或者停止服務、終止進程、創建進程。這是程序的主界面:
開始
在這個WMI應用程序裡,我創建了一個包含了四個用戶控制的庫WMIControlLibrary。這四個用戶控制分別是Explorer,SystemInfo,Services與Processes。每個控制都有其特定的功用。以下是對每個控制作用的一個簡單描述:
Explorer控制 我把我那個"My Explorer"轉換成了一個用戶控制,它還是用來顯示你系統上的驅動器、目錄、文件等信息。
SystemInfo 控制* 這個控制用來顯示操作系統與硬件數據及清單等信息。
Services 控制* 這個控制用來顯示系統當前運行著的服務。
Process 控制* 這個控制用來顯示系統當前運行著的進程。
(*注意:這個控制可以用來監控本地或者網絡上的遠程系統。)
上述的每個控制都引用了System.Management命名空間,以保證它們能訪問各自特定的系統信息。
控制的狀態事件
這其中的一些控制需要點時間才能從系統獲取相關的信息,因此我在每個控制中都實現了一個事件UpdateStatus(string e)。這樣每個控制就可以更新主應用程序窗體的狀態條,用戶也能很清楚地知道控制正在干什麼了。
//控制內部的代碼
//聲明一個Status的事件委托類型
public delegate void Status(string e);
//聲明了一個更新狀態的事件
public event Status UpdateStatus;
//更新狀態條
UpdateStatus("Hello world.");
//主程序代碼
//用參數中的字符串刷新狀態條的顯示文本
private void refreshStatusBar(string stringStatus)
{
//更新狀態條
statusBarStatus.Text = stringStatus;
}
Explorer 控制
在Explorer控制內部,我使用了WMI的Win32_LogicalDisk類來訪問所有本地的及網絡映射的驅動器。要訪問驅動器的相關信息,我得先使用一個ManagementObjectSearcher對象來獲取一個包含了我所需驅動器信息的ManagementOjbectCollection對象(譯注:原文用的是class,我認為不准確,因此改譯為對象)。之後,我們就可以自由支配所有這些驅動器的信息。(比如驅動器名、類型、卷標、標識等等)。你也可以只查詢剩余空間低於1MByte的驅動器的信息,對此只需要改變ManagementObjectSearcher參數而已:
//譯注:這句就是查詢剩余空間低於1MByte的SQL語句,用在ManagementObjectSearcher的構造時。
//是不是很象一般數據庫編程裡用的SQL語句啊?
Select * From Win32_LogicalDisk Where FreeSpace < 1000000
//取得驅動器集
ManagementObjectSearcher query =
new ManagementObjectSearcher ("SELECT * From Win32_LogicalDisk ");
ManagementObjectCollection queryCollection = query.Get();
//遍歷每個對象,以獲取每個驅動器的信息
foreach ( ManagementObject mo in queryCollection)
{
switch (int.Parse( mo["DriveType"].ToString()))
{
case Removable: //可移動驅動器
imageIndex = 5;
selectIndex = 5;
break;
case LocalDisk: //本地驅動器
imageIndex = 6;
selectIndex = 6;
break;
case CD: //CD-ROM驅動器
imageIndex = 7;
selectIndex = 7;
break;
case Network: //網絡驅動器
imageIndex = 8;
selectIndex = 8;
break;
default: //缺省:文件夾
imageIndex = 2;
selectIndex = 3;
break;
}
//獲取驅動器名
Console.WriteLine("Drive: " + mo["Name"].ToString());
}
SystemInfo 控制
SystemInfo控制用於顯示你的本地計算機或者遠程計算機上一些不同類型的信息。它首先定義一個ConnectionOptions對象,並設置好該對象的UserName與Password屬性,准備用此來建立一個WMI的連接。之後再以該ConnectionOptions對象為參數,使用本地或遠程計算機的主機名創建一個ManagementScope對象。
//建立遠程計算機連接
ConnectionOptions co = new ConnectionOptions();
co.Username = textUserID.Text;
co.Password = textPassword.Text;
//將管理范圍確定為該遠程計算機
System.Management.ManagementScope ms = new System.Management.ManagementScope
("\\\\" + stringHostName + "\\root\\cimv2", co);
現在我們就要准備通過創建一個ObjectQuery 成員對象來訪問這個系統上的信息了。我們需要利用這個ObjectQuery對象和之前的那個ManagementScope對象來創建一個ManagementObjectSearcher對象。然後再調用該ManagementObjectSearcher對象的Get()方法來執行ObjectQuery對象定義的那個查詢命令,並將查詢結果返回到一個ManagementObject對象集中。
//查詢操作系統信息
oq = new System.Management.ObjectQuery("SELECT * FROM Win32_OperatingSystem");
query = new ManagementObjectSearcher(ms,oq);
queryCollection = query.Get();
foreach ( ManagementObject mo in queryCollection)
{
//在樹中創建一個操作系統的子結點
createChildNode(nodeCollection, "Operating System: " + mo["Caption"]);
createChildNode(nodeCollection, "Version: " + mo["Version"]);
createChildNode(nodeCollection, "Manufacturer : " + mo["Manufacturer"]);
createChildNode(nodeCollection, "Computer Name : " + mo["csname"]);
createChildNode(nodeCollection, "Windows Directory : " + mo["WindowsDirectory"]);
}
如果你只關心本地主機的信息,那你可以不用創建ConnectionOption,ManagementScope,與ObjectQuery這些對象。你只需要用SQL查詢語句串創建一個ManagementObjectSearcher對象,然後直接調用該對象的Get()方法,就能以一個ManagementObjectCollection對象的形式返回本地主機的信息了。
ManagementObjectSearcher query = new ManagementObjectSearcher("SELECT * From Win32_OperatingSystem");
ManagementObjectCollection queryCollection = query.Get();
SystemInfo控制也用於顯示計算機相關的其他信息:系統制造商,處理器,BIOS,時區,內存、網絡連接、顯卡等等。用於查詢這些信息的代碼只是在SQL查詢語句和返回屬性上不同而已,所以為了減少篇幅我就不把代碼寫出來了。具體的代碼你可以看下載包裡的內容。
Service 控制
Service控制使用了這樣的一個查詢來返回系統中所有服務的信息:
SELECT * FROM Win32_Service
為了能啟動或者停止一個服務,我為ListView動態地創建了一個彈出式菜單(上下文菜單)。你在列表的某個項上單擊鼠標右鍵時,一個啟動或停止服務(依賴於服務的當前運行狀態)的菜單就會彈出。當菜單項被點擊後,我需要利用這樣的查詢語句獲得該服務的ManagementObject對象:
SELECT * FROM Win32_Service WHERE Name = ''ServiceName''
接著我就可以通過調用ManagementObject.InvokeMethod()方法來啟動或者停止該服務了。InvokeMethod()方法的第一個參數是一個Observer。我傳遞一個ManagementOperationObserver對象給這個方法,來管理這些異步操作,以及相應的異步事件與信息。通過檢查返回的completionHandlerObj.ReturnObject的returnValue屬性,我們就可以確定操作是否成功了。
///
/// List view的鼠標右擊事件導致動態上下文菜單的生成
///
private void listViewServices_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
System.Windows.Forms.ListView listViewObject = (System.Windows.Forms.ListView) sender;
ContextMenu mnuContextMenu = new ContextMenu();
MenuItem menuItem = new MenuItem();
ManagementObjectCollection queryCollection;
//是否是鼠標右鍵單擊
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
//取得服務的名稱
ServiceName = listViewObject.GetItemAt(e.X, e.Y).Text;
//取得列表項
ServiceItem = listViewObject.GetItemAt(e.X,e.Y);
//創建彈出式菜單
listViewObject.ContextMenu = mnuContextMenu;
try
{
//取得特定的服務對象
queryCollection = getServiceCollection("SELECT * FROM Win32_Service Where Name =
''" + ServiceName + "''");
foreach ( ManagementObject mo in queryCollection)
{
//據服務的當前狀態創建相應的菜單
if (mo["Started"].Equals(true))
{
menuItem.Text = "Stop";
//設置動作Action屬性
ServiceAction = "StopService";
}
else
{
menuItem.Text = "Start";
ServiceAction = "StartService";
}
mnuContextMenu.MenuItems.Add(menuItem);
// 給菜單項掛上事件處理函數
menuItem.Click += new System.EventHandler(this.menuItem_Click);
}
}
catch (Exception e1)
{
MessageBox.Show("Error: " + e1);
}
}
}
///
/// List view上下文菜單的事件響應函數
///
///
///
private void menuItem_Click(object sender, System.EventArgs e)
{
ManagementObjectCollection queryCollection;
ListViewItem lvItem;
//設置一個異步回調函數的句柄
ManagementOperationObserver observer = new ManagementOperationObserver();
completionHandler.MyHandler completionHandlerObj = new completionHandler.MyHandler();
observer.ObjectReady += new ObjectReadyEventHandler(completionHandlerObj.Done);
//獲得特定的服務對象
queryCollection = getServiceCollection("Select * from Win32_Service Where Name =''" +
ServiceName + "''");
//更新狀態條
updateStatus("Starting/Stopping service...");
foreach ( ManagementObject mo in queryCollection)
{
//啟動或者停止服務
mo.InvokeMethod(observer, ServiceAction, null);
}
//等待,直到invoke調用完成或者超時5秒後
int intCount = 0;
while(!completionHandlerObj.IsComplete)
{
if
(intCount > 10)
{
MessageBox.Show("Terminate process timed out.", "Terminate Process Status");
break;
}
//等待0.5秒
System.Threading.Thread.Sleep(500);
//增加計數器
intCount++;
}
//檢查是否成功執行了
if (completionHandlerObj.ReturnObject.Properties["returnValue"].Value.ToString() == "0")
{
//成功
lvItem = ServiceItem;
if (ServiceAction == "StartService")
lvItem.SubItems[2].Text = "Started";
else
lvItem.SubItems[2].Text = "Stop";
}
else
{
//錯誤信息
string stringAction;
if (ServiceAction == "StartService")
stringAction = "start";
else
stringAction = "stop";
MessageBox.Show("Failed to " + stringAction + " service " + ServiceName + ".",
"Start/Stop Service Failure");
}
//清除對象
ServiceName = "";
ServiceAction = "";
ServiceItem = null;
//更新狀態條
updateStatus("Ready");
this.Update();
}
//----------------------------------
// 完整的處理器
//----------------------------------
using System;
using System.Management;
namespace completionHandler
{
///
/// MyHandler類在InvokeMethod()調用完成後處理通知
///
public class MyHandler
{
private bool isComplete = false;
private ManagementBaseObject returnObject;
///
/// 當InvokeMethod完成後觸發Done事件
///
public void Done(object sender, ObjectReadyEventArgs e)
{
isComplete = true;
returnObject = e.NewObject;
}
///
/// 取IsComplete屬性
///
public bool IsComplete
{
get
{
return isComplete;
}
}
///
/// 屬性允許訪問主函數裡的返回結果
///
public ManagementBaseObject ReturnObject
{
get
{
return returnObject;
}
}
}
}
Process 控制
Process控制顯示系統中運行著的進程,啟動進程的用戶,CPU使用率,內存的使用情況。要獲得進程的用戶信息,需要調用GetOwner(User, Domain)方法,其中的User 與Domain是傳出參數。我們如何才能從InvokeMethod()調用中取回這些傳出型參數呢?這實際取決於我們是如何實現這個InvokeMethod()方法的。如果我們不需要管理異步操作,那麼我們只需要傳遞一個string數組給InvokeMethod()以獲取傳出的參數值。否則,我們就無需給InvokeMethod()傳遞任何的參數了,而是從completionHandlerObj.ReturnObject屬性中取回傳出的參數值。
//-------------------------------------------------
//在不使用observer對象的情況下獲取進程用戶信息
//--------------------------------------------------
//為InvokeMethod()方法准備參數表
string[] methodArgs = {"", ""};
//獲取進程用戶信息
mo.InvokeMethod("GetOwner", methodArgs);
//methodArgs[0] 進程用戶
//methodArgs[1] 進程的域
//-----------------------------------------------
//在使用observer對象的情況下獲取進程用戶信息
//-----------------------------------------------
mo.InvokeMethod(observer,"GetOwner", null);
while (!completionHandlerObj.IsComplete)
{
System.Threading.Thread.Sleep(500);
}
if (completionHandlerObj.ReturnObject["returnValue"].
ToString() == "0")
structProcess.stringUserName = completionHandlerObj.
ReturnObject.Properties["User"].Value.ToString();
else
structProcess.stringUserName = "";
終止進程
終止一個特定的進程與啟動或停止一個服務類似。首先還是要取得選中進程對應的 ManagementObject 對象,然後通過調用InvokeMethod(observer, "Terminate", null) 來殺死一個進程。
//設置一個異步回調的句柄
ManagementOperationObserver observer = new ManagementOperationObserver();
completionHandler.MyHandler completionHandlerObj = new completionHandler.MyHandler();
observer.ObjectReady += new ObjectReadyEventHandler(completionHandlerObj.Done);
//獲取進程的ManagementObject
queryCollection = getProcessCollection("Select * from Win32_Process Where ProcessID =
''" + ProcessID + "''");
//更新狀態條
updateStatus("Invoking terminate process");
foreach ( ManagementObject mo in queryCollection)
{
//啟動或者停止服務(譯注:作者真懶?)
mo.InvokeMethod(observer, "Terminate", null);
}
//等待,直到invoke調用完成或者超時5秒後
int intCount = 0;
while (!completionHandlerObj.IsComplete)
{
if (intCount == 10)
{
MessageBox.Show("Terminate process timed out.", "Terminate Process Status");
break;
}
//等待0.5秒
System.Threading.Thread.Sleep(500);
//增加計數器
intCount++;
}
if (intCount != 10)
{
//InvokeMethod尚未超時
if (completionHandlerObj.ReturnObject.Properties["returnValue"].Value.ToString() == "0")
{
lvItem = ProcessItem;
lvItem.Remove();
}
else
{
MessageBox.Show("Error terminating process.",
"Terminate Process");
}
}
創建進程
要創建一個進程,我們需要調用ManagementClass 的InvokeMethod ()方法。我們可以這麼創建一個ManagementClass對象:
ManagementClass processClass = New ManagementClass(ms,path,null);
其中的ms是一個ManagementScope對象,path是一個ManagementPath對象。ManagementScope對應了一個管理操作對應的范圍。ManagementPath則提供了一個對Win32_Process進行解析與創建的封裝。在調用ManagementClass.InvokeMethod(observer, methodName, inParameters)之前,我們還需要做點其他的准備。我們得把四個傳入參數封裝到一個object數組裡。
uint32 Create(string CommandLine,
string CurrentDirectory,
Win32_ProcessStartup ProcessStartupInformation,
uint32* ProcessId);
參數說明
CommandLine - [傳入] 要執行的命令行。如果有必要,系統會自動在末尾追加一個null字符來截斷該串,表示真正要執行的文件。
CurrentDirectory - [傳入] 子進程的當前驅動器與當前目錄。這個串必須保證當前目錄能解析到一個已知的路徑。用戶可以定義一個絕對的或相對的路徑作為當前的工作目錄。如果該參數為null,新創建的進程就會使用父進程的同一路徑。這樣做是主要是為了保證操作系統外殼能確定應用程序啟動的初始驅動器和工作目錄。
ProcessStartupInformation - [傳入] 這是一個Windows進程的啟動配置,請參見Win32_ProcessStartup.
ProcessId - [傳出] 一個全局的用於標識進程的標識符。這個值的生存期自進程創建時起,至進程終結時止。 //為InvokeMethod()准備參數
object[] methodArgs = {stringCommandLine, null, null, 0};
//執行這個方法
processClass.InvokeMethod (observer, "Create", methodArgs);
下面是創建進程的實現代碼。我編寫了一個CreateProcess()函數接受一個傳入的命令行字符串stringCommandLine作為參數。當你調用CreateProcess("Calc.exe")時,就意味著創建了一個新的計算器的進程。就這麼簡單。
///
/// 在一個本地或者遠程機器上調用Create方法
///
///
private void CreateProcess(string stringCommandLine)
{
//設置一個異步回調的句柄
ManagementOperationObserver observer = new ManagementOperationObserver();
completionHandler.MyHandler completionHandlerObj = new completionHandler.MyHandler();
observer.ObjectReady += new ObjectReadyEventHandler(completionHandlerObj.Done);
string stringMachineName = "";
//連接到遠程計算機
ConnectionOptions co = new ConnectionOptions();
if (radioMachine.Checked == true)
{
stringMachineName = "localhost";
}
else
{
stringMachineName = textIP.Text;
}
if (stringMachineName.Trim().Length == 0)
{
MessageBox.Show("Must enter machine IP address or name.");
return;
}
//獲取用戶名與密碼
if (textUserID.Text.Trim().Length > 0)
{
co.Username = textUserID.Text;
co.Password = textPassword.Text;
}
//獲取指向機器的接入點
System.Management.ManagementScope ms = new System.Management.ManagementScope("\\\\" +
stringMachineName + "\\root\\cimv2", co);
//獲取進程的路徑
ManagementPath path = new ManagementPath( "Win32_Process");
//獲取將要被調用的進程的對象
ManagementClass processClass = new ManagementClass(ms,path,null);
//更新狀態條
updateStatus("Create process " + stringCommandLine + ".");
//為方法准備參數
object[] methodArgs = {stringCommandLine, null, null, 0};
//執行方法
processClass.InvokeMethod (observer, "Create", methodArgs);
//等待,直到invoke調用完成或者超時5秒後
int intCount = 0;
while (!completionHandlerObj.IsComplete)
{
if (intCount > 10)
{
MessageBox.Show("Create process timed out.", "Terminate Process Status");
break;
}
//等待0.5秒
System.Threading.Thread.Sleep(500);
//增加計數器
intCount++;
}
if (intCount != 10)
{
//InvokeMethod尚未超時
//檢查是否出現錯誤
if (completionHandlerObj.ReturnObject.Properties["returnValue"].Value.ToString() == "0")
{
//刷新進程列表
this.Refresh();
}
else
{
MessageBox.Show("Error creating new process.",
"Create New Process");
}
}
//更新狀態條
updateStatus("Ready");
this.Update();
}
總結
編寫這個演示用的WMI應用程序,增加了我不少的經驗。這只展示了WMI很小一部分的功能。我想有了我給出的注釋,代碼還容易理解吧。
你可以使用WMI完成下列工作:
控制硬件與軟件
監控事件
就某個事件運行一個腳本
就某個事件發出一封Email
本文配套源碼