本週 Windows PowerShell 秘訣

Office Space

這是使用 Windows PowerShell 的快速秘訣。只要有新的秘訣,我們每週就會在這裡發佈。如果您想要分享任何秘訣,或有任何疑問,請讓我們知道

您可以在本週 Windows PowerShell 秘訣封存找到更多秘訣。

判斷資料夾大小

一般來說,Windows PowerShell 可讓您的系統管理生活更加輕鬆。而且除了一件事之外都很好:當某些事「真的」出錯時,人們會假設「它們」就是錯的。

例如,過去數週我們收到幾封電子郵件:「嗨,Scripting Guy!您可以想像我在處理最簡單的 PowerShell 指令碼時,正經歷最艱困的時期。我只是要判斷資料夾的大小,但就是無法完成;不論我如何嘗試,資料夾大小就是空白。我到底哪邊做錯了?」

事實證明,您並沒有做錯什麼事。(好吧!除了向 Scripting Guy 求助之外。)您應該是使用類似下面的命令,該命令繫結至 C:\Scripts 資料夾,然後傳回一些基本資訊,包括資料夾大小 (Length):

Get-Item C:\Scripts

而下面是您可能獲得的資訊:

Directory: Microsoft.PowerShell.Core\FileSystem::C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----          4/1/2008  12:39 PM            scripts

您就會得到:資料夾大小,不過什麼都沒提到。我們一定做錯了什麼!

聽著!放輕鬆,不論您做什麼,請不要驚慌。就像我們說的,這不是您的錯;實際上是 PowerShell 的錯。基於某些原因,PowerShell 無法直接傳回資料夾大小。(但它不是唯一無法直接傳回資料夾大小的命令:WMI 也無法傳回資料夾大小。)

這表示當您要判斷資料夾大小時,好運就用完了嗎?不論您信或不信,答案是否定的,您「並非」運氣不佳。也許您無法直接判斷資料夾大小,但是您可以使用類似下面的程式碼來「間接」判斷資料夾大小:

Get-ChildItem C:\Scripts | Measure-Object -property length -sum

就算這像是有點拐彎抹角來判斷資料夾大小的方法,但至少它「確實」可判斷資料夾大小。我們使用這個命令所做的,是使用 Get-ChildItem cmdlet 傳回 C:\Scripts 資料夾中,所有找到項目的集合。然後我們將該集合傳送到 Measure-Object cmdlet,要求 Measure-Object 做兩件事:

  1. 察看集合中每個項目的 Length 屬性。

  2. 回報每個項目長度的總合 sum

我們將得到什麼結果呢?我們得到的輸出會類似以下所示:

Count    : 58
Average  :
Sum      : 1244611
Maximum  :
Minimum  :
Property : length

因此 C:\Scripts 資料夾的大小為何?依據 Measure-Object,它的大小是 1,244,611 個位元組。(Sum 屬性會告訴您答案。)

這樣也不賴;如同前面所說,我們「確實」提供正在尋找的答案。另一方面,在粗略檢視時,仍然很難知道答案究竟「為何」;因為輸出有點雜亂無章,以及答案是以位元組回報而不是以 MB 回報的事實。將它放在心中,我們試著改為執行下面增強功能的程式碼區塊:

$colItems = (Get-ChildItem C:\Scripts | Measure-Object -property length -sum)
"{0:N2}" -f ($colItems.sum / 1MB) + " MB"

好了,這二行看起來「真的」有點拐彎抹角,不是嗎?或許我們最好說明一下這是在做什麼。在第 1 行中,我們做的就是:使用 Get-ChildItem 傳回 C:\Scripts 資料夾中所有項目的集合;然後要求 Measure-Object 計算所有項目長度的總和。不過,這次我們沒有立即回報答案;而是取得該輸出後將它存放在稱為 $colItems 的變數中。

然後在第 2 行中,我們對輸出套用一些格式。開始時使用此結構來取得 Sum 屬性的值,將它轉換成 MB:

($colItems.sum / 1MB)

您說您不瞭解那一行程式碼如何將位元組轉換成 MB?那麼您應該看一下位元組轉換中的秘訣。

一旦經過這個轉換,就可以使用這一小段程式碼,以小數點二位來顯示值。

"{0:N2}" -f

不錯,事實上我們擁有說明其所有運作方式的格式化數字中的秘訣。感謝您的詢問!

最後,我們將 MB 附加到輸出的結尾。結果如何呢?就是這樣:

1.19 MB

這個結果好多了。

如您所見,這些都可順利運作。只有一件事情例外;如果我們開啟 [Windows 檔案總管],檢查 C:\Scripts 資料夾大小,[Windows 檔案總管] 會認定其大小實際上應該是 3.60 MB:

Ff730945.foldersize(zh-tw,TechNet.10).jpg

哎呀!我們「又」做錯什麼了嗎?

