天天看點

PowerShell筆記 - 15.檔案系統

15.檔案系統

<code>本系列是一個重新學習PowerShell的筆記,内容引用自</code>PowerShell中文部落格

在PowerShell控制台中,檔案系統有很特别的重要性。一個明顯的原因是管理者需要執行許多涉及檔案系統的任務。另一個原因是檔案系統是一個層次結構資訊模型。在接下來的章節中,你還會看到PowerShell在此基礎上控制其它層次資訊系統。你可以非常容易的将PowerShell中學到的驅動器,目錄和檔案的知識點應用到其它地方,其中就包括系統資料庫或者微軟的Exchange。

在下面表格中列出的PowerShell 的指令中其全名可能很少有人用到。大家更傾向于使用它們的别名,這些别名來自Windows和Unix系統。

可以讓初學者可以非常快速地找到合适的指令。

指令

别名

描述

cp, cpi

複制檔案或者目錄

Copy-Item

Dir, ls, gci

列出目錄的内容

Get-Childitem

type, cat, gc

基于文本行來讀取内容

Get-Content

gi

擷取指定的檔案或者目錄

Get-Item

gp

擷取檔案或目錄的屬性

Get-ItemProperty

ii

使用對應的預設windows程式運作檔案或者目錄

Invoke-Item

連接配接兩個路徑為一個路徑

Join-Path

mi, mv, move

移動檔案或者目錄

Move-Item

ni

建立新檔案或者目錄

New-Item

ri, rm, rmdir,del, erase, rd

删除空目錄或者檔案

Remove-Item

rni, ren

重命名檔案或者路徑

Rename-Item

rvpa

處理相對路徑或者包含通配符的路徑

Resolve-Path

sp

設定檔案或路徑的屬性

Set-ItemProperty

Cd,chdir, sl

更改目前目錄的位置

Set-Location

提取路徑的特定部分,例如父目錄,驅動器,檔案名

Split-Path

測試指定的路徑是否存在

Test-Path

使用Get-ChildItem列出目錄的内容。預定義的别名為Dir和ls,Get-ChildItem執行了一些很重要的任務:

顯示目錄内容

遞歸地搜尋檔案系統查找确定的檔案

擷取檔案和目錄的對象

把檔案傳遞給其它指令,函數或者腳本

注意:因為Windows管理者一般在實踐中,使用Get-ChildItem的别名Dir,是以接下來的例子都會使用Dir。另外ls(來自UNIX家族)也可以代替下面例子中的Dir或者Get-ChildItem。

一般情況下,你可能隻想知道在一個确定的目錄中有什麼檔案,如果你不指定其它參數。Dir會列出目前目錄的内容。如果你在Dir後跟了一個目錄,它的内容也會被列出來,如果你使用了-recurse參數,Dir會列出所有子目錄的内容。當然,也允許使用通配符。

例如,你想列出目前目錄下的所有PowerShell腳本,輸入下面的指令:

Dir甚至能支援數組,能讓你一次性列出不同驅動器下的内容。下面的指令會同時列出PowerShell根目錄下的PowerShell腳本和Windows根目錄下的所有日志檔案。

如果你隻對一個目錄下的項目名稱感興趣,使用-Name參數,Dir就不會擷取對象(Files和directories),隻會以純文字的形式傳回它們的名稱。

注意:一些字元在PowerShell中有特殊的意義,比如方括号。方括号用來通路數組元素的。這也就是為什麼使用檔案的名稱會引起歧義。當你使用<code>-literalPath</code>參數來指定檔案的路徑時,所有的特殊字元被視為路徑片段,PowerShell解釋器也不會處理。

Dir 預設的參數為<code>-Path</code>。假如你目前檔案夾下有個檔案名為“<code>.\a[0].txt</code>“,因為方括号是PowerShell中的特殊字元,會解釋器被解析。為了能正确擷取到”<code>.\a[0].txt</code>”的檔案資訊,此時可以使用<code>-LiteralPath</code>參數,它會把你傳進來的值當作純文字。

當你想搜尋整個子目錄時,可以使用-recurce參數。但是注意,下面例子在PowerShell2.0版本中執行時會失敗。

你需要了解一點-recurse如何工作的細節來了解為什麼會發生上面的情況。Dir總是會擷取目錄中的内容為檔案對象或者目錄對象。如果你設定了-recurse開關,Dir會遞歸周遊目錄對象。但是你在上面的例子中使用的通配符隻擷取擴充名為ps1的檔案,沒有目錄,是以-recurse會跳過。這個概念剛開始使用時可能有點費解,但是下面的使用通配符例子能夠遞歸周遊子目錄,正好解釋了這點。

