PowerShell - Check if user has folder access through a security group - powershell

I was able to check through this code, but it is showing the permissions of all subfolders, I would like it to show only the current path, how to adjust this code in this way?
Import-Module ActiveDirectory
$User = "domain\user"
$UserOhneDomain = "user"
$Path = "folderpath"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups)
{
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User)
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("domain\" + $GroupArrayItem))
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\user\Desktop\PermissionCheck.txt

Try to remove the "-Recurse" parameter in the following line:
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
I don't have a AD to check now, but i think that is the problem here.

Related

Powershell - cheking var is giving me the right results but Export-CSV gives numbers

what I want is that the $report_groups val gives me in Export-CSV the output that i get in Terminal. But i cant figure it why does he gives me Numbers.
$get_AD_Groups = Get-ADGroup -Filter '*' | Select-Object Name
$report_groups = New-Object -TypeName System.Collections.ArrayList
foreach ($item in $get_AD_Groups) {
$get_users = $item.Name | Get-ADGroupMember | Select-Object Name
$disabled_user = 0
foreach ($user in $get_users) {
$status = $user.Name | Get-ADUser -ErrorAction SilentlyContinue
if(($status.ObjectClass -eq 'user') -and ($status.Enabled -ne 'True')) {
$disabled_user++
}
}
if ($get_users.Count -eq $disabled_user) {
$report_groups.Add($item.Name)
}
}
$report_groups | Export-Csv -Path "..\report.csv" -NoTypeInformation -Force -Delimiter ";"
Now when i run $report_groups in Terminal i get the list of the AD Group BUT as soon i do the Export-CSV this is what i get:
So thanks again to Lee_Dailey for helping me on this.
Changes done.
$report_groups = #()
if ($get_users.Count -eq $disabled_user) {
$fill = [PSCustomObject]#{
AD_GROUP = $item.Name
}
$report_groups += $fill
}

Multiple outputs to same csv file in Powershell

I'm trying to export my firewall rules that are specified in multiple group policy objects and would like to include the Name of the GPO in the exported file. So I tried to take my string variable $Policy and jam it into the csv file each time a new gpo is parsed but all I'm getting is the gpo name and not the fields from Get-NetFirewallRule. Of course if I remove the $policy | Out-File $env:temp\gpos.csv -Append -Force line then I get all of the fields from Get-NetFirewallRule - but they're all on a large csv file and I can't determine their source GPO.
foreach ($policy in $PolicyObjects) {
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
$policy | Out-File $env:temp\gpos.csv -Append -Force
Get-NetFirewallRule -GPOSession $GPO |
Select Name,
DisplayName,
DisplayGroup,
#{Name='Protocol';Expression={($PSItem | Get-NetFirewallPortFilter).Protocol}},
#{Name='LocalPort';Expression={($PSItem | Get-NetFirewallPortFilter).LocalPort}},
#{Name='RemotePort';Expression={($PSItem | Get-NetFirewallPortFilter).RemotePort}},
#{Name='RemoteAddress';Expression={($PSItem | Get-NetFirewallAddressFilter).RemoteAddress}},
Enabled,
Profile,
Direction,
Action | Export-CSV $env:temp\gpos.csv -Append -Force
}
Start-Process notepad $env:temp\gpos.csv
Seems your $PolicyObjects is a list of your group policy displaynames. I'd tighten up your code in one of the following manners.
$PolicyObjects | ForEach-Object {
$GPO = Open-NetGPO -PolicyStore "contoso.com\$_"
foreach($rule in Get-NetFirewallRule -GPOSession $GPO)
{
$portfilter = $rule | Get-NetFirewallPortFilter
$addressfilter = $rule | Get-NetFirewallAddressFilter
[PSCustomObject]#{
GPOName = $_
RuleName = $rule.name
DisplayName = $rule.displayname
DisplayGroup = $rule.displaygroup
Protocol = $portfilter.Protocol
LocalPort = $portfilter.LocalPort
RemotePort = $portfilter.RemotePort
RemoteAddress = $addressfilter.RemoteAddress
Enabled = $rule.enabled
Profile = $rule.profile
Direction = $rule.direction
Action = $rule.action
}
}
} | Export-CSV $env:temp\gpos.csv -Force
Start-Process notepad $env:temp\gpos.csv
or
$csvdata = foreach($policy in $PolicyObjects)
{
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
foreach($rule in Get-NetFirewallRule -GPOSession $GPO)
{
$portfilter = $rule | Get-NetFirewallPortFilter
$addressfilter = $rule | Get-NetFirewallAddressFilter
[PSCustomObject]#{
GPOName = $policy
RuleName = $rule.name
DisplayName = $rule.displayname
DisplayGroup = $rule.displaygroup
Protocol = $portfilter.Protocol
LocalPort = $portfilter.LocalPort
RemotePort = $portfilter.RemotePort
RemoteAddress = $addressfilter.RemoteAddress
Enabled = $rule.enabled
Profile = $rule.profile
Direction = $rule.direction
Action = $rule.action
}
}
}
$csvdata | Export-CSV $env:temp\gpos.csv -Force
Start-Process notepad $env:temp\gpos.csv
In the first one we change the outer loop to a Foreach-Object to take advantage of the pipeline and piping straight to Export-Csv.
In the second we capture all the output then export.
In both we limit the execution time by limiting the opening/writing to file to one time, limit the portfilter calls to one per rule instead of 3, and we use the [PSCustomObject] type accelerator to construct our final object instead of piping to Select-Object with calculated expressions. Both should achieve your desired result if I understood correctly.
Does this make a difference?
$csv=foreach ($policy in $PolicyObjects) {
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
$policy | Out-File $env:temp\gpos.csv -Append -Force
Get-NetFirewallRule -GPOSession $GPO |
Select Name,
DisplayName,
DisplayGroup,
#{Name='Protocol';Expression={($PSItem | Get-NetFirewallPortFilter).Protocol}},
#{Name='LocalPort';Expression={($PSItem | Get-NetFirewallPortFilter).LocalPort}},
#{Name='RemotePort';Expression={($PSItem | Get-NetFirewallPortFilter).RemotePort}},
#{Name='RemoteAddress';Expression={($PSItem | Get-NetFirewallAddressFilter).RemoteAddress}},
Enabled,
Profile,
Direction,
Action
}
$csv | Export-CSV $env:temp\gpos.csv -Append -Force
Start-Process notepad $env:temp\gpos.csv
It's not possible to append data with Export-Csv when the target file does not match the structure.
In your case, Export-Csv tried to append values from column $policy, which doesn't exist. Therefore it added $null.
For a valid CSV file you have to combine everything into one object before using Export-Csv.
Try the below approach, which should work much faster, since it does not use Get-NetFirewallAddressFilter several times for the same item, but only once for per GPO-Session.
Unfortunately I can't test it with a GPO-Session, but since the Parameter -GPOSession exists for Get-NetFirewallPortFilter and Get-NetFirewallAddressFilter as well, it may work.
If you want to test it just locally, you just need to remove the outer foreach-loop and all GPOSession related commands and parameters. Open-NetGPO and -GPOSession
Comparison between my approach and retrieving the Port- and Address-Filter for each rule separately (even when only once per rule) when building a report for local firewall rules:
My approach: < 2 seconds
Other : > 3 minutes (using Get-NetFirwall*Filter for each rule once)
The code requires an elevated console!
#requires -RunAsAdministrator
$ruleReport = [System.Collections.Generic.List[psobject]]::new()
foreach ($policy in $PolicyObjects) {
<#
if you want to append to file after each policy, place the list here instead of above
$ruleReport = [System.Collections.Generic.List[psobject]]::new()
#>
$GPO = Open-NetGPO -PolicyStore "contoso.com\$policy"
$rules = Get-NetFirewallRule -GPOSession $GPO
# retrieving filters once in advance speeds up the execution extremely! (Requires elevated session to get all filters)
# local execution time with that approach reduces the execution time from >3 minutes TO <2 seconds
$portFilters = Get-NetFirewallPortFilter -All -GPOSession $GPO
$addressFilters = Get-NetFirewallAddressFilter -All -GPOSession $GPO
# build dictionary for port filters for rule lookups
$portFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($portFilter in $portFilters) {
$portFilterDict.Add($portFilter.InstanceID, $portFilter)
}
# build dictionary for addresss filters for rule lookups
$addressFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($addressFilter in $addressFilters) {
$addressFilterDict.Add($addressFilter.InstanceID, $addressFilter)
}
foreach ($rule in $rules) {
$ruleInstanceId = $rule.InstanceID
$rulePortFilter = $portFilterDict[$ruleInstanceId]
$ruleAddressFilter = $addressFilterDict[$ruleInstanceId]
# build combined object
$ruleReportItem = [pscustomobject][ordered]#{
Policy = $policy
Name = $rule.Name
DisplayName = $rule.DisplayName
DisplayGroup = $rule.DisplayGroup
Protocol = $rulePortFilter.Protocol
LocalPort = $rulePortFilter.LocalPort
RemotePort = $rulePortFilter.RemotePort
RemoteAddress = $ruleAddressFilter.RemoteAddress
Enabled = $rule.Enabled
Profile = $rule.Profiles
Direction = $rule.Direction
Action = $rule.Action
}
# append to list
$ruleReport.Add($ruleReportItem)
}
<#
if you want to append to file after each policy, place the export here instead of below
$ruleReport | Export-Csv $env:temp\gpos.csv -Append
#>
}
$ruleReport | Export-Csv $env:temp\gpos.csv
Local Firewall Rules
To test the approach just for local Firewall Rules, use this script.
This script adds all available information of each rule and requires an elevated console.
#requires -RunAsAdministrator
$ruleReport = [System.Collections.Generic.List[psobject]]::new()
$rules = Get-NetFirewallRule
$portFilters = Get-NetFirewallPortFilter -All
$addressFilters = Get-NetFirewallAddressFilter -All
$applicationFilters = Get-NetFirewallApplicationFilter -All
$interfaceFilters = Get-NetFirewallInterfaceFilter -All
$interfaceTypeFilters = Get-NetFirewallInterfaceTypeFilter -All
$securityFilters = Get-NetFirewallSecurityFilter -All
$serviceFilters = Get-NetFirewallServiceFilter -All
$portFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $portFilters) {
$portFilterDict.Add($filter.InstanceID, $filter)
}
$addressFilterDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $addressFilters) {
$addressFilterDict.Add($filter.InstanceID, $filter)
}
$applicationFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $applicationFilters) {
$applicationFiltersDict.Add($filter.InstanceID, $filter)
}
$interfaceFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $interfaceFilters) {
$interfaceFiltersDict.Add($filter.InstanceID, $filter)
}
$interfaceTypeFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $interfaceTypeFilters) {
$interfaceTypeFiltersDict.Add($filter.InstanceID, $filter)
}
$securityFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $securityFilters) {
$securityFiltersDict.Add($filter.InstanceID, $filter)
}
$serviceFiltersDict = [System.Collections.Generic.Dictionary[[string], [ciminstance]]]::new()
foreach ($filter in $serviceFilters) {
$serviceFiltersDict.Add($filter.InstanceID, $filter)
}
$cimClassProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties'
$cimClassPropertiesLookup = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
foreach ($cimClassProperty in $cimClassProperties) {
[void] $cimClassPropertiesLookup.Add($cimClassProperty)
}
foreach ($rule in $rules) {
$ruleInstanceId = $rule.InstanceID
$portFilter = $portFilterDict[$ruleInstanceId]
$addressFilter = $addressFilterDict[$ruleInstanceId]
$applicationFilter = $applicationFiltersDict[$ruleInstanceId]
$interfaceFilter = $interfaceFiltersDict[$ruleInstanceId]
$interfaceTypeFilter = $interfaceTypeFiltersDict[$ruleInstanceId]
$securityFilter = $securityFiltersDict[$ruleInstanceId]
$serviceFilter = $serviceFiltersDict[$ruleInstanceId]
$ruleReportItemProps = [ordered]#{
RuleDisplayName = $rule.DisplayName
RuleDescription = $rule.Description
RuleEnabled = $rule.Enabled
RuleProfile = $rule.Profile
RuleAction = $rule.Action
RuleDirection = $rule.Direction
PortFilterProtocol = $rulePortFilter.Protocol
PortFilterLocalPort = $rulePortFilter.LocalPort
PortFilterRemotePort = $rulePortFilter.RemotePort
AddressFilterRemoteAddress = $ruleAddressFilter.RemoteAddress
ApplicationFilterProgram = $applicationFilter.Program
}
foreach ($prop in $rule.psobject.Properties) {
$propName = 'Rule' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName ) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName , $prop.Value)
}
}
foreach ($prop in $portFilter.psobject.Properties) {
$propName = 'PortFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $addressFilter.psobject.Properties) {
$propName = 'AddressFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $applicationFilter.psobject.Properties) {
$propName = 'ApplicationFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $interfaceFilter.psobject.Properties) {
$propName = 'InterfaceFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $interfaceTypeFilter.psobject.Properties) {
$propName = 'InterfaceTypeFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $securityFilter.psobject.Properties) {
$propName = 'SecurityFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
foreach ($prop in $serviceFilter.psobject.Properties) {
$propName = 'ServiceFilter' + $prop.Name
if (-not $ruleReportItemProps.Contains($propName) -and -not $cimClassPropertiesLookup.Contains($prop.Name)) {
$ruleReportItemProps.Add($propName, $prop.Value)
}
}
$ruleReport.Add([pscustomobject]$ruleReportItemProps)
}
$ruleReport | Sort-Object RuleEnabled, RuleDisplayName, RuleProfile | ogv

If clause in Generic List

I have some questions about GenericLists in PowerShell.
The script below prints all access rights on a file share for a specific user with his groups. Now I want to add a new row in my GenericList which shows from where (user/group) the right is inherited.
$User = "Testumgebung\cbruehwiler"
$UserOhneDomain = "cbruehwiler"
$Path = "T:\"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups) {
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
#{name="From" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("TESTUMGEBUNG\" + $GroupArrayItem)) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
The result looks like this:
Item Rights AccessType From
---- ------ ---------- ----
T:\TestFolder FullControl Allow Testumgebung\cbruehwiler
T:\TestFolder Read, Synchronize Allow Testumgebung\cbruehwiler
T:\TestFolder Write, ReadAndExecute, Synchronize Allow Testumgebung\cbruehwiler
The last row now prints only my user. However it should show the user or the group.
You could even merge the two loops into one, like this:
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList) {
# not needed; just for convenience
[string]$id = $Right.IdentityReference
# test if the $Right.IdentityReference corresponds with the user name
if ($id -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
# test if the $Right.IdentityReference without the 'Domain\' part can be found in the list of groups
elseif (($id.Split("\", 2)[-1]) -in $GroupArrayList) {
# set the $User variable to the value of $Right.IdentityReference
$User = "Group: $id"
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}

PowerShell get permissions on a folder for a user with group

I have to print the permissions of a user for a specific folder. Below, I have a working code. However it only scans if the user has been specifically given permissions. Now I also want to check, if a group of which the user is a member of, has permissions on the folder.
I thought about listing all groups in which my user is a MemberOf and then add them into a Generic List. Afterwards, I will execute the following code for each entry of this list.
$User = "testumgebung\cbruehwiler"
$Path = "T:\"
# Generic list object to store output in
$List = New-Object System.Collections.Generic.List[System.Object]
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
# Store all objects in variable
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
# Iterate through every object
foreach ($Item in $FileSystemObjects) {
# Iterate through every individual user right within each object
# Add it to our list if it matchers our $User
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
My list prints the folder name, the different permissions and if it has access or not. I don't really want to change the structure too much.
I found a solution.
Import-Module ActiveDirectory
$User = "Testumgebung\cbruehwiler"
$UserOhneDomain = "cbruehwiler"
$Path = "T:\"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups)
{
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User)
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("TESTUMGEBUNG\" + $GroupArrayItem))
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
This Script checks all rights from a user on a file or share including all of the groups the user is a member of.
You just have to enter the user once with the "DOMAIN\username" variant and once with the "username" variant.
Hope this helps

Powershell How to Modify Script to Hardcode " | export-csv c:\temp\filename.csv -notypeinformation"

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!"
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!"