I saw a tweet this morning that was a PowerShell one-liner for capturing folder permissions to a text file. There's nothing wrong with it but it's hard to be truly productive in 140 characters so I thought I would take the idea and run with it a little bit. Here are some ways you might want to extend the concept.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The basic premise is to pass a bunch of folders and save the results somewhere. For the sake of flexibility and clarity I'll save my ACL results to a variable.
[cc lang="PowerShell"]
$root="c:\scripts"
$acldata=dir $root -Recurse | Where {$_.PSIsContainer} | Get-ACL
[/cc]
I always like seeing what kind of information is available so let me check the first item in $acldata.
[cc lang="DOS"]
PS C:\> $acldata[0] | Select *
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\scripts\ad
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\scripts
PSChildName : ad
PSProvider : Microsoft.PowerShell.Core\FileSystem
AccessToString : BUILTIN\Administrators Allow FullControl
BUILTIN\Administrators Allow 268435456
NT AUTHORITY\SYSTEM Allow FullControl
NT AUTHORITY\SYSTEM Allow 268435456
BUILTIN\Users Allow ReadAndExecute, Synchronize
NT AUTHORITY\Authenticated Users Allow Modify, Synchronize
NT AUTHORITY\Authenticated Users Allow -536805376
AuditToString :
Path : Microsoft.PowerShell.Core\FileSystem::C:\scripts\ad
Owner : SERENITY\Jeff
Group : SERENITY\None
Access : {System.Security.AccessControl.FileSystemAccessRule, System.Security.AccessControl.FileSystemAccessRule, System.Security.AccessControl.FileSystemAcces
sRule, System.Security.AccessControl.FileSystemAccessRule...}
Sddl : O:S-1-5-21-2858895768-3673612314-3109562570-1000G:S-1-5-21-2858895768-3673612314-3109562570-513D:AI(A;ID;FA;;;BA)(A;OICIIOID;GA;;;BA)(A;ID;FA;;;SY)(A;
OICIIOID;GA;;;SY)(A;OICIID;0x1200a9;;;BU)(A;ID;0x1301bf;;;AU)(A;OICIIOID;SDGXGWGR;;;AU)
AccessRightType : System.Security.AccessControl.FileSystemRights
AccessRuleType : System.Security.AccessControl.FileSystemAccessRule
AuditRuleType : System.Security.AccessControl.FileSystemAuditRule
AreAccessRulesProtected : False
AreAuditRulesProtected : False
AreAccessRulesCanonical : True
AreAuditRulesCanonical : True
[/cc]
Ok. Lots of good stuff. For a simple report, I can grab a few key properties, format as a list and send to a file.
[cc lang="PowerShell"]
$acldata | Format-List Path,Owner,AccessToString,SDDL | Out-File ACLReport.txt
[/cc]
But we can do better. For example, I wish the path was just the file system path. No problem, I'll use a hash table and define a new property. While I'm at it I'll improve the AccessToString property.
[cc lang="PowerShell"]
$aclData | Select @{Name="Path";Expression={$_.PSPath.Substring($_.PSPath.IndexOf(":")+2) }},
Owner,@{Name="ACL";Expression={$_.AccessToString}},SDDL | Out-File ACLReport.txt
[/cc]
The downside is that I end up with a text file, which I suppose is fine for small folder structures, and it is reader-friendly. But it is not easy to search or extract information from. For me, I think the better approach is to archive the data in an XML format using Export-Clixml. Export-CSV is not a good option here because the Access property is a collection of access rule objects and CSVs can't properly capture this level of complexity.
[cc lang="Powershell"]
$aclData | Select @{Name="Path";Expression={$_.PSPath.Substring($_.PSPath.IndexOf(":")+2) }},
Owner,Access,AccessToString,Sddl,@{Name="Reported";Expression={(Get-Date).ToShortDateString()}},
@{Name="Computername";Expression={$env:computername}} | Export-Clixml e:\temp\acldata.xml
[/cc]
I've also added a few properties to capture the computername and the date of ALC scan. Now I have richer data, stored in a text file for archival or research purposes. Later on, the file can be imported back into PowerShell.
[cc lang="DOS"]
PS C:\> $acl=Import-Clixml E:\acldata.xml
[/cc]
Now I can search the data using PowerShell.
[cc lang="DOS"]
PS C:\> $acl | select Path,Owner
Path Owner
---- -----
C:\scripts\ad SERENITY\Jeff
C:\scripts\de-DE BUILTIN\Administrators
C:\scripts\Demos BUILTIN\Administrators
C:\scripts\en-US BUILTIN\Administrators
C:\scripts\modhelp BUILTIN\Administrators
C:\scripts\PowerShellBingo BUILTIN\Administrators
C:\scripts\PS-TFM BUILTIN\Administrators
C:\scripts\ad\New SERENITY\Jeff
[/cc]
Or perhaps I want to find folders where Users have FullControl.
[cc lang="PowerShell"]
PS C:\> $acl | where {$_.access | where {$_.FileSystemRights -match "FullControl" -AND $_.IdentityReference -match "Users"}} |
>> format-list Path,AccessToString
>>
Path : C:\scripts\Demos
AccessToString : NT AUTHORITY\Authenticated Users Allow FullControl
BUILTIN\Users Allow FullControl
BUILTIN\Administrators Allow FullControl
BUILTIN\Administrators Allow 268435456
NT AUTHORITY\SYSTEM Allow FullControl
NT AUTHORITY\SYSTEM Allow 268435456
BUILTIN\Users Allow ReadAndExecute, Synchronize
NT AUTHORITY\Authenticated Users Allow Modify, Synchronize
NT AUTHORITY\Authenticated Users Allow -536805376
[/cc]
This is a little tricky because the Access property is a collection of nested rule objects, but it works.
The bottom line is, when archiving information consider who is going to use it and how. Take a little extra time to include information that might be outside the scope of the primary task, but potentially useful as I did by including a date and computername. This type of context may be invaluable when you look at the data a year from now. PowerShell makes it easy to do, so why aren't you?
Utilizing the xml export and import, what would be the easiest way to separate the ACLs into an array string so that each ACL would be a new index in the array? I’m trying to make a way to check the permissions on shares to make sure they conform to standards, and the only way I can see to do it effectively is to compare the current ACL to a control set. Using
$acls = Import-Clixml G:\test\acltst.xml
% {
$perm = $_.acl
}
creates a character array so that each character is an array index. Any help would be greatly appreciated.
You could use Compare-Object. Assume myfolder.xml was created earlier using Export-CLixml. I’m simplifying with a single folder.
PS C:\> $a=Get-ACL myfolder
PS C:\> $b=import-clixml myfolderacl.xml
PS C:\> Compare-Object $a $b -property Access
access SideIndicator
—— ————-
{System.Security.AccessControl.FileSystemAccessRule, Sys… =>
{System.Security.AccessControl.FileSystemAccessRule, Sys… diff $a $b -Property access | where {$_.SideIndicator -eq “=>”} | select -ExpandProperty access
Or this:
PS C:\> diff $a.access $b.access | where {$_.sideindicator -eq “=>”}
Not perfect but at least I’ve narrowed down the differences. This isn’t easy to do because you need to compare two different collections of objects. I’ll have to think some more about this.
Thanks Ill give that a try.
Enumerating access lists produces “friendly” output, but has it’s drawbacks, IMHO.
It’s fine, and probably preferable if there’s a relatively limited number of directory objects involved. If it has to scale to large numbers of objects, it can get agonizingly slow. I think this is due to the fact that the actual ACEs use a SID for the identity reference. Enumerating the access list means doing a name resolution on every ACE to get the CN.
For large scale work you can get much better performance is you retrieve just the SDDL, and work with that. If you need name resolution, build a hash table of SID/CN and use that as a cache so you aren’t doing repetitive lookups of the same SID.
Also, if it’s for auditing purposes, I think the SID needs to be included with the “friendly” name. Display names and CNs are volatile. SIDs are not. A name change can make doing an audit trail from archived permission reports a useless dead end if there is no fixed reference like a SID or account GUID also attached to the permission.
IMHO
Managing permissions in Windows is always a pain and the PowerShell ACL cmdlets are a little better but still not easy to use, especially for the reasons you mention. Thanks for your thoughts.