在這裡,Dir擷取了根目錄下所有以字母“D”打頭的項目。遞歸開關起了作用,那是因為這些項目中就包含了目錄。

現在回到剛開始問題,怎樣遞歸列出同類型的所有檔案,比如所有PowerShell scripts。答案是使用Dir完全列出所有目錄内容,同時指定一個過濾條件。Dir現在可以過濾出你想要列出的檔案了。

除了<code>-filter</code>,還有一個參數乍一看和<code>-filter</code>使用起來很像:<code>-include</code>

你會看到這一戲劇性的變化,<code>-filter</code>的執行效率明顯高于<code>-include</code>:

其原因在于<code>-include</code>支援正規表達式,從内部實作上就更加複雜,而<code>-filter</code>隻支援簡單的模式比對。這也就是為什麼你可以使用<code>-include</code>進行更加複雜的過濾。比如下面的例子,搜尋所有第一個字元為A-F的腳本檔案,顯然已經超出了<code>-filter</code>的能力範圍。

與<code>-include</code>相反的是<code>-exclude</code>。在你想排除特定檔案時,可以使用<code>-exclude</code>。不像<code>-filter</code>,<code>-include</code>和<code>-exclude</code>還支援數組,能讓你擷取目錄下所選類型的檔案。

做到一點即可:不要混淆了<code>-filter</code>和 <code>-include</code>。選擇這兩個參數中的其中一個:具體為當你的過濾條件沒有正規表達式時,使用<code>-filter</code>,可以顯著提高效率。

注意:你不能使用filters在Dir中,列出确定大小的檔案清單。因為Dir的限制條件隻在檔案和目錄的名稱級别。如果你想使用其它标準來過濾檔案,可以嘗試第五章中講到的Where-Object。

下面的例子會擷取你家目錄下比較大的檔案,指定檔案至少要100MB大小。

如果你想知道Dir傳回了多少個檔案項,Dir會将結果儲存為一個數組,你可以通過數組的的Count屬性來讀取。下面的指令會告訴你你的家目錄下有多少檔案(這個操作可能會比較耗時)

你可以使用Dir直接擷取一個單獨的檔案,因為Dir會傳回一個目錄下所有的檔案和目錄對象。下面的例子會得到這個檔案的FileInfo資訊:

你可以通路單個檔案的屬性,如果它們的屬性支援更改,也可以更改。

<code>Get-Item</code>是通路單個檔案的另外一個途徑, 下面的3條指令都會傳回同樣的結果:你指定的檔案的檔案對象。

但是在通路目錄而不是檔案時,Get-Childitem 和 Get-Item表現迥異。

因為Dir的結果中傳回的是獨立的檔案或目錄對象,Dir可以将這些對象直接傳遞給其它指令或者你自己定義的函數與腳本。這也使得Dir成為了一個非常重要的的選擇指令。使用它你可以非常友善地在一個驅動盤下甚至多個驅動盤下遞歸查找特定類型的所有檔案。

要做到這點,在管道中使用<code>Where-Object</code>來處理Dir傳回的結果,然後再使用<code>ForEach-Object</code>,或者你自定義的管道過濾。

你還可以将多個Dir 指令執行的結果結合起來。在下面的例子中,兩個分開的Dir指令,産生兩個分開的檔案清單。然後PowerShell将它們結合起來發送給管道進行深度處理。這個例子擷取Windows目錄和安裝程式目錄下的所有的dll檔案,然後傳回這些dll檔案的名稱,版本,和描述:

因為Dir擷取的檔案和目錄是一樣的,有時限制結果中隻包含檔案或者隻包含目錄很重要。有很多途徑可以做到這點。你可以驗證傳回對象的屬性,PowerShell PSIsContainer屬性,或者對象的類型。

前面的例子(識别對象類型)是目前速度最快的,而後面的(文本比較)比較複雜和低效。

<code>Where-Object</code>也可以根據其它屬性來過濾。

比如下面的例子通過管道過濾2007年5月12日後更改過的檔案:

也可以使用相對時間擷取2周以内更改過的檔案:

除非你通過前面介紹的方式更改了PowerShell控制台的提示資訊,否則你工作的目前目錄會在控制台的指令行開頭顯示。你也可以使用<code>Get-Location</code>或别名<code>pwd</code>指令擷取目前工作的目錄。

如果你想導航到檔案系統的另外一個位置,可以使用Set-Location或者它的别名Cd:

