Scripting Guy 為您解答問題

Hey, Scripting Guy!

歡迎使用 TechNet 專欄,Microsoft Scripting Guy 會在此為您解答有關系統管理指令碼的常見問題。您有關於系統管理指令碼方面的問題嗎?請將電子郵件傳送到 scripter@microsoft.com。我們無法保證能夠逐一回答每個問題,不過我們會盡力而為。

資源

如何取得已在電腦上開啟處理序之所有使用者的清單?

Hey, Scripting Guy! Question

嗨,Scripting Guy!如何取得已在電腦上開啟處理序之所有使用者的清單?

        -- RE

Hey, Scripting Guy! Answer

RE,您好。您知道嗎?過去 5 年以來,觀看主要電視新聞網 (ABC、NBC 和 CBS) 的人數已經突然減少。每天閱讀報紙的人數更大幅降低。同時,每天造訪 Script Center 的人數卻急遽增加。這只代表一件事:當人們需要新聞和資訊時,他們會先到 Script Center 來看看。

Dr. Scripto

但是,你怎麼能怪他們呢?雖然我們確定主要電視網和報紙用心良苦,但是它們卻沒有涵蓋每日的重要報導。例如,假設您想要知道 Dr.Scripto 搖頭玩偶 (英文) 的目前狀態。您會在紐約時報中尋找這項資訊嗎?不會吧。還是 CBS 會在常態性節目中插入有關搖頭玩偶的新聞呢?想得美。那麼,在哪裡可以收到最新的搖頭玩偶快報呢?

這還用說。

說到這裡,我們剛好一則最新的搖頭玩偶快報:搖頭玩偶已製作完成而且準備出貨。

事實上,Scripting Guy 真的收到一盒搖頭玩偶,而且它們很酷。首先,它們很有質感,並非採用廉價的輕薄塑膠製成 (就像 Scripting Guy Dean Tsaltas 一樣)。而且,它們的頭真的會搖 (就像 Scripting Guy Peter Costantini 一樣)。世界上還有比 Dr. Scripto 搖頭玩偶更棒的東西嗎?沒有,絕對沒有。

為了過去 6 週以來坐立難安的讀者們,Scripting Guy 已經填妥所有書面文件並將正確的姓名和地址傳給搖頭玩偶製造商。我們預計他們會在這幾天內開始寄送搖頭玩偶。如果您在「2007 冬季指令碼比賽 (英文)」期間成為幸運的贏家,等待就快結束了。

但是,如果您在「2007 冬季指令碼比賽」期間並非幸運的贏家呢?是不是表示您永遠無法獲得自己的 Dr. Scripto 搖頭玩偶?是的,完全正確。很可惜吧。人生就是如此,不是嗎?

等一下,先別哭。對不起嘛。好吧,告訴您一個好消息:如果您保證會參加明年二月的「2008 冬季指令碼比賽」,而且如果您保證會讀完今天的專欄,我們就會提供另一個 (可能吧) 贏得 Dr. Scripto 搖頭玩偶的機會。但是,不論您怎麼做,請不要嘗試作弊,直接向下捲到今日專欄的結尾。這是沒有用的。

真的喔?好吧。當我沒說。Scripting Editor 剛剛告訴我們直接向下捲到今日專欄的結尾有效的。但是,請別這樣做。畢竟,如果您這樣做,您就永遠無法瞭解如何取得已在電腦上開啟處理序之所有使用者的清單:

Script Center
strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process")

Set objDictionary = CreateObject("Scripting.Dictionary")

For Each objProcess in colProcessList
    objProcess.GetOwner strNameOfUser, strUserDomain
    strOwner = strUserDomain & "\" & strNameOfUser

    If Not objDictionary.Exists(strOwner) Then
        objDictionary.Add strOwner, strOwner
    End If
Next

For Each strKey in objDictionary.Keys
    Wscript.Echo strKey
Next

那麼,這段指令碼如何運作?基本上,它...等一下:您真的對這段指令碼感興趣,還是只是假裝感興趣,以便協助您取得搖頭玩偶呢?喔,好啦。如果是這樣,這段指令碼一開始會先連線到本機電腦上的 WMI 服務 (雖然我們也可以針對遠端電腦執行相同的指令碼)。完成連線之後,我們接著要使用下面這行程式碼來傳回上述電腦中執行之所有處理序的集合:

Set colProcessList = objWMIService.ExecQuery("Select * From Win32_Process")

一旦我們將集合安全地藏在變數 colProcessList 中,接著就可以建立 Scripting.Dictionary 物件的執行個體:

Set objDictionary = CreateObject("Scripting.Dictionary")

為什麼要建立 Scripting.Dictionary 物件的執行個體呢?我們稍後會說明這點。

不過,在這樣做之前,我們必須先設定 For Each 迴圈,以便逐一執行集合中的所有處理序。在該迴圈中,我們要先執行下面兩行程式碼:

objProcess.GetOwner strNameOfUser, strUserDomain
strOwner = strUserDomain & "\" & strNameOfUser

這裡的動作是要做什麼呢?基於某些原因,Win32_Process 類別並未包含可告知您處理序擁有者的屬性 (處理序擁有者與用以執行處理序的帳戶相同)。不過,我們必須針對集合中的每個處理序呼叫 GetOwner 方法,以便判斷處理序擁有者。如您所見,當我們呼叫 GetOwner 時,我們提供了兩個參數。在此情況下,就是 strNameOfUserstrUserDomain。這兩個參數都是「輸出」參數。輸出參數只是我們提供給方法的變數而已。接著,該方法會將適當的值填入這兩個變數中。在此指令碼中,這表示 GetOwner 會將處理序擁有者的名稱指派至 strNameOfUser,然後將該使用者的網域指派至 strUserDomain。沒錯,這些名稱完全是任意的。我們可以將輸出參數命名為任何名稱。您比較想要擁有名為 xy 的輸出參數嗎?沒問題:

objProcess.GetOwner x, y

這裡的重點不是名稱,而是順序:第一個指定的變數會取得擁有者名稱,而第二個指定的變數會取得擁有者網域。

一旦我們擁有該項資訊,接著就可以使用第二行程式碼,以這種格式結合名稱和網域:網域\使用者名稱 (例如,FABRIKAM\kenmyer)。

這很簡單,對吧?

請注意:不過,就算您同意 Scripting Guy 的說法,您也不會因此得到搖頭玩偶。但是,這樣做也沒有任何壞處。

接著就到下面程式碼區塊:

If Not objDictionary.Exists(strOwner) Then
    objDictionary.Add strOwner, strOwner
End If

這時候就需要用到 Dictionary 物件了。在電腦上執行的每個處理序都具有一個擁有者。如果您在電腦上執行 50 個處理序,那麼就會有 50 個處理序擁有者。不過,您不太可能會擁有 50 個唯一的擁有者,它們一定會重複 (例如,大部分處理序會在 NT AUTHORITY\SYSTEM 帳戶底下執行)。基本上,我們不需要也不想要看見所有重複的名稱。但是,我們要如何單獨回應唯一的使用者名稱呢?

其中一種方式就是使用 Dictionary 物件。如您所見,在第一行程式碼中,我們使用 Exists 方法來判斷是否能在 Dictionary 中找到第一個處理序的擁有者 (例如,FABRIKAM\kenmyer)。假設無法在 Dictionary 中找到這個處理序擁有者(當然,第一個處理序的擁有者不會顯示在 Dictionary 中,因為第一次執行迴圈時 Dictionary 將會清空)。在這種情況下,我們接著呼叫 Add 方法,將處理序擁有者新增至 Dictionary (使用變數 strOwner 來指定 Dictionary 機碼和項目):

objDictionary.Add strOwner, strOwner

請注意:什麼?您說您不瞭解 Dictionary 物件如何運作?嘖嘖嘖:表示您還沒讀過 魔咒程式碼 (英文),對吧?

