Darius Dauer
Darius Dauer

Reputation: 43

PowerShell: Display the differences in permissions between folders and their parents

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

Answers (3)

Santiago Squarzon
Santiago Squarzon

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

Keith Langmead
Keith Langmead

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

Santiago Squarzon
Santiago Squarzon

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

Related Questions