跟同事聊到一個需求,異動 Windows 資料夾權限前希望能保留整個資料夾(含子目錄)的原有權限設定,作為異動前後對照或萬一操作失誤的還原參考。

基本上就是記錄檔案總管資料夾內容的安全性頁籤,將各群組或使用者名稱及其對映的完全控制、修改、讀取和執行... 等權限儲存下來,但要用程式完成:

按慣例,又是 PowerShell 展現威力的時刻。研究後發現,對 PowerShell 是件簡單任務,先 Get-ChildItem -Recurse -Directory 列出所有子資料夾,再 Get-ACL 取得權限設定就行了。有個眉角是子資料夾權限多半來自繼承,可以用推算的,逐一表列的意義不大,我們關心的重點在額外設定的權限,排除來自繼承的權限才不會模糊焦點。

故清單的輸出原則為:最上層目錄列舉所有權限,底下的子目錄若完全繼承上層權限就不顯示,權限與上層不同者也只會顯示多出的權限。

工具程式 List-Acl.ps1 的輸入參數有三個,$path 為查詢對象、$report 是開關參數,用來切換輸出權限物件集合或是 HTML 報表、$showInheritedPerms 也是開關,控制要不要印出繼承權限。

若目錄包含多層或大量子資料夾,遞迴查完得花上好一段時間,為避免使用者看不到進度陷入焦慮,我新學會用 Write-Progress 顯示目前處理的目錄名稱,能大幅提升使用者體驗。

List-Acl 的使用方式有兩種,第一種是輸出權限設定物件再轉換,像是接 ConvertTo-Json 轉成 JSON:

.\List-Acl.ps1 C:\WWW | ConvertTo-Json

第二種是加上 -report 參數輸出成 HTML 報表,所有權限一目瞭然,十分實用:(灰色為繼承權限,藍色為額外設定權限)

.\List-Acl.ps1 C:\WWW -report > D:\Report.html

完整程式碼如下:

param (
    [Parameter(Mandatory = $true)]
    [string]$path,
    [switch]$report,
    [switch]$showInheritedPerms
)

function FileSysRightText($sysRights) {
    $t = $sysRights.ToString()
    if ([System.Text.RegularExpressions.Regex]::IsMatch($t, "^[-0-9]")) {
        return "Special"
    }
    return $t
}
function Bool2YN($bool) {
    if ($bool) { return "Y" }
    return "N"
}

function GetAclRecord([string]$path, [bool]$incInheritedPerms) {
    Write-Progress -Activity "$path"
    $acl = Get-Acl $path
    $access = $acl.Access
    if (!$incInheritedPerms) {
        $access = $access | Where-Object { !$_.IsInherited }
    }
    if ($access.Count -gt 0) {
        [PSCustomObject]@{
            Path   = $path;
            Access = ($access | ForEach-Object {
                    "$($_.IdentityReference)`t$($_.AccessControlType.ToString()[0])`t$(FileSysRightText $_.FileSystemRights)`t$(Bool2YN $_.IsInherited)"
                })
        }
    }
}

Clear-Host

$list = New-Object System.Collections.ArrayList

$list.Add((GetAclRecord $path $true)) | Out-Null
Get-ChildItem -Path $path -Directory -Recurse | ForEach-Object {
    $list.Add((GetAclRecord $_.FullName $showInheritedPerms)) | Out-Null
}

Clear-Host