接著我們再執行迴圈,並且針對集合中的下一個處理序重複此程序。假設第 2 個處理序也由 FABRIKAM\kenmyer 擁有。此時,當我們呼叫 Exists 方法時,就會在 Dictionary 中找到處理序擁有者。然後會發生什麼事?什麼事都沒有。我們只要執行迴圈,並且針對集合中的第三個處理序從頭再執行一次。

當我們擷取每個處理序的擁有者完成時,接著就要回應 Dictionary 機碼的值。巧合的是,這些機碼將代表唯一的處理序擁有者集合:

NT AUTHORITY\SYSTEM
NT AUTHORITY\NETWORK SERVICE
NT AUTHORITY\LOCAL SERVICE
FABRIKAM\kenmyer

至此您已經找到答案。

注意:如果您在 Windows Vista 上執行此指令碼,當您「以系統管理員身分執行」而非僅以系統管理員身分執行指令碼時,將會看見不同的結果。這樣瞭解了嗎?還是不太瞭解?這就表示,如果您以系統管理員的身分登入、開啟命令提示字元,然後執行此指令碼,就無法取得處理序擁有者的完整清單。若要取得完整清單,您必須以滑鼠右鍵按一下命令提示字元捷徑,然後選取 [以系統管理員身分執行]。當您從視窗執行指令碼時,就會看見處理序擁有者的完整清單。

那麼,那些搖頭玩偶怎麼處理?好吧,其實 Scripting Guy 會參加今年在佛羅里達州奧蘭多市舉辦的 TechEd (英文) 會議 (六月 4-8 日)。在活動期間,Scripting Guy 將會花大部分的時間坐在展覽大廳的 TechNet Magazine 攤位中,發送有趣的小贈品搖頭玩偶 (共有多少個搖頭玩偶呢?這是個好問題。不過,我們必須先等到瞭解預算情況,才能回答)。如果您想要擁有另一次獲得搖頭玩偶的機會,那麼您只需要報名參加 TechEd、走到 TechNet Magazine 攤位、向 Scripting Guy 打聲招呼,然後就可以參加抽獎了。

當然,聽起來要做的事不少。不過,既然大獎是 Dr. Scripto 搖頭玩偶,您還有什麼好苛求的呢?

您說今年無法到奧蘭多市?呃,幸好,我們也會參加十一月的 TechEd Europe (英文)。您連 TechEd Europe 也沒辦法參加嗎?唉,那您要我們怎麼辦?就把搖頭玩偶發送給要求的每個人嗎?喔。真的很抱歉:我們真的沒有這種做法的預算。但是,最後我們來打個商量吧:在 TechEd 結束之後,我們會看看有什麼讓大家獲得搖頭玩偶的最後機會。因為我們同意您的看法:沒有 Dr. Scripto 搖頭玩偶的生活根本不算是生活,對吧?

使用 Windows PowerShell 判斷處理序擁有權

這個問題的 Windows PowerShell 解決方案會透過提供 Group-Object cmdlet (根據選取屬性的值將物件分組),輕鬆地解決問題。Group-Object 命令的預設顯示會列出屬性值、每個群組中物件的名稱,以及每個群組中物件的數目。因此,這裡就不需要 Scripting Guy 巧妙的指令碼字典技巧了。

下列 Windows PowerShell 命令會回答使用者的問題:

get-wmiobject win32_process | group-object -property {$_.getowner().domain}, {$_.getowner().user}

輸出內容如下。雖然其輸出並非與 VB Script 的輸出完全相同,但是我們尚未進行任何努力,而且將取得更多資訊。

Count Name                      Group
----- ----                      -----
    1                           {System Idle Process}
   27 NT AUTHORITY, SYSTEM      {System, smss.exe, csrss.exe, winlogon.exe...}
    5 NT AUTHORITY, NETWORK ... {svchost.exe, svchost.exe, AdtAgent.exe...}
    4 NT AUTHORITY, LOCAL SE... {svchost.exe, wdfmgr.exe, alg.exe...}
    3 SERVER01, Administrator   {init, inetd, cron}
   31 DOMAIN01, user01          {explorer.exe, smax4pnp.exe...}