可能哦!但並非使用 Windows PowerShell 計算資料夾大小時發生的錯誤。當您在 [Windows 檔案總管] 察看資料夾大小時,[Windows 檔案總管] 會告訴您資料夾的大小「總合」;其中不僅包含父資料夾 (C:\Scripts) 中找到的檔案,還包括該資料夾的子資料夾中找到的所有檔案。如果我們加總父資料夾中的檔案 (也就是指令碼所做的事),則資料夾大小就是 1.19 MB。如果加入 C:\Scripts 所有子資料夾中找到所有檔案的大小,則資料夾大小就是 3.60 MB。

當然,剛剛所示的指令碼「並未」加總 C:\Scripts 子資料夾中找到所有檔案的大小。問題是,如果我們想要資料夾的絕對總大小時該如何做呢?若果是這樣,我們就必須修改一點點指令碼。

什麼?多少才是「一點點」呢?好吧!事實證明真的只是一點點;我們只要將 –recurse 參數加入 Get-ChildItem 中;它會告訴 Get-ChildItem 傳回 C:\Scripts 中所有檔案「加上」 C:\Scripts 子資料夾中找到的所有檔案。下面是修改後的指令碼:

$colItems = (Get-ChildItem C:\Scripts -recurse | Measure-Object -property length -sum)
"{0:N2}" -f ($colItems.sum / 1MB) + " MB"

下面是執行這個指令碼之後傳回的結果:

3.60 MB

酷吧?

我們來嘗試更花俏的做法。假設您要完全分析 C:\Scripts 及其每個子資料夾的內容;讓報告顯示如下:

C:\Scripts -- 1.19 MB
C:\Scripts\BCD -- 0.02 MB
C:\Scripts\Forms -- 0.08 MB
C:\Scripts\Games -- 0.04 MB
C:\Scripts\Games\New Folder -- 0.02 MB
C:\Scripts\New Folder -- 2.23 MB
C:\Scripts\Test Folder -- 0.02 MB

要如何產生這樣的報告呢?以下是其中一種方式:

$startFolder = "C:\Scripts"

$colItems = (Get-ChildItem $startFolder | Measure-Object -property length -sum)
"$startFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB"

$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)
foreach ($i in $colItems)
    {
$subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
    }

以上實際執行了兩個步驟的程序。開始時使用此程式碼區塊來判斷資料夾 C:\Scripts 的大小,不包含任何子資料夾。

$startFolder = "C:\Scripts"

$colItems = (Get-ChildItem $startFolder | Measure-Object -property length -sum)
"$startFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB"

這個部分我們已經討論過了。接著,我們使用下面這行程式碼傳回 C:\Scripts 中找到的所有子資料夾 (以及那些子資料夾的所有子資料夾) 的集合,全部按照資料夾路徑排序:

$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)

如您所見,我們再次使用 Get-ChildItem (以及 –recurse 參數),傳回 C:\Scripts 中找到的項目集合。不過,一旦取得這個集合,就會立即將它傳送到 Where-Object cmdlet;然後要求 Where-Object 限制集合只包括 PSIsContainer 屬性為真 ($True) 的項目。這樣做的目的為何呢?答對了:如果 PSIsContainer 屬性為真,表示我們處理的是資料夾而不是檔案。產生的淨效果是我們已經從集合中取出所有檔案,只剩下 C:\Scripts 中的子資料夾。

然後為了讓事情更完美無缺,我們將此篩選過的集合傳送給 Sort-Object,將它們按照資料夾路徑排序。

剩下的部分很簡單。一開始我們設定 foreach 迴圈,針對集合的所有子資料夾執行:

foreach ($i in $colItems)

在 foreach 迴圈內,我們使用這兩行程式碼來連接每個子資料夾 (有些事可以參數 FullName 屬性來執行,這個屬性與資料夾路徑相同),然後計算其大小:

$subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"

這樣就完成啦!

除此之外,還「有」另一個方法可以判斷資料夾的總大小。看起來似乎有點受騙上當,而且它不是「純粹」PowerShell 的解決方案。下列指令碼將傳回資料夾 C:\Scripts (包括其所有子資料夾) 的大小:

$objFSO = New-Object -com  Scripting.FileSystemObject
"{0:N2}" -f (($objFSO.GetFolder("C:\Scripts").Size) / 1MB) + " MB"

我們在此所做的是使用老朋友 Scripting.FileSystemObject 來判斷資料夾大小。在第 1 行中,我們使用New-Object cmdlet (必須加上 –com 參數,因為我們正在建立 COM 物件) 來建立 FileSystemObject 的新例項。在第 2 行中,我們使用此語法 (以及 GetFolder 方法) 來繫結到資料夾 C:\Scripts,而且只傳回 Size 屬性的值:

($objFSO.GetFolder("C:\Scripts").Size)

然後讓它更花俏一點,將大小轉換成 MB,設定輸出格式,使它只顯示到小數點第二位,然後將 MB 附加到輸出的結尾。最後的結果看起來應該如下:

3.60 MB

無可否認地,FileSystemObject 是老舊,且據說是過時的技術,而它根本就不是世界上最迷人的技術。不過,它「確實」完成了任務。

考慮所有事情之後,它就像 Scripting Guy 一樣糟糕。

對了,有一件事例外:如同我們所說,FileSystemObject 「確實」完成任務。

我們下週再見。

顯示: