Reputation: 43
I'm hoping to come up with a script that can walk a directory tree on a Windows server and show me a tree that only includes directories whose permissions are different from it's parent (or sub) directory. I want to produce an easily understandable report that can help me quickly audit the permissions of a folder structure.
Here's what I've got so far:
DIR "Z:\FileShare" -directory -recurse | GET-ACL | where {$_.AreAccessRulesProtected -eq $true} | select path, accessToString | format-list |out-file c:\permissions.txt
This produces a usable set of data as is but it's a bit bulky.
What I don't know is how to have it filter out redundant text, namely lines like "BUILTIN\Administrators Allow FullControl" and instead only show me the delta. Human readable psudo-code might be "if this ACL line can be found in the immediate parent directory, don't show it here."
Upvotes: 4
Views: 1585
Reputation: 60145
A more efficient take on my previous answer, this approach uses an equatable class and a Hashset<T>
to get unique access rules. Uniqueness is determined by IdentityReference
, FileSystemRights
and AccessControlType
in this case.
using namespace System.Collections.Generic
using namespace System.Security.AccessControl
using namespace System.Security.Principal
class SimpleAcl {
[string] $Path
[IdentityReference] $IdentityReference
[FileSystemRights] $FileSystemRights
[AccessControlType] $AccessControlType
hidden [ValueTuple[IdentityReference, FileSystemRights, AccessControlType]] $_comparable
SimpleAcl([FileSystemAccessRule] $rule, [string] $path) {
$this.Path = $path
$this.IdentityReference = $rule.IdentityReference
$this.FileSystemRights = $rule.FileSystemRights
$this.AccessControlType = $rule.AccessControlType
$this._comparable = [ValueTuple[IdentityReference, FileSystemRights, AccessControlType]]::new(
$this.IdentityReference, $this.FileSystemRights, $this.AccessControlType)
}
[bool] Equals([object] $that) {
if (-not ($that = $that -as [SimpleAcl])) {
return $false
}
return $this._comparable -eq $that._comparable
}
[int] GetHashCode() {
return $this._comparable.GetHashCode()
}
}
$map = @{}
$result = foreach ($dir in Get-ChildItem Z:\FileShare -Directory -Recurse) {
$dirsecurity = Get-Acl $dir.FullName
$parentAcl = $map[$dir.Parent.FullName]
$acls = [HashSet[SimpleAcl]]::new()
foreach ($rule in $dirsecurity.Access) {
$acl = [SimpleAcl]::new($rule, $dir.FullName)
$null = $acls.Add($acl)
if ($parentAcl -and -not $parentAcl.Contains($acl)) {
$acl
}
}
$map[$dir.FullName] = $acls
}
$result | Export-Csv path\to\acls.csv -NoTypeInformation
Upvotes: 0
Reputation: 1157
As noted above, thanks to Santiago for the two scripts, but I found in some circumstances it misses out some folder results. So ended up writing my own version that copes with that scenario.
Don't think it's as fancy as Santiago's, but it provides the information I need and may hopefully be of use to others.
$folderRoot = "E:\SharedFolders"
$GroupBase = "SecurityGroup."
$showRemoved = $false
foreach($dir in @(Get-ChildItem $folderRoot -Directory -Recurse -Depth 3 -ErrorAction SilentlyContinue))
{
$folderPerm = $dir | select-Object -ExpandProperty PSPath | Get-Acl | Select-Object -ExpandProperty Access| Where-Object {$_.IdentityReference -match $GroupBase}
$parentPerm = $dir | select-Object -ExpandProperty PSParentPath | Get-Acl | Select-Object -ExpandProperty Access | Where-Object {$_.IdentityReference -match $GroupBase}
if ($($folderPerm | Sort IdentityReference | select-Object -expandproperty IdentityReference | Out-String) -ne $($parentPerm | Sort IdentityReference | select-Object -expandproperty IdentityReference | Out-String))
{
$permAdded = @($folderPerm | select-Object IdentityReference,FileSystemRights | Where-Object {$($parentPerm | select-Object -expandproperty IdentityReference) -NotContains $($_ | select-Object -expandproperty IdentityReference)})
$permRemoved = @($parentPerm | select-Object IdentityReference,FileSystemRights | Where-Object {$($folderPerm | select-Object -expandproperty IdentityReference) -NotContains $($_ | select-Object -expandproperty IdentityReference)})
if ($permAdded -ne $null)
{
Write-Output "`n$($dir.FullName)"
foreach ($perm in $permAdded)
{
Write-Output "Group Added - $($perm.IdentityReference)"
Write-Output " Access - $($perm.FileSystemRights)"
}
}
if ($showRemoved)
{
if ($permRemoved -ne $null)
{
if ($permAdded -eq $null) { Write-Output "`n$($dir.FullName)" }
Write-Output "`n$($dir.FullName)"
foreach ($perm in $permRemoved)
{
Write-Output "Group Removed - $($perm.IdentityReference)"
Write-Output " Access - $($perm.FileSystemRights)"
}
}
}
}
}
Note 1 - In my scenario, all of the security groups have a common element to their names, so I use $GroupBase
to filter those out, and exclude user ACLs, admin users etc from the results I'm not interested in. If you want all the info unfiltered you can simply remove the Where-Object
statements at the $folderPerm
and $parentPerm
lines.
Note 2 - For speed I've used the -Depth 3
attribute on Get-ChildItem to limit it to 3 folders deep, since that is sufficient for me to find those folders that have been restricted. But that can obviously be adjusted if needed.
Note 3 - If $showRemoved = $true
then it will also output which groups have been removed from subfolders. Useful for full audit, but less so if you just want to see which groups have been applied to which folders, especially since I found it can generate a lot of results! So I made it so you can toggle it as needed.
Upvotes: 0
Reputation: 60145
I tested this with a few folders setting up different ACLs with my own user and it seem to be working but I haven't tested enough to be sure. Basically, the script will loop through directories and add the ACLs to a dictionary where the Keys are each IdentityReference
and the Values are the properties from the ACLs which you're interested on (FileSystemRights
and AccessControlType
) in addition to the folder's absolute path. While enumerating the directories, each object will be compared against the stored values using the Compare-Acl
function which only returns $true
if the object is different.
using namespace System.Collections
using namespace System.Collections.Generic
$map = [Dictionary[string, ArrayList]]::new()
$outObj = {
[pscustomobject]@{
AbsolutePath = $dir.FullName
FileSystemRights = $acl.FileSystemRights
IdentityReference = $acl.IdentityReference
AccessControlType = $acl.AccessControlType
}
}
function Compare-Acl {
param(
[object[]] $Reference,
[object] $Difference
)
foreach ($ref in $Reference) {
$fsRights = $ref.FileSystemRights -eq $Difference.FileSystemRights
$actRef = $ref.AccessControlType -eq $Difference.AccessControlType
if ($fsRights -and $actRef) {
return $false
}
}
$true
}
foreach ($dir in Get-ChildItem Z:\FileShare -Directory -Recurse) {
foreach ($acl in (Get-Acl $dir.FullName | Where-Object AreAccessRulesProtected).Access) {
if ($thisKey = $map[$acl.IdentityReference]) {
$obj = & $outObj
if (Compare-Acl -Reference $thisKey -Difference $obj) {
$null = $thisKey.Add($obj)
}
continue
}
$obj = & $outObj
$map.Add($acl.IdentityReference, [object[]] $obj)
}
}
$map.Keys.ForEach({ $map[$_] }) | Export-Csv path\to\acls.csv -NoTypeInformation
Upvotes: 3