共用方式為


嗨,Scripting Guy!比賽即將展開!還有一些 XML 也準備好了

The Microsoft Scripting Guys

下載本文程式碼: HeyScriptingGuy2008_02.exe (151KB)

大家都知道, 每次一談到運動界的傳奇人物,總免不了提起這些名字:貝比魯斯 (Babe Ruth)、比利 (Pele)、阿里 (Muhammad Ali)、華特培頓 (Walter Payton)、ScooterK 和 MrRat、強尼 (Johnny)—

哈,您真幽默 ... 噢,您是說真的啊?您真得沒聽過 ScooterK 和 MrRat?好吧,顯然有些人還未接獲「冬季指令碼比賽」的最新消息 (將於 2 月 15 日至 3 月 3 日在 TechNet 指令碼舉辦;詳情請參閱 microsoft.com/technet/scriptcenter/funzone/games),ScooterK 和 MrRat 是貨真價實的傳奇人物,這兩位參賽者都至少在 2007 冬季指令碼比賽的其中一個組別中獲得極高分。

現在真正精彩的部份來了:您真的能像球王比利 (Pele) 一樣創造足球傳奇嗎?大概不行,您期望自己有朝一日成為重量級拳擊的世界拳王嗎?嗯,有些 Scripting Guy 的確屬於重量級,但是要成為拳擊冠軍可沒那麼簡單。不過您 — 就是您沒錯 — 卻可以很輕易地成為下一個 ScooterK 或 MrRat。

註:理論上,您也有可能成為下一個貝比魯斯 (Babe Ruth),只要您能夠在棒球雙重賽之間的空檔吃下 24 個熱狗。

別緊張;我們會教導您如何成為下一個 ScooterK 或 MrRat (甚至是下一個 Bizzy 或 H2Data)。而您只需要到指令碼中心參加「指令碼比賽」就行了。我們將在 2 月 15 日發佈 10 個不同的比賽項目 (10 項指令碼挑戰),向您下戰帖,看您是否能完成其中一個或所有挑戰。撰寫指令碼來解決我們發佈的問題,然後透過電子郵件寄給 Scripting Guy (「指令碼比賽」首頁有詳盡的指示說明)。我們將測試您的指令碼,看看是否可行,並加以評分。只要成功完成這 10 個比賽項目,您也會成為運動界的傳奇人物。至少在指令碼運動界是如此,反正也差不多啦。

「指令碼比賽」(2 月 15 日至 3 月 3 日) 有趣又充滿刺激。最棒的是,人人皆可參加「指令碼比賽」。您是系統管理指令碼的新手嗎?那就參加初學者組別;我們專為 VBScript、Windows PowerShellTM 和 (今年的新面孔) Perl 的初學者開設個別競賽。覺得初學者組別太簡單?那您一定要參加進階者組別,我們也有 VBScript、Windows PowerShellTM 和 Perl 的進階者競賽。

「指令碼比賽」(我們有沒有提過比賽期間是 2 月 15 日至 3 月 3 日?)算是指令碼旺季的一大盛事,錯過可惜喲。現在就造訪指令碼中心首頁,瞭解比賽訓練的秘訣和訣竅,等到 2 月 15 日比賽正式開始後再來參加競賽。

2 月 15 日至 3 月 3 日,再次強調,以免向隅。

您說什麼?您說的一點兒也沒錯:在新的《嗨,Scripting Guy!》專欄一開始就介紹了「指令碼比賽」的消息,這對每月的小小專欄來說內容實在是太豐富了。但是,為了取悅《TechNet Magazine》的一票好兄弟 (不用說,我們的人生目標就是取悅他們),我們甘冒這個風險,硬是發表了一篇新專欄。

就在一年前 (哇,真的過那麼久了嗎?),我們推出一篇專欄,講解如何使用指令碼來讀取 XML 檔案。但是那篇專欄中並未說明如何使用指令碼來建立、寫入和修改 XML 檔案。本月我們要亡羊補牢,您不是說想要知道如何撰寫指令碼來建立 XML 檔案嗎?只要您開口,呃,開口然後再等待一年,我們就會設法辦到。[圖 1] 顯示此指令碼。不可否認,這段指令碼看起來很複雜,不過我們會解說它的運作原理。

Figure 1 建立 XML 檔案

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")  
  
Set objRoot = _
  xmlDoc.createElement("ITChecklist")  
xmlDoc.appendChild objRoot  

Set objRecord = _
  xmlDoc.createElement("ComputerAudit") 
objRoot.appendChild objRecord 
  
Set objName = _
  xmlDoc.createElement("ComputerName")  
objName.Text = "atl-ws-001"
objRecord.appendChild objName  

Set objDate = _
  xmlDoc.createElement("AuditDate")  
objDate.Text = Date  
objRecord.appendChild objDate  

Set objIntro = _
  xmlDoc.createProcessingInstruction _
  ("xml","version='1.0'")  
xmlDoc.insertBefore _
  objIntro,xmlDoc.childNodes(0)  

xmlDoc.Save "C:\Scripts\Audits.xml"  

一開始,我們先建立 Microsoft.XMLDOM 物件的執行個體。您可能已經猜到了,這個物件可讓我們處理 XML 檔案。我們的目標是要產生一個簡單的 XML 檔案,如 [圖 2] 所示。

[圖 2] 我們的目標:簡單的 XML 檔案

[圖 2]** 我們的目標:簡單的 XML 檔案 **

若要建立此 XML 檔案,首先我們必須建立根節點 (ITChecklist)。我們該怎麼做?答案如下:

Set objRoot = _
  xmlDoc.createElement("ITChecklist")  
xmlDoc.appendChild objRoot  

這很簡單,對吧?我們只需要呼叫 createElement 方法,再將要為根節點指定的名稱傳送給 createElement。接著直接呼叫 appendChild 方法,將物件參考傳送到新項目 (objRoot) 以當做唯一的方法參數。這時,我們就建立了一個根節點了。

但還不止這些呢!接下來,我們建立 ComputerAudit 節點,這個 ITChecklist 節點的子節點是用來表示單一電腦的資訊。如您所見,建立此節點的程式碼類似於用來建立根節點的程式碼:

Set objRecord = _
  xmlDoc.createElement("ComputerAudit") 
objRoot.appendChild objRecord 

唯一的差別在於:建立根節點時,我們會在 XML 文件本身呼叫 appendChild (請注意物件參考 xmlDoc)。若要新增根節點的子節點,我們則是在根節點 (objRoot) 而非 XML 文件上呼叫 appendChild。就是這麼簡單!

將 ComputerName 與 AuditDate 節點新增為 ComputerAudit 的節點也一樣簡單。若要這麼做,我們同樣要完成一個相似的程序:我們將呼叫 createElement 來建立新節點,並呼叫 appendChild 將此新節點附加到檔案中。

(隨堂考:這次我們要從何處呼叫 appendChild?沒錯,正確解答:從 objRecord,也就是我們剛才建立的父 (ComputerAudit) 節點)。

對了,由於 ComputerName 與 AuditDate 節點都必須包含值,所以我們一定要在新增這些節點到文件之前,先分別為它們指定 Text 屬性值。

我們來看一下實際建立 ComputerName 節點的程式碼,應該就一目了然了:

Set objName = _
  xmlDoc.createElement("ComputerName")  
objName.Text = "atl-ws-001"
objRecord.appendChild objName  

您可能已經猜到,建立 AuditDate 的程式碼幾乎一模一樣。我們只需要在呼叫 createElement 時指定另一個節點名稱,並為 Text 屬性指派不同的值即可。

天啊,我們把問題搞得太簡單了,不是嗎?

針對我們的第一筆 (在本例中也是唯一的一筆) 記錄建立好節點之後,我們要執行這一小巧的程式碼:

Set objIntro = _
 xmlDoc.createProcessingInstruction _
  ("xml","version='1.0'")  
xmlDoc.insertBefore _
  objIntro,xmlDoc.childNodes(0)  

這樣只會在檔案開頭插入 <?xml version="1.0" ?>標記,以確保我們的 XML 文件格式正確。

此時,剩下的工作就是呼叫 Save 方法,將我們的新檔案儲存為 C:\Scripts\Audits.xml:

xmlDoc.Save "C:\Scripts\Audits.xml"  

就這樣,全新的 XML 文件油然而生。

事實上這還蠻有用的:您現在就知道該如何使用指令碼來建立全新的 XML 檔案了。當然,通常您不太需要建立全新的 XML 檔案;相對來說,您只會需要附加新資料到現有檔案中。那麼,Scripting Guy 是否會為大家示範怎麼做呢?

其實,一開始我們本來想回答「不」的,我們才不要為大家示範如何附加資料到現有的 XML 檔案。不過,既然我們心地善良又大方,我們決定跟《TechNet Magazine》的讀者打個商量:如果這份雜誌的讀者都同意參加「2008 冬季指令碼比賽」,那麼為了回報大家,我們會示範如何使用指令碼來附加資料到 XML 檔案。那麼,每個人都同意參加「指令碼比賽」了嗎?

我們還在等您同意喔,沒錯,就是您 — 明尼蘇達州羅契斯特市的那位先生。

這樣才像話嘛!相信我們,「指令碼比賽」一定會讓您樂在其中的;大家每次都玩得不亦樂乎,我們保證。

既然已經一言為定,我們現在就來示範將資料附加到現有 XML 檔案的指令碼。請看一下 [圖 3]

Figure 3 附加到 XML 檔案

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set objRoot = xmlDoc.documentElement
  
Set objRecord = _
  xmlDoc.createElement("ComputerAudit")
objRoot.appendChild objRecord

Set objFieldValue = _
  xmlDoc.createElement("ComputerName")
objFieldValue.Text = "atl-ws-100"
objRecord.appendChild objFieldValue

Set objFieldValue = _
  xmlDoc.createElement("AuditDate")
objFieldValue.Text = Date
objRecord.appendChild objFieldValue
  
xmlDoc.Save "C:\Scripts\Audits.xml"  

如您所見,這與建立 XML 檔案的指令碼並無太大差異。我們開始先建立 Microsoft.XMLDOM 物件的執行個體,接著將 Async 屬性設為 False,這會告訴指令碼我們要同步 (而不是非同步) 載入文件。這有什麼差別?這麼說好了,假如我們非同步載入文件,即使文件尚未完全載入,指令碼依舊可以繼續執行。不用說,如果您嘗試在還不存在的文件上執行作業,那麼很快就會帶來麻煩。藉由確保同步載入 XML 檔案,我們也可以確定在指令碼繼續執行之前,已經完全載入檔案。

一談到完全載入 — 還是算了,就當我們沒說過。接著,我們呼叫 Load 方法來開啟檔案 C:\Scripts\Audits.xml。開啟此檔案後,我們使用這一行程式碼來建立 documentElement 類別的執行個體,結果便會讓我們繫結到文件根節點。當然也就是本例中的 ITChecklist 節點:

Set objRoot = xmlDoc.documentElement

從這裡開始的工作毫不費力。我們建立 ComputerAudit 節點的新執行個體,並使用 appendChild 方法將這個新節點新增到檔案中。接著我們建立 ComputerName 和 AuditDate 節點的新執行個體,並為這些新節點各自指定適當值 (分別是 atl-ws-100 和目前日期)。我們將這兩個項目塞入剛才建立的新 ComputerAudit 節點中,呼叫 Save 方法來儲存檔案,然後再快快樂樂地繼續為即將到來的「指令碼比賽」準備指令碼。

避免我們忘了提,比賽預定於 2 月 15 日至 3 月 3 日在 TechNet 指令碼中心舉辦。

目前為止都很順利。我們能夠建立新的 XML 檔案,也能新增記錄到該檔案中。的確很不錯,但是這件事還沒完;舉例來說,我們要如何修改檔案中的現有記錄?我們至少可以採用一個方法來完成這項工作,如 [圖 4] 所示。

Figure 4 修改 XML

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit " & _
   "[ComputerName = 'atl-ws-100']/AuditDate")

For Each objNode in colNodes
   objNode.Text = Date
Next
  
xmlDoc.Save "C:\Scripts\Audits.xml"  

當我們呼叫 selectNodes 方法來決定查詢要傳回的記錄時,就浮現了這段指令碼最重要的部份。如您所見,當我們呼叫 selectNodes 時,得要細心指定兩個條件:我們只需要 ComputerName 屬性等於 atl-ws-100 的記錄,而且只要取回 AuditDate 屬性。

註:您不知道這是怎麼運作的嗎?那麼請參考一下關於使用 XML 檔案的第一篇文章 (technetmagazine.com/issues/2007/02/HeyScriptingGuy);該文有詳細解說 selectNodes 查詢語法。

一如往常,selectNodes 傳回符合指定條件的所有 XML 記錄集合。這麼一來,我們只要設定 For Each 迴圈來循環處理集合中的所有項目,然後在該迴圈中指派新的值給 AuditDate,就可以更新 AuditDate 屬性值 (我們唯一要求的屬性):

For Each objNode in colNodes
   objNode.Text = Date
Next

您說什麼?您想知道是否可以一次修改多個屬性?當然可以,只可惜今天不行;我們要留待之後的專欄再來討論。

您還有另一個問題:要如何在檔案中更新所有電腦的稽核日期?這很簡單,您可以使用 [圖 5] 中的指令碼。

Figure 5 變更稽核日期

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit/AuditDate")

For Each objNode in colNodes
   objNode.Text = Date
Next
  
xmlDoc.Save "C:\Scripts\Audits.xml"

這個指令碼與先前修改 XML 的指令碼之間唯一的不同是什麼?在這個指令碼中,我們不會指定只要檢視 ComputerName 等於 atl-ws-100 的記錄。藉由忽略這項條件,預設將傳回所有記錄。

結束之前,我們再來看一下最後這個指令碼:假設我們的老友 atl-ws-100 已經打玩最後一場牌局,然後撒手前往天國中的電腦墓園了。

(從神學的角度來看:壽終正寢的電腦最後會到哪裡?後來才知道,原來這些電腦最後通常都給了 Scripting Guy。譬如說,執筆本專欄的 Scripting Guy 有一次得到了一台膝上型電腦,原因是:「你為 Microsoft 做了不少好事,實在該得到一台膝上型電腦」。而這項慷慨之舉只有一個問題,就是這台電腦已經壞到無法開機了。這樣也無妨,事實上,它連硬碟都沒有。

為了維持良好的矽生態循環,我們必須從 XML 檔案中刪除 atl-ws-100。我們該怎麼做?請參閱 [圖 6]

Figure 6 從 XML 檔案刪除

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Audits.xml")

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit " & _
   "[ComputerName = 'atl-ws-100']")

For Each objNode in colNodes
  xmlDoc.documentElement.removeChild _
    (objNode)
Next
  
xmlDoc.Save "C:\Scripts\Audits.xml"  

您可以看到,這與我們之前用來修改 AuditDate 屬性的指令碼很像。事實上,這兩段指令碼只有兩點不同:首先,請注意我們並未費神在 selectNodes 查詢中指定任何屬性值;藉由這麼做,便會傳回 atl-ws-100 的完整 XML 記錄:

Set colNodes=xmlDoc.selectNodes _
  ("/ITChecklist/ComputerAudit " & _
   "[ComputerName = 'atl-ws-100']")

接著,在我們的 For Each 迴圈中,我們直接呼叫 removeChild 方法來刪除 ComputerName 等於 atl-ws-100 的所有記錄:

xmlDoc.documentElement.removeChild _
  (objNode)

這樣就大功告成了。永別了,英年早逝的 atl-ws-100。

好了,有哪一位《TechNet Magazine》編輯還在閱讀本月專欄嗎?您說他們全都跑去玩每月的指令碼猜謎了?太棒了!現在沒人盯著我們,聽好:放下您手邊的事 (也包括閱讀這份雜誌),快到指令碼中心,開始準備參加「指令碼比賽」。畢竟,《TechNet Magazine》隨時都可以看;但是「指令碼比賽」一年只有一次,而且期間有限 (精確的說就是 2 月 15 日至 3 月 3 日,免得您不知道)。千萬別錯過!

註:經您這麼一說,我們的確是等到您讀完整篇專欄,才要您暫停閱讀《TechNet Magazine》,不是嗎?不曉得是誰在搞鬼 ...

Dr. Scripto 的指令碼謎題 (Scripting Perplexer)

這個每月一次的挑戰不僅測試您的解謎功力,更要測試您的指令碼技巧。

2008 年 2 月:神秘的符號

電腦鍵盤包括各種奇形怪狀的字元;更奇怪的是,在指令碼語言 VBScript 或 Windows PowerShell 中,這些字元大多有特定的用途。看看您是否能將這些符號與其指令碼用途配對。第一題已經寫上答案了。

ANSWER:

Dr. Scripto 的指令碼謎題 (Scripting Perplexer)

答案是:神秘的符號,2008 年 2 月

  

The Microsoft Scripting Guys 為 Microsoft 做事,也就是受雇於 Microsoft。他們在不玩、不教或不看棒球 (以及其他各種活動) 的時候,就負責管理 TechNet 指令碼中心。請造訪他們的網站:www.scriptingguys.com

© 2008 Microsoft Corporation and CMP Media, LLC. 保留所有權利;未經允許,嚴禁部分或全部複製.