Get-ACL script with null Value expression

This topic contains 9 replies, has 3 voices, and was last updated by Profile photo of Michael Gibson Michael Gibson 7 months, 2 weeks ago.

Viewing 10 posts - 1 through 10 (of 10 total)
  • Author
    Posts
  • #35188
    Profile photo of Michael Gibson
    Michael Gibson
    Participant

    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 : InvokeMethodOnNullFrom 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.

    #35189
    Profile photo of Raymond Slieff
    Raymond Slieff
    Participant

    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.

    #35190
    Profile photo of Rohn Edwards
    Rohn Edwards
    Participant

    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).

    #35191
    Profile photo of Michael Gibson
    Michael Gibson
    Participant

    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?

    #35194
    Profile photo of Rohn Edwards
    Rohn Edwards
    Participant

    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
    
    #35197
    Profile photo of Michael Gibson
    Michael Gibson
    Participant

    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.

    #35198
    Profile photo of Rohn Edwards
    Rohn Edwards
    Participant

    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).

    #35248
    Profile photo of Michael Gibson
    Michael Gibson
    Participant

    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.

    #35251
    Profile photo of Rohn Edwards
    Rohn Edwards
    Participant

    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
    
    #35254
    Profile photo of Michael Gibson
    Michael Gibson
    Participant

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

Viewing 10 posts - 1 through 10 (of 10 total)

You must be logged in to reply to this topic.