Scripting Guy 為您解答問題

Hey, Scripting Guy!

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

資源

如何撰寫每次更新記錄檔就遞增計數器的指令碼?

Hey, Scripting Guy! Question

嗨,Scripting Guy!如何撰寫每次特定記錄檔更新就遞增計數器的指令碼?

        -- HJ

Hey, Scripting Guy! Answer

HJ,您好。您知道嗎?最近撰寫本專欄的 Scripting Guy 成為干擾現象的受害者:他一直收到來自電腦的來電。過去幾個晚上,Scripting House 的電話一直響不停,而且每次電話的另一端一定是電腦:「您好,這是自動語音問卷調查...」。老實說,撰寫本專欄的 Scripting Guy 討厭講電話;當然,他尤其不喜歡透過電話與電腦交談。知道這一點之後,您應該猜得到他會針對這些問卷調查開發出自己的「自動回應」。

就算他覺得抓狂,這些事件還是他想到一般的自動回應。他想,也許為《嗨,Scripting Guy!》建立自動回應機制是可行的。這樣做應該不會太困難,而且會為他減低大量工作量。首先,這項機制需要辨認出少數關鍵字的能力,然後使用這些關鍵字來判斷所提出的問題。接著,這項機制必須使用傳統的《嗨,Scripting Guy!》格式來準備回應:也就是一連串無意義的趣聞、閒聊和修辭性疑問句,後面接著實際解決問題的指令碼。在仔細玩味這點一下子之後,他想出會產生下列結果的機制:

Script Center
Hey, HJ. You know, the Scripting Son pitched quite well last night; in fact, 
he had [insert number] strikeouts and only walked [insert number]. Did you see 
that new commercial? That has to be the [best/worst] commercial we’ve seen 
in a long time. That Scripting Editor, she sure is [mean], isn’t she? As for 
Peter Costantini, the oldest living Scripting Guy ....

反正,您能夠意會就好。我們必須承認這是個有趣的概念,而且要分辨真的專欄和假的專欄確實很難。但是,這只是假設的想法:畢竟,即使是撰寫本專欄的 Scripting Guy 也不會這麼懶惰,讓電腦為他完成所有工作。

那麼,我們還是來看一下每次特定記錄檔更新就遞增計數器的指令碼:

intModifications = 0

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colMonitoredEvents = objWMIService.ExecNotificationQuery _
    ("SELECT * FROM __InstanceModificationEvent WITHIN 1 WHERE " _
        & "TargetInstance ISA 'CIM_DataFile' and " _
            & "TargetInstance.Name='C:\\Scripts\\Test.txt'")

Do
    Set objLatestEvent = colMonitoredEvents.NextEvent
    If objLatestEvent.TargetInstance.LastModified <> objLatestEvent.PreviousInstance.LastModified Then
        intModifications = intModifications + 1
        Wscript.Echo "File modified: " & Now
        Wscript.Echo "Number of modifications: " & intModifications
        Wscript.Echo
    End If
Loop

有趣的部分來了:瞭解這段指令碼如何運作。如您所見,我們一開始的做法非常簡單:我們將值 0 指派給名為 intModifications 的變數 (不用說,這個變數就是我們在每次上述檔案修改時用來持續計算的計數器變數)。然後,我們要連線至本機電腦上的 WMI 服務,不過 (如果您已經聽過這個方法,請阻止我們) 您也可以輕易地針對遠端電腦執行這段指令碼。您只要將該電腦的名稱指派給變數 strComputer 即可。

接著就到下面這段奇怪的程式碼:

Set colMonitoredEvents = objWMIService.ExecNotificationQuery _
    ("SELECT * FROM __InstanceModificationEvent WITHIN 1 WHERE " _
        & "TargetInstance ISA 'CIM_DataFile' and " _
            & "TargetInstance.Name='C:\\Scripts\\Test.txt'")

信不信由您,這就是我們的事件訂閱查詢。我們不會在今天的專欄中說明事件訂閱的輸入和輸出內容。如果您需要這個主題的詳細資訊,可能必須查看一下關於 WMI 事件的 Scripting Week 2 網路廣播 (英文)。簡言之,我們只是要求指令碼每隔一秒 (WITHIN 1) 查看是否已經建立任何 __InstanceModificationEvent 類別的新成員 (顧名思義,這個類別的執行個體是在每次物件修改時建立的)。當然,電腦上的物件會一直進行修改:處理序會使用更多 (或更少) CPU 效能、記憶體耗用量會上下起伏,而且網路連線會啟用和停用。這樣看起來是沒問題,但是我們不想要在發生這些任何事件時收到通知。因此,我們在查詢中加入了一些條文:

  • 我們只想要在 __InstanceModificationEvent 類別的新執行個體 (TargetInstance) 剛好是檔案 (CIM_DataFile) 時收到通知。

  • 我們只想要在這個新檔案具有 C:\\Scripts\\Test.txt 的 Name (路徑) 時收到通知。

請注意:什麼?為什麼檔案路徑中含有額外的 \?其實很簡單:基本上,\ 是 WMI 中的保留字元。因此,每次您在 Where 子句中使用 \ 時,就必須「逸出」該字元。這表示您必須在每個 \ 前面加上第二個 \。因此,C:\Scripts\Test.txt 就會寫成 C:\\Scripts\\Test.txt。

到現在都沒問題吧?很好。然後,我們要建立一個設計成會一直執行的 Do 迴圈。請注意,我們並未加入任何結束條件 (例如,執行直到發生某個事件為止,或在某個條件成立時執行):

Do

在該迴圈內部,我們接著要使用下面這行程式碼來指示指令碼暫時等候,直到目標檔案 (C:\Scripts\Test.txt) 遭修改為止:

Set objLatestEvent = colMonitoredEvents.NextEvent

如果該檔案一直沒有遭修改會發生什麼事?什麼事都不會發生。指令碼將盡責地「封鎖」該行程式碼,直到宇宙毀滅為止。而且,即使宇宙毀滅,它仍將繼續等候檔案遭修改。

但是,如果該檔案修改會發生什麼事?此時,指令碼將進行兩項作業。首先,它會建立 __InstanceModificationEvent 類別的新成員。其次,由於這個新的類別成員符合事件訂閱準則,所以將導致指令碼解除封鎖。然後,它會導致指令碼執行下面這行程式碼:

If objLatestEvent.TargetInstance.LastModified <> objLatestEvent.PreviousInstance.LastModified Then

根據您想要查看事件的方式而定,這行程式碼可能是選擇性的。在測試指令碼時,我們發現只要開啟檔案 (即使沒有進行任何變更),就會觸發 __InstanceModificationEvent 類別的新執行個體。但是,我們不喜歡這點,因為這樣會讓我們難以區別某人實際修改了檔案,還是僅查看檔案。為了解決該問題,我們使用上面那行程式碼來判斷檔案上次遭修改的時間 (透過檢查 LastModified 屬性的值完成)。值得一提的是,我們的做法是比較最新版檔案 (TargetInstance) 的 LastModified 屬性與舊版檔案 (PreviousInstance) 的 LastModified 屬性。也就是說,我們會比較目前存在的檔案與建立 __InstanceModificationEvent 類別的新成員之前存在的檔案。如果這兩個日期和時間相符,那麼我們就知道檔案並未實際遭修改。

而且,如果它們並不相符,就表示已經進行了某種變更。因此,我們要執行下面這個小程式碼區塊:

intModifications = intModifications + 1
Wscript.Echo "File modified: " & Now
Wscript.Echo "Number of modifications: " & intModifications
Wscript.Echo

在此,我們先以 1 為單位遞增計數器變數 intModifications。這是因為剛進行了新的修改。然後,我們要回應兩件事:1) 檔案已經遭修改的事實,以及目前的日期和時間 (Now),以及 2) 已經進行的總修改次數 (intModifications)。接下來,我們要從頭開始執行迴圈並重複程序。

總而言之,很簡單吧,電腦也能做到。

這裡有幾點要注意。首先,我們建議您在 CScript 指令碼裝載底下的命令視窗中執行這段指令碼。為什麼呢?這完全是因為指令碼能夠將通知寫入命令視窗並持續執行的方式。如果您在 WScript 底下執行這段指令碼,就必須在每次檔案遭修改時關閉許多對話方塊。真討厭。

其次,只有當指令碼正在執行時,才會進行清點。您可能已經猜到,如果指令碼終止,那麼您就無法追蹤修改 (而且目前的清點結果將永久清除)。因此,您可能會想要修改指令碼,讓它將清點結果寫入文字檔,而非回應至螢幕上。此外,您可能會想要將這段指令碼轉換成永久事件取用者,讓 WMI 在不需要指令碼的情況下追蹤檔案修改。這並不難,只是有點複雜而已。如需永久事件取用者的詳細資訊,請參閱 MSDN 上的 WMI SDK (英文)

現在重點來了:誰撰寫本日專欄?是通常撰寫本專欄的 Scripting Guy,還是某部冷冰冰而且無思考能力的電腦盡可能剖析了問題、開始回覆一連串完全無關的趣聞,然後單純地模仿從 Scripting Editor 電腦上複製的指令碼呢?

您知道嗎?如果您會這樣問,就表示確實很難區別,對吧?