1

I have this awesome script I use to generate a list of folders with their assigned security groups and each user in each group.

When I run it, I type .\getfolderacls.ps1 -verbose | export-csv c:\temp\filename.csv -notypeinformation.

That works perfectly, but I'd like to hardcode the | export-csv... part so that I can just run it without the arguments (or are they parameters?).

I tried simply appending | export-csv c:\temp\test.csv -notypeinformation to the bottom of the script, but that throws the error An empty pipe element is not allowed.

Script:

    [CmdletBinding()]
Param (
    [ValidateScript({Test-Path $_ -PathType Container})]
    [Parameter(Mandatory=$false)]
    [string]$Path       
)
Write-Verbose "$(Get-Date): Script begins!"
Write-Verbose "Getting domain name..."
$Domain = (Get-ADDomain).NetBIOSName

Write-Verbose "Getting ACLs for folder $Path"

Write-Verbose "...and all sub-folders"
Write-Verbose "Gathering all folder names, this could take a long time on bigger folder trees..."
$Folders = Get-ChildItem -Path I:\foldername -Directory -Recurse -Depth 2

Write-Verbose "Gathering ACL's for $($Folders.Count) folders..."
ForEach ($Folder in $Folders)
{   Write-Verbose "Working on $($Folder.FullName)..."
    $ACLs = Get-Acl $Folder.FullName | ForEach-Object { $_.Access | where{$_.IdentityReference -ne "BUILTIN\Administrators" -and $_.IdentityReference -ne "BUILTIN\Users"  }}
    ForEach ($ACL in $ACLs)
    {   If ($ACL.IdentityReference -match "\\")
        {   If ($ACL.IdentityReference.Value.Split("\")[0].ToUpper() -eq $Domain.ToUpper())
            {   $Name = $ACL.IdentityReference.Value.Split("\")[1]
                If ((Get-ADObject -Filter 'SamAccountName -eq $Name').ObjectClass -eq "group")
                {   ForEach ($User in (Get-ADGroupMember $Name -Recursive | Select -ExpandProperty Name))
                    {   $Result = New-Object PSObject -Property @{
                            Path = $Folder.Fullname
                            Group = $Name
                            User = $User
                            FileSystemRights = $ACL.FileSystemRights
                                                                               }
                        $Result | Select Path,Group,User,FileSystemRights
                    }
                }
                Else
                {    $Result = New-Object PSObject -Property @{
                        Path = $Folder.Fullname
                        Group = ""
                        User = Get-ADUser $Name | Select -ExpandProperty Name
                        FileSystemRights = $ACL.FileSystemRights
                                            }
                    $Result | Select Path,Group,User,FileSystemRights
                }
            }
            Else
            {   $Result = New-Object PSObject -Property @{
                    Path = $Folder.Fullname
                    Group = ""
                    User = $ACL.IdentityReference.Value
                    FileSystemRights = $ACL.FileSystemRights
                                    }
                $Result | Select Path,Group,User,FileSystemRights
            }
        }
    }
}
Write-Verbose "$(Get-Date): Script completed!"
  • these repeated lines `$Result | Select Path,Group,User,FileSystemRights` seem to be your output. add a line just above them that outputs to the CSV you want. something like `$Result | Export-Csv -Path "$env:temp\Report.csv" -Append". ///// as an aside, why are you using `Select-Object` there? it looks like you are simply ensuring the sequence of the props. if so, you can simplify [and slightly speed up] the process by replacing `New-Object PSObject -Property @{` with `[PSCustomObject]@{`. that will KEEP your definition sequence. [*grin*] – Lee_Dailey Feb 20 '19 at 20:37
  • Thanks for the response, Lee_Dailey. When I replaced New-Object PSObject -Property @{ with [PSCustomObject]@{, I got the error: "Get-ADGroupMember : The size limit for this request was exceeded At C:\Temp\Get_folder_acls_Test.ps1:26 char:40 + ... { ForEach ($User in (Get-ADGroupMember $Name -Recursive | Selec ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (Domain Users:ADGroup) [Get-ADGroupMember], ADException – Daniel Gower Feb 21 '19 at 13:35
  • since the two commands are nearly identical in result ... that means you accidentally changed something else. [*grin*] the only diff in the result is that the items will end up in the declared sequence & you no longer require the `Select-Object` to specify a property order in your output object. – Lee_Dailey Feb 22 '19 at 03:37

1 Answers1

0

Your script's output is being produced inside a foreach loop - ForEach ($Folder in $Folders) ... (as opposed to via the ForEach-Object cmdlet, which, unfortunately, is also aliased to foreach).

In order to send a foreach loop's output to the pipeline, you can wrap it in a script block ({ ... }) and invoke it with the dot-sourcing operator (.). Alternatively, use the call operator (&), in which case the loop runs in a child scope.

Here are simplified examples:

# FAILS, because you can't use a foreach *loop* directly in a pipeline.
PS> foreach ($i in 1..2) { "[$i]" } | Write-Output
# ...
An empty pipe element is not allowed. 
# ...


# OK - wrap the loop in a script block and invoke it with .
PS> . { foreach ($i in 1..2) { "[$i]" } } | Write-Output
[1]
[2]

Note: I'm using Write-Output as an example of a cmdlet you can pipe to, solely for the purpose of this demonstration. What's required in your case is to wrap your foreach loop in . { ... } and to follow it with | Export-Csv ... instead of Write-Output.

Using . { ... } or & { ... } sends the output generated inside the loop to the pipeline as it is being produced, one by one, aka in streaming fashion - as (typically) happens with output produced by a cmdlet.


An alternative is to use $(...), the subexpression operator (or @(...), the array-subexpression operator, which works the same in this scenario), in which case the loop output is collected in memory as a whole, up front, before it is sent through the pipeline - this is typically faster, but requires more memory:

# OK - call via $(...), with output collected up front.
PS> $(foreach ($i in 1..2) { "[$i]" }) | Write-Output
[1]
[2]

To spell the . { ... } solution out in the context of your code - the added lines are marked with # !!! comments (also note the potential to improve your code based on Lee_Dailey's comment on the question):

[CmdletBinding()]
Param (
    [ValidateScript({Test-Path $_ -PathType Container})]
    [Parameter(Mandatory=$false)]
    [string]$Path       
)
Write-Verbose "$(Get-Date): Script begins!"
Write-Verbose "Getting domain name..."
$Domain = (Get-ADDomain).NetBIOSName

Write-Verbose "Getting ACLs for folder $Path"

Write-Verbose "...and all sub-folders"
Write-Verbose "Gathering all folder names, this could take a long time on bigger folder trees..."
$Folders = Get-ChildItem -Path I:\foldername -Directory -Recurse -Depth 2

Write-Verbose "Gathering ACL's for $($Folders.Count) folders..."
. { # !!!
  ForEach ($Folder in $Folders) 
  {   Write-Verbose "Working on $($Folder.FullName)..."
      $ACLs = Get-Acl $Folder.FullName | ForEach-Object { $_.Access | where{$_.IdentityReference -ne "BUILTIN\Administrators" -and $_.IdentityReference -ne "BUILTIN\Users"  }}
      ForEach ($ACL in $ACLs)
      {   If ($ACL.IdentityReference -match "\\")
          {   If ($ACL.IdentityReference.Value.Split("\")[0].ToUpper() -eq $Domain.ToUpper())
              {   $Name = $ACL.IdentityReference.Value.Split("\")[1]
                  If ((Get-ADObject -Filter 'SamAccountName -eq $Name').ObjectClass -eq "group")
                  {   ForEach ($User in (Get-ADGroupMember $Name -Recursive | Select -ExpandProperty Name))
                      {   $Result = New-Object PSObject -Property @{
                              Path = $Folder.Fullname
                              Group = $Name
                              User = $User
                              FileSystemRights = $ACL.FileSystemRights
                                                                                }
                          $Result | Select Path,Group,User,FileSystemRights
                      }
                  }
                  Else
                  {    $Result = New-Object PSObject -Property @{
                          Path = $Folder.Fullname
                          Group = ""
                          User = Get-ADUser $Name | Select -ExpandProperty Name
                          FileSystemRights = $ACL.FileSystemRights
                                              }
                      $Result | Select Path,Group,User,FileSystemRights
                  }
              }
              Else
              {   $Result = New-Object PSObject -Property @{
                      Path = $Folder.Fullname
                      Group = ""
                      User = $ACL.IdentityReference.Value
                      FileSystemRights = $ACL.FileSystemRights
                                      }
                  $Result | Select Path,Group,User,FileSystemRights
              }
          }
      }
  }
} | Export-Csv c:\temp\test.csv -notypeinformation  # !!!
Write-Verbose "$(Get-Date): Script completed!"
mklement0
  • 382,024
  • 64
  • 607
  • 775