if ($report) {
@"
    <!DOCTYPE html>
    <html><head><title>Permission Checklist for $path</title>
    <style>
        html,body,table { font-size: 10pt; font-family: 微軟正黑體; }
        table { border-collapse: collapse; }
        thead td { text-align: center; background-color: royalblue; color: white; }
        td { padding: 3px; border: 1px solid #bbb; }
        td[rowspan] { vertical-align: top; }
        td.Y { color: gray; }
        td.N { color: blue; }
    </style>
    </head><body><h3>$path Permission Checklist</h3>
    <table><thead><tr><td>Path</td><td>User</td><td>Rights</td></tr></thead>
    <tbody>
"@
    $list | Where-Object { $_.Path } | ForEach-Object {
        $acl = $_
        $first = $true
        $acl.Access | ForEach-Object {
            "<tr>"
            if ($first) {
                "<td rowspan=$($acl.Access.Count)>$($acl.Path)</td>"
                $first = $false
            }
            $p = $_.Split("`t")        
            "<td class='$($p[1]) $($p[3])'>$($p[0])</td><td class='$($p[1]) $($p[3])'>$($p[2])</td>"
            "</tr>"
        }
    }
    "</tbody></table></body></html>"
}
else {
    Write-Output $list
}

不到 100 行寫完這個實用小工具,自己也覺得滿意,是我近期的得意作品之一,分享給大家。

A handy tool to list permissions of directories with PowerShell.


Comments

# by James Chang

拍拍手...真的很不錯...共用資料夾開太多就很煩惱....

# by Andrew Hsieh

謝謝高手的分享... 目前執行script後Report HTML只有第一層目錄權限,但沒有子目錄權限,請問是否有其他要注意的地方? (我是跑在Win10 專業版版本 1803 上) 非常感謝!

# by Jeffrey

to Andrew Hsieh,清單輸出原則為:最上層目錄列舉所有權限,底下的子目錄若完全繼承上層權限就不顯示,權限與上層不同者也只會顯示多出的權限。如果要輸出所有繼承權限,可加上 -showInheritedPerms 參數。

# by Andrew Hsieh

Hi Jeffrey, 感謝您的快速回覆! 先說聲抱歉,可能我之前的表達有一點不清楚,實際上我執行script後產生的 HTML 檔案是只有第一層目錄,以您的範例就是只有 C:\WWW 而已,並沒有出現第二層目錄 AppNet and Darkblog,所以我才想是否是有遺漏甚麼.非常感謝!

# by Jeffrey

to Andrew Hsieh, 試著在 C:\WWW 建個 C:\WWW\Test 資料夾,改一下 Test 的 NTFS 權限(例如:賦與 Everyone 完全控制),再跑一次 PS Script,此時應該會看到 C:\WWW\Test。

# by Andrew Hsieh

Hi Jeffrey, 完美,已經OK. ps. 經確認只有一層係因為我的測試目錄都是"繼承權限",沒有"額外設定權限",所以才沒有第二層目錄. 感謝喔!

# by 聶風

抱歉,這個功能要怎麼使用呢?程式碼要存成psl嗎?

# by Jeffrey

to 聶風,存成 .ps1 (123 的 1,不是 l),如果之前沒用過 PowerShell,這裡有篇教學 https://officeguide.cc/powershell-beginner-introduction/

# by kith

這是一個很棒的Powershell,已經用了約半年左右,都可以解決很多報表產出!!! 不過近期我遇到一個問題,PowerShell get-acl List folder contents vs ReadAndExecute 網址:https://stackoverflow.com/questions/34414438/powershell-get-acl-list-folder-contents-vs-readandexecute 請問是否有機會解決目錄權限在判斷上的落差,謝謝

# by kith

自問自答,調整ps1內容: function FileSysRightText($sysRights) { $t = $sysRights.ToString() if ([System.Text.RegularExpressions.Regex]::IsMatch($t, "^[-0-9]")) { return "Special" } if ([System.Text.RegularExpressions.Regex]::IsMatch($t, "ReadAndExecute, Synchronize")) { return "ListDirectory" } return $t }

# by kith

調整如此後~~ReadAndExecute 的權限,會有問題,繼續調整

# by jason11111

請問報表產生的user,可以是顯示名稱嗎?

# by Jeffrey

to jason11111, 使用 net user (CMD) 或 Get-ADUser (PowerShell) 可由帳號查詢使用者名稱,應能滿足你的需求。

# by Chuck

想請問我用Powershell 控制台跑了這個程式 有顯示結果,但都沒有產出html檔案,是哪裡還要修正嗎? 感謝

# by Chuck

抱歉 不用理我 上面已經寫出怎麼產report...感謝

# by GG

幫了我一個大忙!!

# by speed

# 2021-02-17 04:58 PM by Jeffrey to Andrew Hsieh,清單輸出原則為:最上層目錄列舉所有權限,底下的子目錄若完全繼承上層權限就不顯示,權限與上層不同者也只會顯示多出的權限。如果要輸出所有繼承權限,可加上 -showInheritedPerms 參數。 請問 -showInheritedPerms 參數,要放在哪裡?

# by speed

沒事,我看懂了!!! 非常強大!!

# by Terence

最近在稽核共用資料夾權限, 看到這個覺得的救了!!!! ((不用一個一個截圖)) 不過...我們資料夾超多層的,有辦法限制只scan到第n層就不掃描嗎? 謝謝!

# by Terence

沒事,已解決,是我不懂 PowerShell 加一個 -Depth n 即可。

# by Erik

感謝大哥提供如此好用的程式。 順便告知一下 bug: .\List-Acl.ps1 C:\WWW -report > D:\Resport.html 這樣最後的英文多了一個 "s"

# by Jeffrey

to Erik, 感謝,不是 Bug,是個人特色。哈!

# by KEVIN

請問版大,如果權限在產出的時候,之前變成中問,可以用甚麼樣的方式?

# by Jeffrey

to Kevin, 沒看懂「之前變成中(間?)」的意思

# by KEVIN

不好意思沒有說清楚,例如:程式產出報表權限時顯示(FullControl),直接變成設定的中文文字(完全控制),大概是這個意思。

# by Jeffrey

to KEVIN, 比較快的做法是自己翻譯,MS Learn 有權限列舉的完整清單 https://learn.microsoft.com/zh-tw/dotnet/api/system.security.accesscontrol.filesystemrights?view=netframework-4.8 但我沒查到官方的中文對照表

# by QCC

請問可以指定掃到第幾層嗎?

# by Jeffrey

to QCC,在 Get-ChildItem 加上 Depth 參數可控制掃到第幾層 https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem

# by frozenblue

感謝,非常實用,解決了不少問題。

# by CL

非常實用,感謝大感謝~~

# by Kai

大大~ 如果只想要列出某指定帳號 ABC 所擁有權限的資料夾目錄及檔案樹狀圖 要如何修改調整呢

# by Jeffrey

to Kai, 判斷是否擁有權限有點複雜,例如目錄授權A群組,帳號屬於B群組,B群組是A群組成員;X帳號同時是A、B群組成員,A群組唯讀、B群組可讀寫,結果為可讀寫... 我沒想到好方法可以快速判斷。

# by Quit

請問各位前輩,上述留言關於 [僅搜尋到第n層] 的 -Depth n 參數,要怎麼下? 不知道要加在哪裡 假如只想要蒐到第3層目錄就好 .\List-Acl.ps1 C:\WWW -report -Depth 3 -showInheritedPerms > D:\Report.html 這樣會錯誤[找不到符合參數名稱 'Depth 3' 的參數。]

# by Jeffrey

to Quit, 要改程式,加在 Get-ChildItem https://learn.microsoft.com/zh-tw/powershell/module/microsoft.powershell.management/get-childitem

# by Quit

To Jeffrey, 我試著在U底下建立了test、test\test1、test\test1\test2\... 持續到test5 然後在程式改成了 Get-ChildItem -Path $path -Depth 3 -Directory -Recurse | ForEach-Object { 得到的結果如下: U:\test\test1\test2\test3 第1層的test,是不會計算在內嗎?因為看起來是搜尋到第4層

# by Jeffrey

to Quit, 文件是寫「-Depth 2 包含 Path 參數的目錄、子目錄的第一層,以及子目錄的第二層。」你看看是否與你實測結果一致。

Post a comment