本週 Windows PowerShell 秘訣

Office Space

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

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

處理安全性描述元

對許多人來說,安全性描述元永遠代表系統管理指令碼的聖杯:覆蓋在神秘與神話之下,安全性描述元是每個人夢想的一件事,但卻從未期望親自看見它們。

聽起來很有趣,因為使用指令碼的話,並非完全「不可能」來管理安全性描述元。畢竟,WMI 多年來都擁有這個功能,而且在最初發行的 Windows PowerShell 中,就引進稱為 Set-ACL 的 cmdlet。這有什麼了不起?

嗯!這個偉大想法的事實就是有些東西可能理論上可行,但並非一定非常切合實際。您可以使用 WMI 來管理安全性描述元嗎?當然,只不過膽小怕事的人絕不會想要用它。好吧!不過,您不是可以使用 Set-ACL 來設定安全性描述元嗎?當然,但依照預設,只有當您手動設定另一個物件 (例如一個檔案) 的安全性設定值,然後將那些設定值複製到新物件時,才會使用。下面是取自 PowerShell 說明檔的範例:

$DogACL = get-acl c:\dog.txt
set-acl -path C:\cat.txt -AclObject $DogACL

您從檔案 Dog.txt 取得安全性描述元,然後將該安全性描述元複製到 Cat.txt。那很有用,但並非多數人心中所期望的。

因此,有沒有「任何」方法可以輕易使用指令碼來設定安全性描述元呢?嗯!事實證明,只要您想要使用 Windows PowerShell,而且願意一頭栽進 .NET Framework,就會有辦法:

$colRights = [System.Security.AccessControl.FileSystemRights]"Read, Write" 

$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None 
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None 

$objType =[System.Security.AccessControl.AccessControlType]::Allow 

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer") 

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType) 

$objACL = Get-ACL "C:\Scripts\Test.ps1" 
$objACL.AddAccessRule($objACE) 

Set-ACL "C:\Scripts\Test.ps1" $objACL

我們承認,這第一眼看起來可能不像是讓您「輕鬆」使用指令碼設定安全性描述元的解決方案。請再忍耐一下,您可能會改變心意,尤其是在我們說明這個指令碼如何運作之後,以及在我們指出多數指令碼都是可在「任一」 PowerShell 指令碼中,用來管理安全性描述元的照本宣科文字之後。

然後,您也有可能「不會」改變心意。我們很快就會知道答案!

讓我們從一開始的地方開始 -- 哦不,稍等一下,讓我們在一開始「之前」的地方就開始。在更進一步之前,我們應該注意到 Scripting Guy 在檔案與資料夾上,玩弄安全性描述元的時間有限。我們可以保證此範例指令碼在任一與所有狀況下都可以運作嗎?不。我們可以保證此指令碼不會將指定檔案或資料夾的安全性弄得亂七八糟嗎?不。我們可以保證此指令碼不會在時空連續體中撕破一個洞,而讓您被吸入黑洞中嗎?不。我們展示的範例指令碼,對我們而言運作的很好,我們只能這樣說。使用此範例要自負風險,請明智地使用 (也就是說,將它用在測試機器上,直到您滿意而它不會造成任何問題為止)。

那應該可以讓您安心了吧?

好的,「現在」就讓我們開始進行。在程式碼的第一行中,我們建立檔案系統權限的陣列;那是結構 [System.Security.AccessControl.FileSystemRights] (.NET Framework 檔案系統列舉) 所代表的內容。在範例程式碼中,我們指定兩個權限 (Read 與 Write) 給稱為 $colRights 的變數;我們稍後將使用此變數,也就是在我們實際建立新的 ACE (存取控制項目) 時:

$colRights = [System.Security.AccessControl.FileSystemRights]"Read, Write"

