[C#] WMI 管理遠端電腦簡化工作

一些資深的 Windows 網路管理員都有 WMI(Windows Management Instrumentation),它是微軟公司便於管理大型企業網路所依循 DMTF(Distributed Management Task Force) 所制定的 WBEM(Web-based Enterprise Management)規範所實作的。而 DMTF 正是眾多、周邊、網路、通訊公司等硬體大廠為了讓管理者更便於管理大型網路所聯合組成的。

問題

但還是有很多管理者不知道它怎麼使用,沒錯我也是其中之一。等到我再次注意到它的時候是因為在當兵工作時是處理各地的使用者電腦問題,處理完要打該用戶是哪個單位哪個長官,這樣的狀況下以往的作法就是利用遠控登入他的電腦去看該登入長官是誰(或是看該長官電腦 C:\Documents and Settings 最後使用目錄是誰),再去 AD(Active Directory)查該長官是哪個單位的。

一天下來重複這樣的工作總是覺得不夠自動化,有點浪費時間有沒有更有效率的方法。

透過寫程式自動化去做,剛開始幾個想法(前提有本機最高權限)

  1. telnet 去透過 Windows Shell 取得 C:\Documents and Settings 最後使用目錄是誰,或是登入用戶是誰
    缺點:Windows 預設沒有開 Telnet,預設指令好像也沒有取得登入用戶
  2. 透過 net use 將預設共享 C$ 加到自己的網路磁碟中
    缺點:後來 Windows XP 群組原則的關係無法成功,除非將所有群組原則都改寫,而且也沒有辦法取得登入用戶
    Process p = Process.Start("net.exe", "use z: \\computername\c$ password /USER:domainname\username");
     p.WaitForExit();
    

在這樣的背景下我思考是否還有其他的解決方案,此時我想到了去年在微軟玩 OA Cloud 建立 Multi-Tenant 在 Power Shell 所使用到的 WMI,WMI 本身包含了許多物件模組,程式設計師可以透過這些模組來取得本地或遠端之 Windows 的相關訊息(如:目前登入使用者、執行程序、服務等),甚至是硬體資訊。以往這些資訊要取得在 .NET 架構下都必須透過 DllImport 的技術,這些對熟悉用 C++ 開發 Windows 的用戶或是不難,但是如果要取得遠端電腦的資訊呢?有沒有簡單支援遠端的辦法嗎?很高興的是 WMI 為我們想到了一切,WMI 最早出現在 Windows 2000 系統上,但他同樣可以額外裝在 Windows NT 4 和 Windows 9x 上。更令人興奮的是 Windows XP 以上的版本都內建了這個服務甚至是啟動它,這對管理者來說真的一大福音,這樣我們就可以將很多遠端管理的問題透過這個服務介面來處理。

像是

  • 遠端登錄編輯器的操作
  • 作業系統與硬體相關資訊的取得
  • 作業系統服務的管理
  • 作業系統服務的管理…等

Multi-Tenant

WMIC(Windows Management Instrumentation Command-line)命令提示介面

WMI 提供了一個 WMIC(Windows Management Instrumentation Command-line) 命令提示介面供我們調試操作,它主要分為兩個模式

  • 交互模式(Interactive mode):在命令提示字元下輸入 WMIC 會進入 WMIC 的交互模式,每當指令執行完畢後,系統還會返回到 WMIC 提示號下,它可以讓使用者調試多個指令操作。
  • 非交互模式(Non-Interactive mode):必須將 WMIC 指令當作參數放在 WMIC 後面,當指令執行完畢後會回原命令提示字元下,而不是進入到 WMIC 提示號下。

交互模式(Interactive mode)

非交互模式(Non-Interactive mode)

透過在指令介面我們嘗試建構出我們需要的 WMI Query 語法,再轉化成其他語言的程式碼。但這些制式化的動作微軟已經好心的提供了 WMI Code Creator v1.0工具可以協助產生VBScript、C# 與 VB .NET 的程式碼。

關於它的管理與安全性操作可以在微軟管理主控台MMC(Microsoft Management Console)透過新增/移除嵌入式管理單元加入WMI控制

MMC之WMI控制

透過了解 WMI 這些基本的操作我們就可以開始使用 WMI 撰寫工作上幫助處理管理網路的工作了,使用 C# 開發時需要加入System.Management 的參考,並在開頭引入

using System.Management;

C# WMI 取得登入使用者(logon user)

public string logonUser(string ip)
{
    string _user = string.Empty;
    try
    {
        ConnectionOptions co = new ConnectionOptions();
        co.Username = "computer_account";                      // 使用者名稱
        co.Password = "computer_password";                         // 使用者密碼
        co.Authentication = AuthenticationLevel.Default;    // 認證模式設定 (採用預設)
        co.Impersonation = ImpersonationLevel.Impersonate;  // 設定 COM 模擬等級
        co.EnablePrivileges = true;
        co.Authority = "ntdlmdomain:DOMAIN";

        System.Management.ManagementScope ms
        = new System.Management.ManagementScope("\\\\" + ip + "\\root\\cimv2", co);

        System.Management.ObjectQuery oq =
        new System.Management.ObjectQuery("SELECT * FROM Win32_ComputerSystem");
        ManagementObjectSearcher query = new ManagementObjectSearcher(ms, oq);

        ManagementObjectCollection queryCollection;
        queryCollection = query.Get();

        foreach (ManagementObject mo in queryCollection)
        {
            _user = mo["UserName"].ToString();
        }

        char[] charSeparators = new char[] { ' ' };

        int deger = _user.Split(charSeparators, StringSplitOptions.None).Length;
        _user = _user.Split(charSeparators, StringSplitOptions.None)[deger - 1];

    }
    catch (System.Runtime.InteropServices.COMException) {
        return "offline";
    }
    catch (Exception)
    {
        return "ERROR";
    }
    return _user;
}

C#遠端目錄列表(remote directory list)

public ArrayList getRemoteDir(string ip, string dir)
{
    ArrayList dirlist = new ArrayList();
    try
    {
        ConnectionOptions options = new ConnectionOptions();
        options.Username = "computer_account";   // 使用者名稱
        options.Password = "computer_password";   // 使用者密碼
        options.Authentication = AuthenticationLevel.Default;    // 認證模式設定 (採用預設)
        options.Impersonation = ImpersonationLevel.Impersonate;  // 設定 COM 模擬等級
        options.EnablePrivileges = true;

        System.Management.ManagementScope ms
        = new System.Management.ManagementScope("\\\\" + ip + "\\root\\cimv2", options);

        //目錄名稱所在的 WMI object : cimv2\Win32_Directory , property = name
        System.Management.ObjectQuery oq =
        new System.Management.ObjectQuery(@"Associators of {Win32_Directory.Name='" + dir + "'} Where AssocClass =Win32_Subdirectory ResultRole = PartComponent"); ;

        ManagementObjectSearcher query = new ManagementObjectSearcher(ms, oq);

        ManagementObjectCollection queryCollection = query.Get();
        foreach (ManagementObject mo in queryCollection)
        {
            dirlist.Add(new DirObj(mo.Properties["Name"].Value.ToString(), mo.Properties["CreationDate"].Value.ToString()));
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    return dirlist;
}

常用問題

如果在程式中遇到了 RPC 伺服器不可用。 (異常來自 HRESULT:0x800706BA),請檢查
  • 是否擁有遠端最高權限用戶
  • 是否有開啟 WMI
  • 打開防火牆遠端登入 netsh firewall set service RemoteAdmin
  • 另外也要檢查程式碼中 ConnectionOptions 中的驗證訊息是否正確
HRESULT:0x800706BA

結論

透過撰寫 WMI 的程式真的幫助我省下很多在 Windows 網路管理上的時間,它還有很多,日後如果有什麼遠端管理上的問題想自動化,不如就先想到有這個服務可以幫助我們,像是取得網路上各個電腦的 RAM 大小,主機板型號等。希望大家都可以在工作上省下更多時間陪陪家人或在學習更多的事物。

參考資料

C# WMI 程式

工具

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *


*