若要清除截斷的 Name 值,您可以將它傳送至 Format-Table cmdlet 並使用其 -Wrap 參數。

get-wmiobject win32_process | group-object -property {$_.getowner().domain}, {$_.getowner().user} | format-table -wrap

瞭解命令

這則命令會使用 Get-WmiObject cmdlet 搭配 Win32_process 類別。它會針對電腦上的每個處理序傳回 Win32_Process 物件。

get-wmiobject win32_process 

我們可以使用管線運算子 (|),將結果傳送至 Group-Object cmdlet。Group-Object 具有 -Property 參數,其中指定分組依據的屬性。如果您提供一個以上的屬性,它就會先依據第一個屬性值分組,然後再依據後續屬性值分組。

get-wmiobject win32_process | group-object -property

在此情況下,我們要跟隨英雄 Scripting Guy 的腳步 [尊敬地鞠躬]。我們必須提供一個屬性給 Group-Object。但是首先,我們要使用 GetOwner 方法來取得具有 Domain 和 User 屬性的物件。

針對每個屬性,我們要使用 $_ 符號來代表目前的物件,也就是 Get-WmiObject 透過管線傳送至 Group-Object 的每個 Win32_process 物件。$_ 後面會接著一個點 (.)、方法的名稱 (GetOwner),以及一組括號,表示它是方法而非屬性。

$_.getowner()

接著,我們要取得 GetOwner 方法傳回之物件的 Domain 和 User 屬性。只要在方法後面加上另一個點 (.) 以及屬性名稱即可。

$_.getowner().domain
$_.getowner().user

因為我們已經撰寫要取得每個屬性的運算式,所以我們要將每個運算式括在大括號 {} 中。

{$_.getowner().domain}
{$_.getowner().user}

我們將使用 Group-Object 的 -Property 參數來指定分組依據的兩個屬性。當您針對 Windows PowerShell 參數指定一個以上的值時,就要使用逗號隔開這些值。此外,請務必以正確的分組順序指定這些值。首先是網域,然後才是使用者。

group-object -property {$_.getowner().domain}, {$_.getowner().user}

然後,我們會將它放在一起。

get-wmiobject win32_process | group-object -property $_.getowner().domain}, {$_.getowner().user}

處理序物件與處理序物件

就 Windows PowerShell 使用者而言,這項工作的重要部分 (Scripting Guy 為我們做的部分) 就是要瞭解 Get-Process cmdlet 傳回的處理序物件與 WMI 傳回的處理序物件之間的差異。

讓我們使用 Get-Member cmdlet 來查看一下這些物件。Get-Member 會取得物件的 .NET 類型及其所有屬性和方法。

首先,我們要將 Get-Process 命令的結果傳送至 Get-Member。

get-process | get-member

它會顯示出您擁有 System.Diagnostics.Process 物件,而且它會列出該物件的所有屬性和方法。雖然我在此不會列出這些項目,但是您可以在自己的電腦上或在 MSDN (英文) 中查看這些項目。

然後,我們要將 Get-WmiObject Win32_process 命令的結果傳送至 Get-Member。

get-wmiobject win32_process | get-member

哇!我們得到 System.Management.ManagementObject#root\cimv2\Win32_Process 物件 (基本上是 WMI 處理序物件)。同時,我們也得到一些非常有用的屬性和方法 (根據 MSDN (英文))。

這些物件具有一些常用屬性,例如 WorkingSet (Get-Process)、WorkingSetSize (WMI) 和 SessionID (兩者都有)。雖然它們具有一些常用方法 (例如 Terminate (WMI) 和 Kill (Get-Process)),但是它們也具有許多無關的屬性和方法。此外,如果您使用 Windows PowerShell,就是一項優勢,因為您同時擁有這兩者。

因此,下次您需要物件的某些資訊時,請同時檢查物件的 PowerShell 和 WMI 版本。

喔,對了,您一定要盡所有努力獲得 Dr. Scripto 搖頭玩偶。我有一個搖頭玩偶放在桌上 (貼有 Windows PowerShell 標誌),人生便從此大不相同了。