在指令碼中只能指定 Read 與 Write 這兩個檔案系統權限嗎?不。理論上 (因為我們尚未嘗試所有各種可能性,所以說「理論上」) 您可以指定下列任何權限:

  • AppendData

  • ChangePermissions

  • CreateDirectories

  • CreateFiles

  • Delete

  • DeleteSubdirectoriesAndFiles

  • ExecuteFile

  • FullControl

  • ListDirectory

  • Modify

  • Read

  • ReadAndExecute

  • ReadAttributes

  • ReadData

  • ReadExtendedAttributes

  • ReadPermissions

  • Synchronize

  • TakeOwnership

  • Traverse

  • Write

  • WriteAttributes

  • WriteData

  • WriteExtendedAttributes

懂了嗎?簡而言之,在程式碼第一行中,我們簡單指出要指定的檔案系統權限。

在下兩行程式碼中,我們指出如何繼承這些權限並傳遞給子項目。(實際上的情況大多會針對資料夾,而不是檔案。)在兩種情況下,我們都將此值設定為 None。或者,我們可以將 InheritanceFlag 設定為下列任何一項:

  • ContainerInherit (ACE 由子容器繼承,例如子資料夾)

  • ObjectInherit (ACE 由子物件繼承,例如檔案)

  • None

在同一行,我們可以將 PropagtionFlag 設定為下列任何一個選項:

  • InheritOnly (ACE 傳遞至所有的子物件)

  • NoPropagateInherit (ACE 不傳遞至子物件)

  • None

您應該如何才能知道所有這些值所代表的「意義」呢?我們的建議是設定測試資料夾,在該資料夾中丟入一些測試檔案,然後嘗試 InheritFlags 與 PropagateFlags 的各種組合,看看會發生什麼事。

接著,我們宣告 ACE 類型;ACE 可以是 [允許] 存取物件或 [拒絕] 存取物件。在我們的情況下,將會允許存取:

$objType =[System.Security.AccessControl.AccessControlType]::Allow

看到沒?還不錯吧?下一行程式碼只是單純地建立新物件 ($objUser),代表被指定下列權限的使用者:

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer")

此時,我們已經準備好在記憶體中建立自己的新 ACE。那是我們在建立 System.Security.AccessControl.FileSystemAccessRule 類別的新例項時,所做的事,小心將所有的新變數 (如 $objUser 與 $colRights) 當作參數傳送到 New-Object cmdlet:

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType)

當然,這是「完全」照本宣科;只要您已經指定正確的值給個別的變數,則這行程式碼應該像在任何安全性描述元中一樣地工作。

但是請稍等一下,不要走開;我們尚未完成。到目前為止我們已經完成的事,是在記憶體中建立新的 ACE;我們尚未真正將這個新的 ACE 加入檔案或資料夾。為了這麼做,首先我們需要從該檔案或資料夾擷取安全性描述元;那是這行程式碼所執行的工作:

$objACL = Get-ACL "C:\Scripts\Test.ps1"

沒錯:這很簡單吧!我們正在做的就是使用 Get-ACL cmdlet,從檔案 C:\Scripts\Test.ps1 擷取安全性描述元,然後將資料儲存到稱為 $objACL 的變數。

一旦我們擁有 Test.ps1 之安全性描述元的物件參照,就可以使用 AddAccessRule 方法來新增我們的新 ACE:

$objACL.AddAccessRule($objACE)

現在剩下的工作就是使用 Set-ACL cmdlet,將修改過的安全性描述元指定回 Test.ps1:

Set-ACL "C:\Scripts\Test.ps1" $objACL

這真的是所有的過程嗎?嗯!請接著往下看。嘗試執行下面的命令來擷取安全性描述元,然後將它顯示為清單:

Get-ACL "C:\Scripts\Test.ps1" | Format-List

現在,讓我們看看使用者 fabrikam\kenmyer 是否顯示在安全性描述元的任何位置:

Path   : Microsoft.PowerShell.Core\FileSystem::C:\scripts\test.ps1
Owner  : FABRIKAM\pilarackerman
Group  : FABRIKAM\Domain Users
Access : FABRIKAM\kenmyer Allow  Write, Read, Synchronize
BUILTIN\Administrators Allow  FullControl
NT AUTHORITY\SYSTEM Allow  FullControl
FABRIKAM\ pilarackerman Allow  FullControl
BUILTIN\Users Allow  ReadAndExecute, Synchronize

嗯!您看到了什麼:

Access : FABRIKAM\kenmyer Allow  Write, Read, Synchronize

如同我們所說,這看起來運作得很好。儘管如此,在我們自行重複的風險下,在「真正」的電腦上玩弄安全性描述元之前,請先在測試電腦上試試看,應該是個不錯的主意。

現在如果您改變心意,並決定「擺脫」 ACE,會發生什麼事呢?嗯!有兩種方法可以做達成該功能。首先,您可以使用 RemoveAccessRule 方法,移除單一權限。例如,下面這個指令碼會從 kenmyer ACE 移除 Write 權限:

$colRights = [System.Security.AccessControl.FileSystemRights]"Write" 

$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None 
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None 

$objType =[System.Security.AccessControl.AccessControlType]::Allow 

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer") 

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType) 

$objACL = Get-ACL "c:\scripts\test.ps1" 
$objACL.RemoveAccessRule($objACE) 

Set-ACL "C:\Scripts\Test.ps1" $objACL

如您所見,此方法幾乎是相同的。事實上,只有兩點差異。第一,我們只指定 Write 權限給檔案系統權限的清單;因為這是我們想要處理的唯一權限:

$colRights = [System.Security.AccessControl.FileSystemRights]"Write"

第二,我們使用 RemoveAccessRule 方法,從安全性描述元「移除」這個權限:

$objACL.RemoveAccessRule($objACE)

或者,我們可以使用 RemoveAccessRuleAll 方法,從安全性描述元完全移除使用者 fabrikam\kenmyer:

$colRights = [System.Security.AccessControl.FileSystemRights]"Read" 

$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None 
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None 

$objType =[System.Security.AccessControl.AccessControlType]::Allow 

$objUser = New-Object System.Security.Principal.NTAccount("wingroup\kenmyer") 

$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType) 

$objACL = Get-ACL "c:\scripts\test.ps1" 
$objACL.RemoveAccessRuleAll($objACE) 

Set-ACL "C:\Scripts\Test.ps1" $objACL


這一次,我們只新增單一權限 (Read) 到檔案系統權限的集合中。剛剛我們才告訴您,必須在檔案系統權限中列出「一些東西」,雖然看起來似乎並不重要。如果 ACE 包含整批權限那就有關係了;只增加一個權限到集合中,像是在開玩笑一樣。

此外,我們還在這個指令碼中使用不同的方法 RemoveAccessRuleAll:

$objACL.RemoveAccessRuleAll($objACE)

這個方法是要做什麼呢?答對了:它會移除整個 ACE。再次執行 Get-ACL,然後看一下 Test.ps1 的安全性描述元:

Path   : Microsoft.PowerShell.Core\FileSystem::C:\scripts\test.ps1
Owner  : FABRIKAM\pilarackerman
Group  : FABRIKAM\Domain Users
Access : BUILTIN\Administrators Allow  FullControl
NT AUTHORITY\SYSTEM Allow  FullControl
FABRIKAM\ pilarackerman Allow  FullControl
BUILTIN\Users Allow  ReadAndExecute, Synchronize

不錯吧!真不賴。

無可否認地,那只是處理安全性描述元的粗略介紹,但本週我們只有這點時間了。試試看 (對了,要在測試機器上試喔);同時,Scripting Guy 也會繼續使用這個方法。告訴你一個小秘密,最後我們應該可以找到系統管理指令碼的聖杯:有計畫地管理安全性描述元的方法。

大伙兒下週見。

顯示: