嗨,Scripting Guy!我們最愛的 Shell 遊戲
The Microsoft Scripting Guys
下載本文程式碼: HeyScriptingGuy2008_03.exe (150KB)
有句 古語說,再小的麻雀落到地上,神都看得到。別誤會,Scripting Guy 可不是在建議各位去把天上的麻雀打下來(當然嘍,如果有人想要出面料理一下禮拜六大清早在屋頂上鬼叫的不識相的烏鴉,那就另當別論了。[Scripting Editor 是很愛烏鴉的,所以我們非常肯定撰寫本文的 Scripting Guy 所說的「料理」,是指「讓它們健健康康」的意思啦-小編]) 不過,知道有人關心你跌倒,而且不管你看起來多小多不起眼,總是有人在某個地方照應你,還是很窩心的。[就算他只是個小編。-小編]
烏鴉如此,系統管理員也是如此。我們可以這麼說,在指令碼的世界裡,大部分的焦點都落在指令碼的龍頭老大身上:Windows PowerShellTM、VBScript、WMI、ADSI,甚至 FileSystemObject。也難怪這些技術會搶盡所有的鋒頭,它們的確可以讓您做許多既酷眩又實用的事情。不過那不表示它們是指令碼撰寫人員唯一能夠使用的技術。絕對不是。
就拿 Shell 物件為例吧。Shell 物件也許不像 Windows PowerShell 那麼知名,那麼廣被宣傳,但它還是很重要 — 而且很實用。
遲早有一天會有人要求您以 Shell 物件撰寫系統管理指令碼的。不過這就產生一個問題了:您可以用 Shell 物件做哪些事情呢?事實上,您可以用 Shell 物件做許多很酷的事情,其中一部分 (例如,在複製或移動檔案時,管理磁碟配額和顯示進度列) 在<Microsoft® Windows® 2000 指令碼手冊>(英文) (microsoft.com/technet/scriptcenter/guide) 已有說明。在本期專欄當中,我們要另外告訴您 Shell 物件還能做哪些很眩的事情,那些事情您也許連想都沒想過能夠做到,更別提用 Shell 物件來做了。
變更檔案的上次修改日期
很多人也許一看這個標題就想:「慢著,檔案的上次修改日期是不能用指令碼更改的吧,至少不能用 VBScript。」而我們的反應則是:「您怎麼會有這種想法呢?」
對啦,我們 Scripting Guy 也許說過這句話啦。不過結果證實,我們錯了。想不到吧!竟然可以用 VBScript 更改檔案的上次修改日期,而且只要用 Shell 物件就辦得到。
附註:至於 Scripting Guy 怎麼會出這種錯,這您就別再問了;過了這麼多年,應該很清楚了吧。真要問的話,下面這個問題還更有意思:到底我們是怎麼做對的?
言歸正傳,您說您想要更改檔案的上次修改日期對吧?就用這個指令碼:
Set objShell = _
CreateObject("Shell.Application")
Set objFolder = _
objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
objFolder.ParseName("Dr_Scripto.jpg")
objFolderItem.ModifyDate = _
"01/01/2008 8:00:00 AM"
您瞧,一點都不複雜。一開始先建立 Shell.Application 物件的執行個體 (對了,別把 Shell.Application 和 Wscript.Shell 搞混了,後面那個是 Windows Script Host Shell 物件,而我們今天用的 Shell 物件是 Windows Shell 物件)。等 Shell 物件啟動並執行之後,再用 Namespace 方法繫結到 C:\Scripts 資料夾,然後用名字古怪的 ParseName 方法繫結到資料夾中的指定檔案。在本例中,我們要繫結到檔名為 Dr_Scripto.jpg 的 JPEG 影像:
Set objFolderItem = _
objFolder.ParseName("Dr_Scripto.jpg")
繫結到檔案本身之後,接下來要怎麼做呢?很簡單,只要把新的日期和時間指派給 ModifyDate 屬性就行了:
objFolderItem.ModifyDate = _
"01/01/2008 8:00:00 AM"
就這樣。
沒錯。怎麼樣?很酷吧?只要執行這個指令碼之後,檔案的上次修改日期就會設為 2008 年 1 月 1 日早上 8 點 (您也可以在 Windows 檔案總管開啟 C:\Scripts 資料夾親自驗證)。
也許有人在想:「酷呆了,我可以更改檔案的上次修改日期,可是更改檔案的上次修改日期到底有什麼意義呢?」這個嘛,很多人常把上次修改日期作為一種版本控制系統。比方說,假設您有很多指令碼複本,如果想追蹤官方版的指令碼,其中一個方法就是複查上次修改日期。您可以藉由檢查上次修改日期,分辨出某一個複本是不是未經修改的原始指令碼版本。
附註:當然嘍,也許有人可以執行我們剛剛示範的指令碼來變更上次修改日期。但是只要您所做的動作沒有一一進行程式碼簽署,別人就有辦法入侵系統。假設您跟一群很有道德感的朋友們共用指令碼,採用這個方法肯定是明智之舉。
那麼,如果您想要更新所有指令碼的上次修改日期,並且將它們標準化,又該怎麼做呢?假設所有的指令碼都在 C:\Scripts 資料夾當中,只要執行下面這個程式碼區塊就行了:
Set objShell = _
CreateObject("Shell.Application")
Set objFolder = _
objShell.NameSpace("C:\Scripts")
Set colItems = objFolder.Items
For Each objItem In colItems
objItem.ModifyDate = _
"01/01/2008 8:00:00 AM"
Next
您可以看到這個指令碼剛開始跟第一個指令碼非常類似,但在本例中,當我們繫結到 C:\Scripts 資料夾之後,並不是用 ParseName 個別繫結到資料夾中的各個檔案,而是改用下面這行程式碼,傳回資料夾中所有的檔案:
Set colItems = objFolder.Items
收到檔案集合之後,接著就設定 For Each 迴圈,對集合中的每一個項目重複執行同樣的動作。在 For Each 迴圈中,我們使用下面這行方便好用的程式碼,把集合中第一個檔案的 ModifyDate 屬性值改為 2008 年 1 月 1 日早上 8 點:
objItem.ModifyDate = _
"01/01/2008 8:00:00 AM"
接著只要再執行迴圈,針對集合中的下一個檔案重複這個程序就行了。全部執行完成之後,C:\Scripts 資料夾中所有檔案 (隱藏檔不算) 的上次修改日期就完全一樣了。您瞧,夠簡單吧?現在誰還敢說您不能用 VBScript 來更改檔案的上次修改日期?
很好。
說到出錯 ...
讓我們回到最開始的地方吧,當 Scripting Guy 著手撰寫系統管理指令碼時,還不能存取所有的延伸檔案資訊,這些資訊是附加到 NTFS 檔案系統中的一個檔案。比方說,如果您用滑鼠右鍵按一下 .wav 檔,然後再按一下 [內容],就會看到類似 [圖 1] 所示的資訊。
[圖 1]** 檔案屬性摘要 **
您要如何使用指令碼來擷取像是位元速率或音訊範例大小的值呢?很抱歉,沒辦法。不過如果使用 Shell 物件的話就另當別論了 (如果您已經下載並安裝 Windows Desktop Search 3.0,那用這個軟體也成)。其實在這段時間當中,Shell 物件已經增加了擷取檔案所有延伸屬性資訊的功能,只是 Scripting Guy 不知怎的忽略了這件事。因此我們一直告訴大家:「指令檔不能用來判斷 .wav 檔的位元速率」。不過即使如此,只要看看 [圖 2] 所示的程式碼,就知道是可以的。
Figure 2 請使用一個指令碼來尋找 .wav 檔的位元速率
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = objFolder.ParseName("J0388563.wav")
For i = 0 to 33
strHeader = objFolder.GetDetailsOf(objFolder.Items, i)
strValue = objFolder.GetDetailsOf(objFolderItem, i)
If strValue <> "" Then
Wscript.Echo strHeader & vbTab & strValue
End If
Next
這段指令碼如何運作呢?這個指令碼一開始也和剛才看過的指令碼很類似,我們先建立 Shell.Application 物件的執行個體,再繫結到 C:\Scripts 資料夾,然後再用 ParseName 方法繫結到我們要討論的檔案 (此處是 J0388563.wav)。
不過接下來就有點詭異了。首先我們設定一個 For Next 迴圈,從 0 執行到 33;這是因為檔案支援 31 個不同的延伸屬性,其索引號碼就是 0 到 33 (請注意,有的號碼雖然是有效的索引號碼,卻不含任何屬性值,例如 27 和 28)。事實上,檔案支援 [圖 3] 所示的屬性。
Figure 3 檔案屬性
索引 | 屬性 |
0 | Name |
1 | Size |
2 | Type |
3 | Date Modified |
4 | Date Created |
5 | Date Accessed |
6 | Attributes |
7 | Status |
8 | Owner |
9 | Author |
10 | Title |
11 | Subject |
12 | Category |
13 | Pages |
14 | Comments |
15 | Copyright |
16 | Artist |
17 | Album Title |
18 | Year |
19 | Track Number |
20 | Genre |
21 | Duration |
22 | Bit Rate |
23 | Protected |
24 | Camera Model |
25 | Date Picture Taken |
26 | Dimensions |
29 | Episode Name |
30 | Program Description |
32 | Audio Sample Size |
33 | Audio Sample Rate |
那麼,在 For Next 迴圈裡面要做什麼呢?首先就是執行下面這行程式碼:
strHeader = _
objFolder.GetDetailsOf(objFolder.Items, i)
我們使用 GetDetailsOf 方法來擷取屬性 0 的名稱 (記住,我們的迴圈是從 0 執行到 33)。接著再把這個名稱儲存在名為 strHeader 的變數,然後再使用這行程式碼來擷取項目 0 的屬性值,把傳回的資訊儲存在名為 strValue 的變數:
strValue = _
objFolder.GetDetailsOf(objFolderItem, i)
看到它是怎麼運作了吧?項目 0 碰巧就是 Name 屬性。因此第一次跑完迴圈時,strHeader 會等於 Name。另外,第一個檔案的真正名稱 (亦即 Name 屬性的名稱) 是 J0388563.wav。因此第一次跑完迴圈時,strValue 會等於 J0388563.wav。經過我們解說之後,似乎就沒那麼複雜了吧?
不過您可能也知道,並非所有的檔案類型都支援所有的延伸屬性。比方說,.jpg 檔就不會有 Album Title 或 Audio Sample Rate)。因此在下面這行程式碼中,我們要檢查傳回的值是否為空字串:
If strValue <> "" Then
如果該值是空字串,只要跳回迴圈最上層,以下一個延伸屬性重複這個程序就行了。如果不是空字串,就傳回屬性名稱和值,如下所示:
Wscript.Echo strHeader & vbTab & strValue
結果呢?就變成這樣:
Name j0388563.wav
Size 169 KB
Type Wave Sound
Date Modified 1/19/2004 8:56 AM
Date Created 3/6/2006 2:02 PM
Date Accessed 12/3/2007 10:41 AM
Attributes A
Status Online
Owner FABRIKAM\kenmyer
Bit Rate 90kbps
Audio Sample Size 4 bit
Audio Sample Rate 11 kHz
很好。
雖然時間快不夠用了 (空間也是),不過如果您還想知道有哪些更簡單的方法可以傳回最重要的屬性值,不妨改試 [圖 4] 的這個指令碼。
Figure 4 傳回屬性值的簡易方法
Const colInfoTip = -1
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
objFolder.ParseName("01. Out On The Weekend (Album Version).wma)")
Wscript.Echo objFolder.GetDetailsOf(objFolderItem, colInfoTip)
這段指令碼在做什麼呢?如果您是在 Windows 檔案總管中,而且將滑鼠指標移到某個檔案上,這時候就會出現工具提示,提供一些該檔案的相關資訊,如 [圖 5] 所示。那麼如何使用指令碼取得這項工具提示資訊呢?沒錯,就用我們剛剛顯示給您看的指令碼:
[圖 5]** 工具提示所顯示的屬性 **
Artist: Neil Young
Album Title: Harvest
Year: 1972
Track Number: 1
Duration: 0:04:35
Type: Windows Media Audio file
Bit Rate: 256kbps
Protected: Yes
Size: 5.31 MB
本期專欄已接近尾聲。最後讓我再提一下麻雀吧。追補小麻雀的人,很有可能是因為下面這個理由才這麼做:這世界上有些地方把麻雀進入屋內視為不祥之兆。為什麼呢?因為那表示屋裡會死人 (有些地方只有麻雀停在鋼琴上才算。不過那不是迷信,只是怪談)。下面才是真正有意思的部分:這世界上還有一些地方,把麻雀飛到屋內視為屋裡有人要結婚的吉兆。真搞不懂哪邊才是對的。
Dr. Scripto 的指令碼謎題 (Scripting Perplexer)
這個每月一次的挑戰不僅測試您的解謎功力,更要測試您的指令碼技巧。
March 2008:Cryptic Script
本月份的謎題是密碼,意思是說每一個字母都各以不同的字母替代表示 (但所有的符號和數字都原封不動)。同一個字母所代表的一定是同一個替代的字母。比方說,所有出現的 a 皆以 b 替代表示,而所有出現的 b 皆以 z 替代表示,依此類推。以下是一個簡單的範例:
tdsjqu dfoufs
解碼後如下所示:
script center
這個案例的解碼動作,是把每一個字母換成它的前一個字母,因此 t 換成 s,d 就換成 c,依此類推。
下面這個謎題更沒有規則可循了。解碼之後的指令碼,會開啟一個 Microsoft Excel® 實例、增加新的試算表、把四個值指派給試算表中的第一欄,然後以遞增順序排列該欄的順序。
祝您玩得愉快。
謎題
kqyjs edfjkzyrlyg = 1
kqyjs edyq = 2
kqyjs edjqtstqmj = 2
jzs quwzekzd = ktzfszquwzks("zekzd.faadlkfslqy")
quwzekzd.iljludz = stcz
jzs quwmqtxuqqx = quwzekzd.mqtxuqqxj.frr
jzs quwmqtxjozzs = quwmqtxuqqx.mqtxjozzsj(1)
quwmqtxjozzs.kzddj(1, 1) = "kfs"
quwmqtxjozzs.kzddj(1, 2) = "rqg"
quwmqtxjozzs.kzddj(1, 3) = "ufs"
quwmqtxjozzs.kzddj(1, 4) = "faz"
jzs quwtfygz = quwzekzd.fkslizkzdd.zysltztqm
quwtfygz.jqts quwtfygz, edfjkzyrlyg, , , , , , edyq, , , edjqtstqmj
ANSWER:
Dr. Scripto 的指令碼謎題 (Scripting Perplexer)
答案是:Cryptic Script, March 2008
下面是解碼關鍵:
Actual Letter a b c d e f g h i j k
Coded Letter f u k r z b g o l w x
Actual Letter l m n o p q r s t u v w x y z
Coded Letter d n y q a h t j s c i m e v p
下面是解碼後的真正指令碼:
Const xlAscending = 1
Const xlNo = 2
Const xlSortRows = 2
Set objExcel = CreateObject("Excel.Application")
objExcel.Visible = True
Set objWorkbook = objExcel.Workbooks.Add
Set objWorksheet = objWorkbook.Worksheets(1)
objWorksheet.Cells(1, 1) = "Cat"
objWorksheet.Cells(1, 2) = "Dog"
objWorksheet.Cells(1, 3) = "Bat"
objWorksheet.Cells(1, 4) = "Ape"
Set objRange = objExcel.ActiveCell.EntireRow
objRange.Sort objRange, xlAscending, , , , , , xlNo, , , xlSortRows
The Microsoft Scripting GuysScripting Guy 為 Microsoft 做事,也就是受雇於 Microsoft。他們在不玩、不教或不看棒球 (以及其他各種活動) 的時候,就負責管理 TechNet 指令碼中心。請造訪他們的網站:www.scriptingguys.com。
© 2008 Microsoft Corporation and CMP Media, LLC. 保留所有權利;未經允許,嚴禁部分或全部複製.