Get-ACL script with null Value expression

Hello,

First time poster and very newbie to powershell but trying and getting there at the moment with example code and trying to mold it into functional scripts to improve taks in my position.

The script uses get-acl to print acl rights for our entire group volumes that can span many Tb’s.

$OutFile = “C:\powershell\MLCPermissions.csv”
$ACLList =@()
Del $OutFile
$RootPath = “E:\Data\GROUPS”

$Folders = get-childitem -Recurse $RootPath | where {$_.psiscontainer -eq $true}

foreach ($Folder in $Folders){
$ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access }
Foreach ($ACL in $ACLs) {
$OutInfo = New-Object -TypeName psobject -Property @{
FolderPath = $Folder.Fullname
FileSystemRights = $ACL.FileSystemRights
IdentityReference = $ACL.IdentityReference.ToString()
AccessControlType = $ACL.AccessControlType.ToString()
IsInherited = $ACL.IsInherited
InheritanceFlags = $ACL.InheritanceFlags
PropagationFlags = $ACL.PropagationFlags}
$ACLList+=$OutInfo
}
}
$ACLList|select FolderPath,FileSystemRights,IdentityReference,AccessControlType,IsInherited,InheritanceFlags,PropagationFlags|export-csv C:\powershell\MLCPermissions.csv -NoTypeInformation

The script for the greater part works but I have been getting errors on big file systems like this

You cannot call a method on a null-valued expression.
At Z:\Scripts Library\AD\ExportFolderPermissions\ExpoertFolderPermissions.ps1:18 char:58

  •   IdentityReference = $ACL.IdentityReference.ToString <<<< ()
    
    • CategoryInfo : InvalidOperation: (ToString:String) , RuntimeException
    • FullyQualifiedErrorId : InvokeMethodOnNull

From my investigation and limited knowledge is this caused by a character that is not accepted by powershell.

Any help to get around this would be much appreciated.

Well, it’s telling you that $ACL.IdentityReference is null, so you can’t run the ToString() method on it. You could check for that specifically with something like this.

if ($ACL.IdentityReference -eq $null)
{
IdentityReference = “null”
} else {
IdentityReference = $ACL.IdentityReference.ToString()
}

but you would have to do this outside that object creation I believe.

Also it could just be your ACL is malformed because of something weird in the path, so change

$ACLs = get-acl $Folder.fullname
over to
$ACLs = Get-Acl -LiteralPath $Folder.fullname
to see if literal paths help.

Depending on the powershell version requirements, you might gain some speed changing

$Folders = get-childitem -Recurse $RootPath | where {$_.psiscontainer -eq $true}
to
$Folders = get-childitem -Recurse -Path $RootPath -Directory
This does the same thing but skips the post where processing.

Are you by any chance using PowerShell v2? Before version 3 (at least I think that’s when it was changed), a foreach() {} block will iterate once over a null value, so if you have a folder with an empty DACL, the $ACL variable will be null. To see this in action, try this:

foreach ($Blah in $null) {
    'Blah'
}

In PowerShell v2, you should see Blah printed once, but in v3, you shouldn’t. PowerShell doesn’t mind you accessing non-existent properties, but it doesn’t like you trying to invoke methods on null objects, so you get an error message when ToString() is called.

One way to fix it in your code is to change $ACL.IdentityReference.ToString(). to $ACL.IdentityReference -as [string]. That will give a line in your CSV with empty elements (which can be helpful to point out empty DACLs).

If you’re calling this over a large folder structure, you might want to consider changing the code from collecting the different bits of information completely before moving on to the next step to streaming over the data. Maybe something like this:

Get-ChildItem $RootPath -Recurse | 
    where { $_.PSIsContainer } | 
    Get-Acl | 
    Select-Object @{Name='Path'; Expression={Convert-Path $_.Path}} -ExpandProperty Access |
    Export-Csv $OutFile -NoTypeInformation

That should work, except that Get-Acl has a quirk where its errors will terminate the pipeline, so the whole thing will blow up if it comes across a folder you don’t have access to. You can fix it by wrapping it in a ForEach-Object scriptblock like this:

Get-ChildItem $RootPath -Recurse | 
    where { $_.PSIsContainer } | 
    ForEach-Object {
        $_ | Get-Acl | select @{Name='Path'; Expression={Convert-Path $_.Path}} -ExpandProperty Access
    } |
    Export-Csv $OutFile -NoTypeInformation

Try that out and see if it works (at least on folders you can access).

First off apologies as I am struggling to work out exactly where this code should be entered.

Do I enter that code in after $folders = and then pass that to the For Each loop?

Not a problem. You just need to define $OutFile and $RootPath, then call the snippet I had above. This should be all you need:

$OutFile = "C:\powershell\MLCPermissions.csv"
Del $OutFile
$RootPath = "E:\Data\GROUPS"

Get-ChildItem $RootPath -Recurse | 
    where { $_.PSIsContainer } | 
    ForEach-Object {
        $_ | Get-Acl | select @{Name='Path'; Expression={Convert-Path $_.Path}} -ExpandProperty Access
    } |
    Export-Csv $OutFile -NoTypeInformation

Rohn

Thankyou very much. I have much to learn and that script seems to work a treat. I have it running over a large file system now and just checked the output and it seems spot on.

I can follow the logic but get lost at this point here as select @{Name=‘Path’; Expression={Convert-Path $_.Path}}

I appreciate your time very much.

That’s called a calculated property, and it’s a feature of Select-Object (for more info, see the help topic for Select-Object). You can try running the command with Path instead of @{Name=‘Path’; Expression={Convert-Path $_.Path}}, but you’ll see that the ‘Path’ column in the CSV has extra info that PowerShell cares about, but you probably don’t. Using the calculated property allows you to create a property named ‘Path’ (because that’s the Name we gave it in the hashtable) with a value that’s what you get if you run the original ‘Path’ property through the ‘Convert-Path’ cmdlet (because that’s the Expression we gave it in the hashtable).

Thankyou for your replies Rohn and I have an understanding now. I have just tried to limit the results by where {$_.InheritanceFlags -eq $false} but can not get it to output. The code you provided worked a treat over the weekend but produced a csv that was too large to open.

You’re welcome.

Are you trying to filter on IsInherited instead of InheritanceFlags? IsInherited specifies whether or not the ACE was inherited from the parent; the InheritanceFlags specify what child objects the ACE will apply to. If you are looking for IsInherited, try this:

$OutFile = "C:\powershell\MLCPermissions.csv"
Del $OutFile
$RootPath = "E:\Data\GROUPS"

Get-ChildItem $RootPath -Recurse | 
    where { $_.PSIsContainer } | 
    ForEach-Object {
        $_ | Get-Acl | select @{Name='Path'; Expression={Convert-Path $_.Path}} -ExpandProperty Access | where { $_.IsInherited -eq $false }
    } |
    Export-Csv $OutFile -NoTypeInformation

If you’re interested in getting only explicit ACEs, you might also try the code below instead (it should be faster since the ACE filtering is happening internally in the .NET method). Unfortunately, it resembles C# more than PowerShell, so it’s probably going to be confusing. The built-in PowerShell ACL cmdlets are just very thin wrappers for the underlying .NET security classes. You can do a lot with them, but getting the most out of them sometimes requires making weird code like this:

$OutFile = "C:\powershell\MLCPermissions.csv"
Del $OutFile
$RootPath = "E:\Data\GROUPS"

Get-ChildItem $RootPath -Recurse | 
    where { $_.PSIsContainer } | 
    ForEach-Object {
        $SD = $_ | Get-Acl
        $SD.GetAccessRules(
            $true,  # Include explicit ACEs
            $false, # Don't include inherited ACEs
            [System.Security.Principal.NTAccount]  # Translate IdentityReference to NTAccount
        ) | Add-Member -MemberType NoteProperty -Name Path -Value $_.FullName -PassThru
    } |
    Export-Csv $OutFile -NoTypeInformation

Rohn you are a gentleman and thank you for your help. You have got me out of a pickle.