路徑的指定可以是相對路徑,也可以是絕對路徑。在上面的最後一個例子中,兼而有之這兩種路徑。相對路徑依賴你目前的路徑,比如.\test.txt檔案總是指定的是目前目錄中的test.txt檔案,而..\test.txt指定的是父目錄的test.txt檔案。相對路徑通常比較實用,比如你想使用的腳本庫位于目前工作目錄,你就可以在不引入其它目錄的情況下,直接工作。而絕對路徑通常具有唯一性,并且獨立于你目前的目錄。

當你使用相對路徑時,PowerShell必須将這些相對轉換成絕對路徑。在你使用相對路徑執行一個檔案或者一條指令時,該轉換會自動發生。你也可以自己使用<code>Resolve-Path</code>指令來處理。

然而,<code>Resolve-Path</code>指令隻有在檔案确實存在時,才會有效。如果你的目前檔案夾中沒有一個名為<code>a.png</code>的檔案時,<code>Resolve-Path</code>轉換報錯。

如果你指定的路徑中包含了通配符,<code>Resolve-Path</code>還可以傳回多個結果。下面的指令執行後,會擷取PowerShell目錄下面的所有的ps1檔案的名稱。

像Dir一樣,<code>Resolve-Path</code>可以在下行函數中扮演選擇過濾器的的角色。下面的例子會示範在記事本中打開一個檔案進行處理。指令調用記事本程式通過<code>Resolve-Path</code>打開這個檔案。

如果沒有符合标準的檔案,<code>Resolve-Path</code>會抛出一個異常,記錄在<code>$?</code>變量中,在錯誤發生時表達式<code>!$?</code>一直會統計,在<code>True</code>的情況下,代表可能沒找到檔案。

如果<code>Resolve-Path</code>找到了多個檔案會把它儲存在一個數組中,這樣的化會有很多不期望的檔案被打開。函數使用了之前講到的PowerShell 内部的函數<code>PromptForChoice()</code>,來請求使用者做出選擇。

目前的目錄可以使用<code>Push-Location</code>指令儲存到目錄堆棧的頂部,每一個<code>Push-Location</code>都可以将新目錄添加到堆棧的頂部。使用<code>Pop-Location</code>可以傳回。

是以,如果你要運作一個任務,不得不離開目前目錄,可以在運作任務前将用<code>Push-Location</code>存儲目前路徑,然後運作結束後再使用<code>Pop-Location</code>傳回到目前目錄。<code>Cd $home</code>總是會傳回到你的<code>Home</code>目錄,<code>Push-Location</code> 和 <code>Pop-Location</code>支援堆棧參數。這使得你可以建立很多堆棧,比如一個任務,一個堆棧。<code>Push-Location -stack job1</code>會把目前目錄儲存到job1堆棧中,而不是标準堆棧中。當然在你想重新回到這個位置時,也需要在<code>Pop-Location</code>中指定這個參數<code>-stack job1</code>。

Windows使用了很多特殊的目錄,根據系統的安裝,可能稍有不同。一些非常重要的目錄的路徑同時也儲存在Windows環境變量中,這樣PowerShell 可以非常友善和清晰的通路它們。你也可以使用.NET framework中的Environment類去通路其它特殊目錄。

特殊目錄

示例

Application data

存儲在本地機器上的應用程式資料

$env:localappdata

User profile

使用者目錄

$env:userprofile

Data used incommon

應用程式公有資料目錄

$env:commonprogramfiles

Public directory

所有本地使用者的公有目錄

$env:public

Program directory

具體應用程式安裝的目錄

$env:programfiles

Roaming Profiles

漫遊使用者的應用程式資料

$env:appdata

Temporary files(private)

目前使用者的臨時目 錄

$env:tmp

Temporary files

公有臨時檔案目錄

$env:temp

Windows directory

Windows系統安裝的目錄

$env:windir

環境變量傳回的隻是其中一部分,還不是全部的特殊目錄。比如如果你想将某個檔案放到一個使用者的桌面,你需要的路徑在環境變量中是無法擷取的。但是你可以使用.NET的方法environment類下面的GetFolderPath()方法。下面會示範如何在桌面上建立一個快捷方式。

<code>GetFolderPath()</code>目錄的類型可以在枚舉值<code>SpecialFolder</code>中找到。你可以使用下面一行腳本檢視它的内容。

如果你想預覽所有<code>GetFolderPath()</code>支援的目錄内容,可以使用下面的例子:

路徑名稱由文本構成,能讓你随心所欲地構造他們。你也應當看到了上面例子中構造使用者桌面快捷方式的過程了:

一定要確定你的路徑中的反斜杠個數正确。這也就是為什麼前面的例子中在file.txt前面使用了一個反斜杠。還有一個更可靠的方式,就是使用指令 Join-Path方法,或者.NET中的Path靜态類。

<code>Get-ChildItem</code>和 <code>Get-Item</code> 指令可以擷取已經存在的檔案和目錄。你也可以建立自己的檔案和目錄,重命名它們,給它們填充内容,複制它們,移動它們,當然也可以删除它們。

建立一個新目錄最友善的方式是使用<code>MD</code>函數它是<code>mkdir</code>的别名,它内部調用的是<code>New-Item</code>指令,指定參數<code>–type</code>的值為<code>Directory</code>:

可能之前你已經使用過<code>New-Item</code>來建立過檔案,但是它們完全是空的:

檔案通常會在你儲存資料時,自動被建立。因為空檔案一般沒多大用處。此時重定向和<code>Out-File</code>,<code>Set-Content</code>這兩個指令可以幫助你:

事實證明在操作上重定向和<code>Out-File</code>非常的類似:當PowerShell轉換管道結果時,檔案的内容就像它在控制台上面輸出的一樣。<code>Set-Content</code>稍微有所不同。它在檔案中隻列出目錄中檔案的名稱清單,因為在你使用<code>Set-Content</code>時,PowerShell不會自動将對象轉換成文本輸入。相反,<code>Set-Content</code>會從對象中抽出一個标準屬性。上面的情況下,這個屬性就是Name了。

通常,你可以将任何文本寫入一個文本檔案。最後一行示範的是将一個日期對象寫入到檔案中。比如你手動使用<code>ConvertTo-HTML</code>将管道結果轉換後,<code>Out-File</code>和<code>Set-Content</code>會殊途同歸。

如果你想決定對象的那個屬性應當顯示在HTML頁面中,可以使用之前提到的<code>Select-Object</code> 在對象轉換成HTML前過濾屬性。

在重定向的過程中,控制台的編碼會自動指定特殊字元在文本中應當如何顯示。你也可以在使用Out-File指令時,使用-encoding參數來指定。

如果你想将結果導出為逗号分割符清單,可以使用<code>Export-CSV</code>代替<code>Out-File</code>。

你可以使用雙重定向和<code>Add-Content</code>向一個文本檔案中追加資訊。

這個結果讓小夥伴們驚呆了:雙箭頭重定向可以工作,但是文本中顯示的字元有間隔。重定向操作符通常使用的是控制台的字元集,如果你的文本中碰巧同時包含了ANSI和Unicode字元集,可能會引起意外的結果。相反,使用<code>Set-Content</code>,<code>Add-Content</code>和<code>Out-File</code>這幾條指令,而不使用重定向,可以有效地規避前面的風險。這三條指令都支援<code>-encoding</code>參數,你可以用它來選擇字元集。

你可能會驚訝,PowerShell允許你建立新的驅動器。并且不會限制你隻建立基于網絡的驅動器。你還可以使用驅動器作為你的檔案系統中重要目錄,甚至你自定義的檔案系統的一個快捷方式。

使用<code>New-PSDrive</code>指令來建立一個新的驅動器。可以像下面那樣建立一個網絡驅動器。

在工作目錄中建立一個快捷方式也非常友善。下面的指令行會建立一個名為<code>desktop:</code>和 <code>docs:</code>的驅動器,它可以代表你的”桌面“目錄和Windows目錄:“我的檔案”

然後你想更改目前目錄為桌面時,隻須輸入:

使用<code>Remove-PSDrive</code>來删除你建立的驅動器。如果該驅動器正在使用則不能删除。注意在使用<code>New-PSDrive</code>和<code>Remove-PSDrive</code>建立或删除驅動器時,指定的字母不能包含冒号,但是在使用驅動器工作時必須指定冒号。

使用<code>Get-Content</code>可以擷取文本檔案的内容:

如果你知道檔案的絕對路徑,還可以使用變量符号這個快捷方式讀取文本内容:

通常,這個符号不是很實用,因為在括号中不允許适用任何變量。而大多數情況下絕對路徑不會适用所有機器的作業系統。

<code>Get-Content</code>逐行讀取文本的内容,然後把文本的每一行傳遞給管道。是以,在你想讀取一個長檔案的前N行,應當适用<code>Select-Object</code>:

使用<code>Select-String</code>可以過濾出文本檔案中的資訊。下面的指令行會從檔案中過濾出包含 <code>third</code>短語的行。

在PowerShell中處理逗号分隔的清單檔案中的資訊時你須要使用<code>Import-Csv</code>檔案。為了測試,先建立一個逗号分隔的文本檔案。

然後就可以使用Import-Csv輸入清單檔案了,

如你所見,<code>Import-Csv</code>了解逗号檔案的格式,并且可以逐列顯示資料。是以在解析逗号分割的文本檔案時,你可以節省下很多工作量:<code>Import-Csv</code>會替你完成。

第一行被解析成列的标題。然後你就可以将非常友善地将逗号分隔的值作為輸入,比如建立使用者賬号。

進階主題:除了使用<code>ForEach-Object</code>循環你還可以在括号中使用腳本塊。對于每一個管道内部的管道對象,腳本塊都會被執行。在下面的例子中,逗号分割檔案中的每一個使用者名都會通過<code>echo</code>的參數<code>-InputObject</code>傳回并輸出。

經常會碰到的一個任務就是解析原始資料,比如日志檔案,從所有的資料中擷取結構化的目标資訊。比如日志檔案:windowsupdate.log 它記錄了windows更新的細節資訊(在之前的例子中我們已經多次用到過這個小白鼠)。該檔案還有大量資料,以至于乍一看沒什麼可讀性。初步分析表明該檔案是逐行存儲的資訊,并且每行的資訊片段是以Tab字元分割的。

正規表達式為描述這類檔案格式提供了最友善的方式,之前已經提到過。

你可以按照下面的例子來使用正規表達式适當地描述檔案indowsupdate.log的内容。

<code>$matches</code>傳回了每個圓括号中定義的子正規表達式的比對項,這樣你就可以使用數字索引來尋址每個文本數組元素了。比如你隻對某一行中的日期和描述感興趣,然後格式化輸出它:

這種情況下,推薦給每一個子表達式取一個名字,這樣可以在後面通過該名字通路。

現在你可以使用<code>Get-Content</code>一行一行讀取整個日志檔案了,然後使用上面的方式逐行處理。這意味着即使在一個龐大的檔案中,你也可以快速,相對高效地收集所有你需要的資訊。下面的例子正好會列出那些日志行的描述資訊中包含了短語“woken up”的文本行。這可以幫助你找出一台機器是否曾經因為自動更新被從待機或者休眠模式喚醒。

如果進入循環,會将儲存在<code>$_</code>中的完整文本行輸出。你現在知道了如何使用正規表達式将一個包含特定資訊片段的文本行分割成數組。

然而,還有第二種,更為精妙的方法,從檔案中選擇個别文本行,它就是<code>Switch</code>。你隻需要告訴語句塊,那個檔案你想檢查,那個模式你想比對。剩下的工作就交給<code>Switch</code>吧!下面的語句會擷取所有安裝的自動更新日志。使用它比之前使用的<code>Get-Content</code>和<code>ForEach-Object</code>更快速。你隻需要記住正規表達式“<code>.*</code>”代表任意數量的任意字元。

如果你想找到其它程式的更新,比如SMS或者Defender。隻需要在你的正規表達式中使用“SMS”或者“Defender”替換“automatic updates”即可。事實上,<code>Switch</code>可以接受多個模式,按照下面聲明在花括号中的那樣,依賴多個模式進行比對。這就意味着隻需幾行代碼,就可以找出多個程式的更新。

不是所有的檔案都包含文本。有時,我們需要讀取二進制檔案中的資訊。正常情況下一個檔案的擴充名扮演的很重要的角色。因為它決定了Windows使用什麼程式來打開這個檔案。然而在許多二進制檔案中,檔案頭也緊密的內建到檔案中。這些檔案頭包含了該檔案是屬于那一類檔案的内部類型名稱。借助于參數<code>-readCount</code>和<code>-totalCount</code>,<code>Get-Content</code>可以擷取這些“魔法位元組”。參數<code>-readCount</code>指明每次讀取多少位元組,<code>-totalCount</code>決定了你想從檔案中讀取的總的位元組數。目前情況下,你需要從檔案中讀取的應當是前4個位元組。

Explorer的前四個位元組為4d, 5a, 90, 和 00或者已經列出的文本MZ。這是Microsoft DOS的開發者之一Mark Zbikowski的簡稱。是以,标記MZ就代表了可執行的程式。這個标記和圖檔檔案的标記不同:

如你所見,<code>Get-Content</code>也可以讀取二進制檔案,一次隻讀一個位元組。參數<code>-readCount</code>指定每一步讀取多少個位元組。<code>-totalCount</code>指定總共要讀取的位元組數,一旦給它指派為<code>-1</code>,它會從頭到尾讀取所有檔案内容。你可以通過将資料輸出為十六進制來預覽可執行檔案。因為純二進制文本不易閱讀。

<code>Move-Item</code> 和 <code>Copy-Item</code>用來執行移動和拷貝操作。它們也支援通配符。比如下面的腳本會将你家目錄下的的所有PowerShell腳本檔案複制到桌面上:

但是,隻有在目前目錄當下的腳本會被複制。幸虧<code>Copy-Item</code>還有一個參數<code>-recurse</code>,這個參數的效果類似Dir中的效果。如果你的初始化目錄不包含任何目錄,它也不會工作。

使用Dir也可以複制所有PowerShell腳本到你的桌面,讓我們先給你找出這些腳本,然後将結果傳遞給Copy-Item:

小技巧:你可能想要縮減腳本行,因為檔案對象整合了一個CopyTo()方法。

但是結果可能會出錯,因為CopyTo()是一個低級的函數。它需要檔案的目标路徑也被複制。因為你隻是想複制所有檔案到桌面,你已經指定了目标路徑的目錄。CopyTo()會嘗試将檔案複制這個精确的字元串路徑(桌面)下,但是肯定不會得逞,因為桌面是一個已經存在的目錄了。相反的Copy-Item就聰明多了:如果目标路徑是一個目錄,它就會把檔案複制到這個目錄下。

此時,你的桌面上可能已經堆滿了PowerShell腳本,最好的方式是将它們儲存到桌面的一個子目錄中。你需要在桌面上建立一個新目錄,然後從桌面到這個子目錄中移動所有的腳本。

此時,你的桌面又恢複了往日的整潔,也把腳本安全的儲存到桌面了。

使用<code>Rename-Item</code>你可以給檔案或者目錄換個名字。但是這樣做時要格外小心,因為如果把某些系統檔案給重命名了,可能會導緻系統癱瘓。甚至你隻是更改了某些檔案的擴充名,也會導緻它們不能正常打開或者顯示它們的一些屬性。

因為<code>Rename-Item</code>可以在管道中的語句塊中使用,這就給一些複雜的任務提供了令人驚訝的友善的解決方案。比如,你想将一個目錄的名稱和它的子目錄的名稱,包括目錄下的檔案的名稱中所有的“x86”詞語移除掉。下面的指令就夠了:

然而,上面的指令會實際上會嘗試重命名所有的檔案和目錄,即使你找的這個詞語在檔案名中不存在。産生錯誤并且非常耗時。為了大大提高速度,可是使用<code>Where-Object</code>先對檔案名進行過濾,然後對符合條件的檔案進行重命名。

如果你想更改檔案的擴充名,首先需要意識到後果:檔案随後會識别為其它檔案類型,而且可能被錯誤的應用程式打開,甚至不能被任何應用程式打開。下面的指令會把目前檔案夾下的所有的PowerShell腳本的字尾名從“.ps1”改為“.bak”。

由于<code>-whatIf</code>參數的緣故,一開始語句隻會表明可能會執行重命名操作。

資料集往往随着時間的增長而增長。如果你想整理一個目錄,你可以給定所有的檔案一個統一的名稱和序号。你可以從檔案的某些具體的屬性中合成檔案名。還記得上面在桌面上為PowerShell腳本建立的那個子目錄嗎?讓我們對它裡面的PowerShell腳本以數字序号重命名吧。

使用<code>Remove-Item</code>和别名<code>Del</code>可以删除檔案和目錄,它會不可恢複的删除檔案和目錄。如果一個檔案屬于隻讀檔案,你需要指定參數<code>-force</code> :

如果一個目錄被删除了,它裡面所有的内容都會丢失。在你嘗試去删除一個檔案夾連同它的内容時,PowerShell都會請求你的準許。這樣是為了防止你無意間銷毀大量資料。隻有空目錄才不需要請求确認資訊。

但是,如果你指定了參數<code>-recurse</code>:PowerShell會将這個目錄連同它裡面的内容删除,沒有任何确認提示。

對于NTFS驅動器來說,通路權限決定着那個使用者可以通路檔案和目錄。對于每一個檔案和檔案夾,所謂的安全描述符(SD)規定了安全資料。安全描述符決定安全設定是否隻對目前目錄有效,或者它可以被傳遞給其它檔案和目錄。真正的通路權限是在通路控制清單(ACL)中。每一個通路權限的通路控制項(ACE)也在ACL中。

注意:檔案和目錄通路權限相當于一個複雜的電子鎖。如果使用得當,你可以把它變成一個有力的安全系統。然而,如果使用不當,你可能很容易把自己鎖在外面,失去了通路重要資料的權限,或者破壞了Windows作業系統(當你無意間禁止了通路關鍵系統目錄的權限後)。作為檔案和目錄的所有者,你總是有更正權限的選項;作為一個管理者,你也總能取得檔案和目錄的擁有權。但這是不得已的後門,你不能依賴它:你應當在你能意識到後果的情況下更改權限。最好一開始使用測試檔案和目錄做實驗。

PowerShell使用<code>Get-Acl</code> 指令<code>Set-Acl</code> 來管理權限。此外,類似<code>cacls</code>這樣的傳統指令也可以在PowerShell的控制台上面使用。通常他們更改起來通路權限會比PowerShell指令更快。尤其在你處理非常多的檔案和目錄時。由于Windows Vista的釋出,<code>cacls</code>一直被視為過時。如果可能的化,你可以使用它的繼任者<code>icacls</code>。

檔案和目錄的有效安全設定在通路控制清單中,使用<code>Get-Acl</code>時,會擷取清單中的内容。是以如果你想找出誰能夠通路某些檔案或者目錄,可以這樣處理:

檔案和目錄的所有者還有一些特殊的權限。比如檔案的所有者總是能夠通路檔案。你可以通過Owner屬性,來擷取所有者名稱。

實際上通路權限就是——誰可以做什麼,下面輸出通路屬性:

在上面表格的<code>IdentityReference</code>列,告訴你誰有特殊的權限。<code>FileSystemRights</code>列告訴你權限的類型。<code>AccessControlType</code>列格外重要,如果它顯示“拒絕”而不是“允許”,你懂的,它會限制使用者通路。

<code>Get-Acl</code>執行後傳回的對象,包含若幹方法可以用來更新權限和設定所有權。如果你隻想設定自己的權限,都沒必要去安全描述符世界深究。往往,讀取一個已經存在的檔案安全描述符,把它傳遞給另一個檔案,或者按照特殊SDDL語言文字的形式指定安全資訊就夠了。

<code>技巧</code>:下面的例子會讓你認識一些日常步驟。注意兩點即可:别忘了<code>cacls</code>這個可靠的工具,因為使用它會比PowerShell指令更高效。此外,<code>Get-ACL</code>和<code>Set-ACL</code>不僅僅應用于檔案層面,還可以用于其它有通路控制的安全描述符的任何地方,比如Windows系統資料庫(會在下一章講解)

在一個初級的案例中,你可能都不會建立任何新的權限,隻會從一個已經存在的檔案或者目錄的通路控制清單中克隆一個權限,然後把它轉讓給其它檔案。優點是可以使用圖形使用者界面來設定那些通常比較複雜的權限。

開始之前,先建立兩個目錄作為測試:

現在,打開資料總管,設定Prototype目錄的安全設定。

在資料總管中,右擊Prototype目錄,選擇屬性,然後點選安全頁籤,點選編輯。通過添加其他使用者來更改測試目錄的安全設定。在下面的對話框中給新使用者設定權限。

注意:你也可以通過勾選拒絕複選框來拒絕使用者的權限。這樣做時,可要留心了。因為限制權限總是有高優先級。比如,你給了自己完全控制的權限,但是拒絕了“Everyone”這個組來通路。這樣就把自己關在檔案系統的外面了。因為你也屬于”Everyone”這個組,同時因為限制的優先級比較高,哪怕你已經給了自己“完全控制”的權限,這個限制也作用于你。

你更改了權限後,捎帶在資料總管中看看第二個目錄Protected。這個目錄仍舊是預設賦予的權限。下一步,我們會把Prototype剛才設定的權限轉交到Protected目錄。

注意:你本身需要特殊的權限去設定上面的權限。如果你用的是Windows Vista作業系統,并且啟用了UAC,使用PowerShell操作時,會出現錯誤,提示你沒有權限。這時可以通過讓控制台以管理者權限運作來擷取權限。

實驗做完了,現在呢,Protected和Prototype一樣安全。當你在資料總管中檢視它們的安全設定時,你會發現所有的設定都是相同的。

前面的例子非常簡單,你所做的隻是把已有目錄的安全設定移交給其它目錄。在你的日常工作中,你可能得具備一個你根本就不需要的Prototype目錄。但是你可以通過文本格式的安全描述符來歸納安全設定。每一個安全設定都是被特殊的安全描述符描述語言(<code>SDDL</code>)定義的。它能讓你以文本的形式讀取Prototype目錄的安全資訊,以後無須借助Prototype目錄即可使用。

讓我們删掉這個測試目錄Protected吧,然後在<code>SDDL</code>中儲存Prototype目錄的安全資訊。

然後把這個<code>SDDL</code>文本儲存到第二個腳本中,可将該安全設定賦給任意目錄。

