Scripting Guy 為您解答問題

Hey, Scripting Guy!

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

重要資源

如何讓使用者使用 SendTo 命令來變更副檔名?

Hey, Scripting Guy! Question

嗨,Scripting Guy!這是我想做的事:我想在 SendTo 資料夾放入可讓使用者輕鬆變更檔案副檔名的指令碼(通常,我們的使用者會隱藏副檔名,而這樣會讓他們在需要變更副檔名時遭遇困難)。不過,我還是不知道該怎麼做。可以幫助我嗎?


        -- JvdB

Hey, Scripting Guy! Answer

嘿,JvdB:我們希望每個人都能夠享受並參與 2008 冬季指令碼比賽。請記住,您還來得及加入:項目 1 和 2 的截止日期是明天 (2008 年 2 月 20 日,你還是有很充裕的時間),而項目 9 和 10 要到 2008 年 3 月 3 日才截止。換句話說,您仍然來得及參與這個充滿樂趣的比賽,並贏得我們為 2008 比賽所準備的大獎。就像大家說的,所有人都會玩得很盡興。

那麼,我們 Scripting Guy 也覺得很好玩嗎?嗯,目前還不錯。不過,我們在開幕日 (2 月 15 日) 凌晨可不這麼認為。為了對非「太平洋標準時間」時區內的參賽者公平一點,我們決定在午夜 (Redmond 時間) 發佈所有項目指示以及相關的網頁(我們原先計劃在 Redmond 時間上午 8:00 發佈所有東西)。所以請記住,撰寫此專欄的 Scripting Guy 在 15 日午夜的那一秒鐘就開始發佈網頁了。如果計算一下當地語系的項目指示總頁數,您會發現大概有 200 頁。

如您所見,200 頁都已全數發佈且沒有任何錯誤,只除了以下 2 個頁面:「初級」和「進階級」的首頁 (我們非常需要這兩個頁面)。這讓我們在接下來的兩個半小時中只感受到挫折和沮喪,撰寫此專欄的 Scripting Guy 一直嘗試要讓這兩個頁面出現在網站中。無數的失敗後,他接著開始找尋其他的解決方法,而這個方法更加重了工作量,因為網頁必須花費 15 到 20 分鐘才能顯示 (一般只要 1 到 2 分鐘即可)。他最後在凌晨 2:30 左右上床睡覺,而他才剛睡著就又被 6:15 的鬧鐘吵醒。

所以囉!所有人都會玩得很盡興。

附帶一提,主要比賽的第 1 天發生了差點破壞 Scripting Guy 完美記錄的重大災難:

  • 指令碼第 1 週的開始日,大約有 2,000 以上的人報名參與網路廣播。然而因為某些因素,網路廣播的工作人員預估實際會出現的人數應該只有 500 人。而隨著人員的踴躍登入,伺服器便開始罷工,最後我們竟然讓整個 Microsoft.com 當機了。

    從某方面來說,這仍然是 Scripting Guy 到目前為止感到最驕傲的成就。

  • Windows PowerShell 週的開始日,大約有 1,200 人嘗試登入網路廣播。由於技術上的小問題,最後只有大約 300 人可以進入系統,其中大約只有一半的人可以真正聽到網路廣播(不過,既然是 Scripting Guy Jean Ross 在進行簡報,您也可以換個方向想:那些聽不到網路廣播的人,實際上是比較幸運的)。

  • 為了要趕上 TechEd 2007 的開始日,Scripting Guy Greg Stemp 本來打算搭乘晚班飛機從西雅圖飛到奧蘭多。只是當他到驗票櫃檯報到時,卻發現他的班機取消了。結果他錯過了第 1 天的所有議程,直到第 1 天的傍晚才現身,這剛好也是所有事情結束的時刻(編輯附註:不過,考量到 Scripting Guy Jean Ross 已想辦法趕到奧蘭多,並處理好所有的事。您也可以換個方向想:那天每個人的運氣都非常好)。

換句話說,在第 1 天發生災難應該算是無可避免的事吧,至少對 Scripting Guy 而言是這樣。

不過在這個不太順利的開始之後,事情開始好轉(這是件好事;我們根本不敢去想如果更糟的事發生會怎樣)。就這樣,到目前為止我們很滿意比賽進行的情況。在星期日晚上,登入的人數幾乎達到整個 2007 指令碼參賽人數的一半。

不過,Scripting Guy 畢竟還是貪心的,我們希望慫恿更多人一起參與。我們提供了樂趣和教學,但您卻不買帳。我們提供了大獎,但您卻無動於哀。好吧!現在該是下重藥的時刻了:我們將提供您一組指令碼,如果您將這組指令碼儲存在 SendTo 資料夾後,可讓使用者更輕鬆快速地變更檔案的副檔名,您覺得這樣如何?

我們就知道您一定會同意的:


strFilePath = Wscript.Arguments(0)
strFilePath = Replace(strFilePath, "\", "\\")

strNewExtension = InputBox("Please enter the new file extension (without the period):")

strComputer = "."

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

Set colFiles = objWMIService.ExecQuery _

("Select * from CIM_DataFile where Name = '" & strFilePath & "'")

For Each objFile In colFiles
strNewName = objFile.Drive & objFile.Path & objFile.FileName & "."&strNewExtension
errResult = objFile.Rename(strNewName)
Next

您可以看到,指令碼開始時使用這一行程式碼來擷取第一個命令列引數:

strFilePath = Wscript.Arguments(0)

大部分的人應該都知道,當您將檔案或資料夾拖曳到 .VBS 檔案時,檔案或資料夾的完整路徑會自動提供給指令碼做為命令列引數(也就是給 .VBS 檔案)。因此,SendTo 命令也是這樣。若您使用 SendTo 命令來將檔案或資料夾傳送給指令碼,則會自動提供檔案或資料夾的完整路徑做為命令列引數。換句話說,假設您在名稱為 C:\Scripts\Test.txt 的檔案按一下滑鼠右鍵,然後選取 SendTo。若您將該檔案「傳送」到 SendTo 資料夾,您認為該指令碼引數集合的第一個項目會是什麼呢?您答對了:C:\Scripts\Test.txt。

這實際上還蠻合理的。但程式碼的第二行可能就不是這樣了:

strFilePath = Replace(strFilePath, "\", "\\")

這裡發生了什麼事呢?請注意,這個檔案路徑將要用在 WMI 查詢中。如您所知,檔案路徑看起來如下:

C:\Scripts\Test.txt

所以,路徑哪裡有問題嗎?事實上,有的:路徑包含了兩個 \ 字元。這為什麼會是問題呢?\ 字元在 WMI 中剛好是保留字元。若您要在 WMI 查詢中使用保留字元,指令碼一定會出錯。這行程式碼看起來好像沒錯,但其實不然:

Set colFiles = objWMIService.ExecQuery _
("Select * from CIM_DataFile where Name = 'C:\Scripts\Test.txt'")

這表示您不能在 WMI 查詢中使用檔案路徑嗎?不是這樣的,它只表示每當您要在 WMI 查詢中使用保留字元時,都要將該字元「逸出」。因為逸出字元剛好就是 \,這表示您要將路徑中的每個 \ 鍵入兩次,如下:

C:\\Scripts\\Test.txt

第 2 行就是這樣做:它利用 Replace 函數,將每個 \ 字元取代為:\\。 這樣就可以得到我們要在 WMI 查詢中使用的路徑了。

接下來,我們提示使用者輸入新的副檔名 (不用鍵入句點 (.)),並將該值儲存在名稱為 strNewExtension 的變數中:

strNewExtension = InputBox("Please enter the new file extension (without the period):")

連線至本機電腦的 WMI 服務之後,我們終於可以發出 WMI 查詢了:


Set colFiles = objWMIService.ExecQuery _
("Select * from CIM_DataFile where Name = '" & strFilePath & "'")

我們在這裡會直接繫結到使用者按一下的檔案。若要這麼做,我們必須選取所有在 CIM_Datafile 類別中,Name (路徑) 等於變數 strFilePath 值 (此變數包含我們修改的路徑 (C:\\Scripts\\Test.txt)) 的例項。因為檔案路徑在電腦上必須是唯一的,所以這會將我們繫結到使用者按一下的檔案。

注意:我們必須提醒您,此指令碼只能在本機電腦運作。簡而言之,若您嘗試按一下 UNC 路徑,則 WMI 將假設檔案位於本機電腦。理論上,您可以針對儲存於遠端電腦上的檔案執行這個命令,但是您必須以某種方式告訴指令碼遠端電腦的名稱。或者您也可以將遠端電腦的資料夾對應為磁碟機。這樣應該就可以運作了,因為在此情況下,檔案路徑將會包括磁碟機代號而非 UNC 路徑。

即使我們的查詢只會傳回集合中的一個項目,但集合就是集合。這表示我們需要設定 For Each 迴圈以在集合中的所有項目中循環。在該迴圈中,我們要做的第一件事如下:

strNewName = objFile.Drive & objFile.Path & objFile.FileName & "."& strNewExtension

我們做這個動作的目的,就是使用選取的原始檔案屬性和新的副檔名來建構新的檔案路徑(無論您的意圖和目的為何,您都可以藉由提供新檔案路徑來重新命名檔案)。您可能已經猜到,Drive 屬性會傳回儲存檔案所在的磁碟機。例如:

c:

Path 屬性是有點怪的小屬性,它會傳回「不」包含磁碟機代號和檔案名稱的資料夾資訊(別問為什麼,這就是它作業的方式)。在我們的例子中,完整檔案路徑為 C:\Scripts\Test.txt,這表示該 Path 屬性會等於 \Scripts\。與 Drive 屬性合併之後,我們就可以得到路徑,到目前為止的路徑如下:

C:\Scripts\

接下來是 FileName 屬性,它會傳回「不」含副檔名的檔案名稱 (當然也不包含名稱和副檔名之間的點)。因此:

C:\Scripts\Test

最後但也很重要的一點是,我們要加上點和副檔名 (儲存於變數 strNewExtension)。我們假設新的副檔名為 log,這樣我們的全新檔案路徑就會如下:

C:\Scripts\Test.log

請注意,這邊我們「不需要」將路徑中的所有 \ 字元逸出。只有在查詢時,才需要這麼做。

最後一步要做的就是叫用 Rename 方法來將重新命名檔案:

errResult = objFile.Rename(strNewName)

以上就是所有需要執行的動作。最棒的是,當使用者選擇在 Windows 檔案總管中隱藏副檔名時,它仍然可以正確運作。那是因為 WMI 不需要「看到」副檔名,它永遠知道其副檔名為何。

WMI 知道所有事情。

在這邊要記住兩件事。第一,此指令碼必須儲存在 SendTo 資料夾,其原因非常明顯:若不這樣做,則無法使用 SendTo 命令來存取。第二,此指令碼只對檔案有效。您可以嘗試將資料夾傳送到該指令碼,但是不會有任何事情發生。那是因為資料夾不是 CIM_DataFile 類別的成員。資料夾是屬於 Win32_Directory 類別的成員。

JvdB,您就試著做做看吧!如果您還沒開始行動,我們建議您參觀一下 2008 冬季指令碼比賽。我們敢說到目前為止所有參賽者都玩得非常盡興,並享受各項挑戰。請記住,我們還有分「初級」和「進階級」兩組。而且您可以使用三種指令碼語言或其中一種指令碼語言:VBScript、Windows PowerShell 以及 Perl。換句話說,每個人都有機會可以參與!

對了,我們還有另一個建議:如果您以後將參加類似的大型比賽,請千萬記住不管您做什麼事,「別」邀請 Scripting Guy 參加第 1 天的比賽,相信我吧!

顯示: