共用方式為


嗨,Scripting Guy!

Hey,Scripting Guy!

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

今天的問題:如何按字母的順序列出群組的成員呢?


如何按字母的順序列出群組的成員呢?

嗨,Scripting Guy!我有一個指令碼會傳回 Active Directory 群組中所有使用者的名稱。如何按字母的順序來排序這些名稱呢?

-- JW

JW,您好。是的,談到排序資料,撰寫這類指令碼的到處都是。但不巧的是,VBScript 沒有內建的排序機制,ADSI 也沒有。因此,大部份的人就認為,不管 ADSI、WMI 或呈現的資料順序為何,他們都沒有選擇,只能接受。真的是這樣嗎?當 ADSI 傳回按字母順序排序的群組成員時,您就要任由擺佈嗎?嘿!別忘了有一句諺語說:有志者事竟成。

事實上,有很多種不同的方法排序資料,如果您想知道更多,鼓勵您收聽 2005 年 1 月的網路廣播「指令碼週 2」,一共有 10 篇 (時間尚未正式擬定,近期內會在「指令碼中心」公布詳細資料)。因為您只處理一個項目 (使用者名稱),所以我們將示範如何使用單一反昇排序 (Bubble Sort) 按字母順序傳回這些名稱。如果您需要處理並排序多個項目 (使用者名稱、電子郵件地址、設定檔路徑等),您就不會想使用反昇排序;那太複雜了。但是若是要排序這類的單一清單,反昇排序就很好用,且只需要幾行程式碼而已。

指令碼看起來像這樣:

Dim arrNames()
intSize = 0
Set objGroup = GetObject("LDAP://CN=Accountants,OU=Finance,DC=fabrikam,DC=com")
For Each strUser in objGroup.Member
    Set objUser =  GetObject("LDAP://" & strUser)
    ReDim Preserve arrNames(intSize)
    arrNames(intSize) = objUser.CN
    intSize = intSize + 1
Next
For i = (UBound(arrNames) - 1) to 0 Step -1
    For j= 0 to i
        If UCase(arrNames(j)) > UCase(arrNames(j+1)) Then
            strHolder = arrNames(j+1)
            arrNames(j+1) = arrNames(j)
            arrNames(j) = strHolder
        End If
    Next
Next 
For Each strName in arrNames
    Wscript.Echo strName
Next

以建立名為 arrNames 的動態陣列開始 (如需詳細資訊,請參閱 Microsoft Windows 2000 指令碼指南 (英文) 的這個部份)。為什麼要這樣做呢?我們已經知道了 ADSI 沒有內建的排序機制;因此,我們無法排序傳回的資料。相反的,我們必須取得資料、暫時將資料儲存在陣列中,然後排序陣列。

下一步,我們會使用這行程式碼來繫結到虛擬 Active Directory 的 Accountants 群組:

Set objGroup = GetObject("LDAP://CN=Accountants,OU=Finance,DC=fabrikam,DC=com")

繫結到群組時,取得的其中一個屬性是 Member 屬性,這個屬性剛好保存了群組的成員清單。如果只是想要回應每個群組成員的辨別名稱,且不需要排序成員清單,就可以使用像下列的程式碼:

For Each objMember in objGroup.Member
    Wscript.Echo objMember
Next

但我們不是要這個,對吧!第一,我們可能不需要像 CN=Ken Myer、OU=Finance、DC=fabrikam 及 DC=com 的辨別名稱。而是要單純的舊式一般名稱 (CNs),像 Ken Myer 這樣的名稱。因此,在 For-Each 迴圈中,我們繫結到每個使用者帳戶,就可更輕易取得每個使用者的 CN。我們使用這個程式碼來繫結到個別的使用者帳戶:

Set objUser =  GetObject("LDAP://" & strUser)

第二,我們不想馬上回應使用者名稱;我們要將這些名稱儲存在陣列中,然後在螢幕中實際顯示前,先進行排序。這就是這個程式碼的功用;它會取得每個使用者的 CN, 然後在 arrNames 陣列內建立位置儲存 CN:

ReDim Preserve arrNames(intSize)
arrNames(intSize) = objUser.CN
intSize = intSize + 1

(好吧,我們知道,如果您不習慣使用陣列,這會比撰寫程式碼看起來更不知所云。如果需要相關解釋,請參閱 Microsoft Windows 2000 指令碼指南 (英文)。)

最後,arrNames 會包含 Accountants 群組中每個使用者的 CN。我們所要作的就是用反昇排序去排序陣列。我們無法在此專欄中解釋反昇排序的詳細資訊;我們會在「指令碼週 2」中作更進一步的解釋。現在,反昇排序會有條不紊地將集合中的每個項目與其他項目進行比較,慢慢準確地判斷字母順序。例如,假設陣列看起來像這樣:

Sam
Mary
Abigail

反昇排序會開始比較 Sam 及 Mary。因為照字母的順序,Mary 在 Sam 前面,所以反昇排序會將它們在陣列中的位置對調。陣列就會看起來像這樣:

Mary
Sam
Abigail

然後,排序會比較 Sam 及 Abigail,然後再次對調在陣列中的位置。現在陣列看起來像這樣:

Mary
Abigail
Sam

我們現在知道 Sam 是陣列中的最後一個項目。就是這樣沒錯;我們已經將 Sam 與其他每個項目進行比較了。因此,在這個簡單範例中,反昇排序已完成 Sam,所以只要比較 Mary 跟 Abigail 就好了。指令碼會進行兩者的比較、對調「它們」的位置,然後提供最後的排序清單:

Abigail
Mary
Sam

這只是基本的概念,坦白說,實際的程式碼看起來更複雜:

For i = (UBound(arrNames) - 1) to 0 Step -1
    For j= 0 to i
        If UCase(arrNames(j)) > UCase(arrNames(j+1)) Then
            strHolder = arrNames(j+1)
            arrNames(j+1) = arrNames(j)
            arrNames(j) = strHolder
        End If
    Next
Next

如果您花時間去追蹤發生的事情,您會看見這個程式碼遵循了上述的程序;且看起來還不賴。唯一要注意的是,我們必須將每個名稱轉換成大寫 (使用 UCase 函數),才能進行比較。因為 VBScript 會使用每個字母的 ASCII 值來進行比較。在 ASCII 中,大寫字母一律會排在小寫字母前;因此 Zachary 會排在 anne 前面。將每個名稱轉換成大寫可確保 ANNE 排在 ZACHARY 前面。

最後,我們使用其他的 For-Each 迴圈循環 arrNames 陣列,並按字母的順序回應使用者名稱。

我們已經注意到,反昇排序不是唯一排序資料的方法,它有一些限制如下:最好是用在只有一個欄位 (如使用者名稱) 要處理的時候,且如果清單中有上百個項目,速度會很慢。如果反昇排序無法處理您的排序需求,您可以收聽「指令碼週 2」,或參閱《指令碼指南》中的中斷連接資料錄集中的簡短討論。


如需詳細資訊

查看嗨,Scripting Guy!- 過往文件

 

回到頁首 回到頁首