注意:你的第二個目錄是完全獨立于Prototype目錄的。你所需要做的可能是,借助Prototype目錄使用圖形使用者界面,臨時生成一個SDDL安全設定定義。

然而,<code>SDDL</code>不能很友善的移交給其它機器。如果你仔細看下,每個授權使用者不是根據使用者名識别,而是根據它們的安全辨別符(<code>SID</code>)識别。不同的機器上,即使使用者名相同,這個<code>SID</code>也不會相同,因為它們隸屬不同的賬戶。但是在一個域(<code>domain</code>)中,相同名字的賬号的<code>SID</code>是相同的,因為域會集中管理。其結果就是<code>SDDL</code>解決方案在基于域環境的公司網絡中非常完美。

盡管如此,如果你處在一個小型的對等網絡中,<code>SDDL</code>也能非常有用。你隻需要使用“複制黏貼”去替換SID而已。不過,在對等網絡中,<code>cacls</code> 或者 <code>icacls</code>指令可能更簡單一點。

權限也可以被手動建立。其優點就是,即使沒有集中域,你也可以根據使用者名來指定授權使用者,這樣可以以相同的方式在任意機器上工作。但是注意,它引入了額外的工作,因為你必須完全建立你自己的安全描述符,接下來的例子會展示。但是在實踐中發現這個過程非常的耗時。使用cacls和icacls都比它簡單一點。現在我們删除掉測試目錄Protected,再次建立一個新的目錄,讓它隻有預設的通路權限。

接下來,讓我們一起看看每個通路規則是怎麼定義的。每一個規則需要5個細節:

Person:這是該規則應當适用的人或者組。

Access:這裡選擇規則要控制的權限。

Inheritance:這裡選擇規則要應用的對象。這個規則能夠,并且一般是會授予它的子對象,這樣它就能自動适用于目錄中的檔案了。

Propagation:決定權限是否要傳遞給子對象(比如子目錄和檔案),通常情況下設定為None,僅僅授予權限。

Type:它能讓你設定權限或者限制,如果限制,指定的權限會明确不予準許。

接下來問題是這些規範允許那些值?這個例子示範通過.NET對象(第六章)顯示這些規範。你可以使用下面的機器列出通路權限允許的值:

如果你想設定權限時,實際上得結合上面清單中列出的相關值,比如:

結果是一個數字,讀和寫權限的位掩碼。在上面的例子中,你可以非常簡單第擷取相同的結果,因為允許你指定你想要的項目,甚至把它們放在一個逗号分隔項中,緊跟在括号括起來的.NET枚舉類型後面。

因為這裡你沒有指定二進制計算符<code>-bor</code>,它的結果是可讀的文本。而此時需要位掩碼來工作,是以把它轉換成Integer整形資料類型。你可以像這樣随時得出設定的相關值。

這樣做的意義在于你現在可以測試其它.NET枚舉類型的值,把它們轉換成整數。雖然不能增強你的指令的可讀性,但是可以壓縮腳本。因為下面的腳本行和前面例子中的腳本行可以做同一件事。

最後,我們看看PowerShell是怎麼指定特定使用者的權限的。在上面的例子中,你指定了使用者或者組的名稱,但是權限不能識别使用者名,但能識别賬号的唯一<code>SID</code>,使用者名在内部會被更改成<code>SID</code>,你也可以在腳本中手動更改使用者名,看看指定的使用者名是否存在。

一個<code>NTAccount</code>對象描述了一個權限可以配置設定的安全主體。在實踐中,它是使用者群組。<code>NTAccount</code>對象可以使用<code>Translate()</code>來輸出它包含的與主體對應的<code>SID</code>。而這隻會在指定的賬号确實存在的情況下有效。否則,你會得到一個錯誤。是以你也可以使用<code>Translate()</code>來驗證一個賬号的存在性。

通過<code>Translate()</code>擷取的SID非常有用。如果你仔細看,你會發現管理者組的SID和你自己目前賬号的SID完全不同:

管理者組的<code>SID</code>不但很短,而且是唯一的。為了整合這個賬号,Windows使用了所謂的衆所周知的<code>SID</code>,它在所有的Windows系統中都是相同的。這一點很重要,因為你德文系統中運作上面的腳本會失敗。在本地化的德文系統上,因為<code>Administrators</code>組叫做”<code>Administratoren</code>”,”<code>Everyone</code>”組叫做”<code>Jeder</code>”。但是這些賬号的SID是相同的。知道了這些組的SID号,你就可以使用它們代替那些本地化的名稱了。下面是怎樣将SID轉換成使用者賬号的名稱:

如何讓你的腳本能夠非常完美地在國際本地化機器上運作: