How to get the -Depth switch to work in a script? - powershell

I'm trying to limit the recursion depth for this script that generates a list of folders, associated security groups, and the members of each group. I'm using PowerShell 5.1.
I've tried adding -Depth 3 on line 18 (as seen below), but I still get all levels. I've tried adding -Depth 3 on the command line when I run the script, but that errors.
This is the command I used to run the script:
./Get_folder_acls_depth_test.ps1 -Path I:\dir_name -Recurse | Export-Csv c:\temp\dir_name.csv
I tried this also, but got an error:
./Get_folder_acls_depth_test.ps1 -Path I:\dir_name -Recurse -Depth 3 | Export-Csv c:\temp\dir_name.csv
[CmdletBinding()]
Param(
[ValidateScript({Test-Path $_ -PathType Container})]
[Parameter(Mandatory=$true)]
[string]$Path,
[switch]$Recurse
)
Write-Verbose "$(Get-Date): Script begins!"
Write-Verbose "Getting domain name..."
$Domain = (Get-ADDomain).NetBIOSName
Write-Verbose "Getting ACLs for folder $Path"
if ($Recurse) {
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 $Path -Directory -Recurse -Depth 3
} else {
$Folders = Get-Item -Path $Path
}
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 }
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
AccessControlType = $ACL.AccessControlType
Inherited = $ACL.IsInherited
}
$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
AccessControlType = $ACL.AccessControlType
Inherited = $ACL.IsInherited
}
$Result | Select Path,Group,User,FileSystemRights
}
} else {
$Result = New-Object PSObject -Property #{
Path = $Folder.Fullname
Group = ""
User = $ACL.IdentityReference.Value
FileSystemRights = $ACL.FileSystemRights
AccessControlType = $ACL.AccessControlType
Inherited = $ACL.IsInherited
}
$Result | Select Path,Group,User,FileSystemRights
}
}
}
}
Write-Verbose "$(Get-Date): Script completed!"
The script works fine for getting all levels, I just would like to limit it to say levels 2-4.

I tested this hacking your script to check that your if/else statement was working correctly and I get correct results:
function Test-Recurse {
Param(
[ValidateScript( {Test-Path $_ -PathType Container})]
[Parameter(Mandatory = $true)]
[string]$Path,
[switch]$Recurse
)
begin {
Write-Verbose "$(Get-Date): Script begins!"
$folders = $null
}
process {
if ($Recurse) {
Write-Output -InputObject "Recurse has been selected"
$folders = Get-ChildItem -Path $Path -Directory -Recurse -Depth 3
}
else {
Write-Output -InputObject "Recurse has NOT been selected"
$folders = Get-ChildItem -Path $Path -Directory
}
}
end {
return $folders.fullName
}
}
PS C:\GitHub\Guyver1wales\PowerShell> Test-Recurse -Path c:\programdata\razer
Recurse has NOT been selected
C:\programdata\razer\Installer
C:\programdata\razer\Razer Central
C:\programdata\razer\RzEndpointPicker
C:\programdata\razer\Services
C:\programdata\razer\ServiceSetup
C:\programdata\razer\Synapse
PS C:\GitHub\Guyver1wales\PowerShell> Test-Recurse -Path
c:\programdata\razer -Recurse
Recurse has been selected
C:\programdata\razer\Installer
C:\programdata\razer\Razer Central
C:\programdata\razer\RzEndpointPicker
C:\programdata\razer\Services
C:\programdata\razer\ServiceSetup
C:\programdata\razer\Synapse
C:\programdata\razer\Installer\Logs
C:\programdata\razer\Razer Central\Icons
C:\programdata\razer\Razer Central\Logs
C:\programdata\razer\Razer Central\Icons\Dark
C:\programdata\razer\Razer Central\Icons\Lifestyle
C:\programdata\razer\Razer Central\Icons\Light
C:\programdata\razer\RzEndpointPicker\Accounts
C:\programdata\razer\Services\Logs
C:\programdata\razer\Synapse\Accounts
C:\programdata\razer\Synapse\CrashReporter
C:\programdata\razer\Synapse\Devices
C:\programdata\razer\Synapse\Logs
C:\programdata\razer\Synapse\Mats
C:\programdata\razer\Synapse\Modules
...
C:\programdata\razer\Synapse\ProductUpdates\Uninstallers
C:\programdata\razer\Synapse\ProductUpdates\Uninstallers\RazerCommonConfig
C:\programdata\razer\Synapse\ProductUpdates\Uninstallers\RazerDeathAdder3500Config
C:\programdata\razer\Synapse\ProductUpdates\Uninstallers\RazerFonts
C:\programdata\razer\Synapse\ProductUpdates\Uninstallers\Razer_Common_Driver
PS C:\GitHub\Guyver1wales\PowerShell>
-Depth starts from 0 so -Depth 3 will display 4 sub-folders:
C:\programdata\razer\Synapse\ProductUpdates\Uninstallers\RazerDeathAdder3500Config
0 = \Synapse
1 = \ProductsUpdates
2 = \Uninstallers
3 = \RazerDeathAdder3500Config

Related

Optimizing VERY Slow PS Script

I have a PS script that is taking CSV files (the largest being about 170MB) and splitting it into multiple smaller CSVs based on the ListGuid value. It is then taking each file and uploading it to a specific path in SharePoint using PnP based on the Web Guid and List Guid. This script it taking forever to run and I am having trouble finding ways to optimize it. Any help would be appreciated. Here is the script:
$PermissionsFile = Get-ChildItem -Path $downloadFilePath -Filter *.csv
foreach ($file in $PermissionsFile) {
$SiteCollectionReport = Import-Csv -Path "$downloadFilePath/$($file.Name)"
$filteredListTestFile = $SiteCollectionReport | Where-Object {$_.Type -eq "List"}
$groupedListFile = $filteredListTestFile | Select-Object Url, ListGuid -Unique
$subWebsConnection = Connect-SharePoint -WebUrl $SiteCollectionReport.Url[0] -CheckForAppCredentials
$subWebs = Get-PnPSubWebs -Recurse -IncludeRootWeb -Connection $subWebsConnection | Select-Object Url, Id
$permissionsSiteConnection = Connect-SharePoint -WebUrl "https://company.sharepoint.com/sites/edmsc/Internal" -CheckForAppCredentials
foreach ($guid in $groupedListFile) {
$webGuid
$listGuid = $guid.ListGuid
$SiteCollectionReport | Where-Object {$_.ListGuid -like $listGuid -and $_.Type -eq "List"} | Export-Csv -Path "Path\Permissions $($listGuid).csv" -NoTypeInformation
$url = $guid.Url
$siteCollectionName = $url.Split("/")[4]
if ($url.Contains(" ")) {
$url = $url.Replace(" ","%20")
}
$split = $url.substring(0, $url.LastIndexOf("/"))
if ($split.Contains("Lists")) {
$split = $split -split "Lists"
}
foreach ($web in $subWebs) {
if ($web.Url -eq $split) {
$webGuid = $web.Id
#Write-Host "Adding permissions reports for $split"
#Write-Host "List Guid $listGuid"
Write-Host "Web Guid $webGuid"
}
}
$fieldValues = #{"ObjectType"="List/ListItem"; "WebGuid"=$webGuid; "ListGuid"=$listGuid}
#$permissionsSiteWeb = Get-PnPWeb -Connection $permissionsSiteConnection
Add-PnPFile -Path "Path\Permissions $($listGuid).csv" -Folder "SiteCollectionPermissions/$siteCollectionName/$webGuid" -Values $fieldValues -Connection $permissionsSiteConnection
}
Write-Host "Deleting Permissions Files..."
Get-ChildItem -Path "Path" -Include *.csv* -File -Recurse | ForEach-Object { $_.Delete()}
}

Powershell Script is printing out duplicate entries of the same path

My objective is to write a powershell script that will recursively check a file server for any directories that are "x" (insert days) old or older.
I ran into a few issues initially, and I think I got most of it worked out. One of the issues I ran into was with the path limitation of 248 characters. I found a custom function that I am implementing in my code to bypass this limitation.
The end result is I would like to output the path and LastAccessTime of the folder and export the information into an easy to read csv file.
Currently everything is working properly, but for some reason I get some paths output several times (duplicates, triples, even 4 times). I just want it output once for each directory and subdirectory.
I'd appreciate any guidance I can get. Thanks in advance.
Here's my code
#Add the import and snapin in order to perform AD functions
Add-PSSnapin Quest.ActiveRoles.ADManagement -ea SilentlyContinue
Import-Module ActiveDirectory
#Clear Screen
CLS
Function Get-FolderItem
{
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin
{
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/S","/NJH","/BYTES","/FP","/NC","/NFL","/TS","/XJ","/R:0","/W:0"))
If ($PSBoundParameters['MaxAge'])
{
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge'])
{
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process
{
ForEach ($item in $Path)
{
Try
{
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop))
{
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile'])
{
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
}
Else
{
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | ForEach {
Try
{
If ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)")
{
$object = New-Object PSObject -Property #{
ParentFolder = $matches.fullname -replace '(.*\\).*','$1'
FullName = $matches.FullName
Name = $matches.fullname -replace '.*\\(.*)','$1'
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
}
Else
{
Write-Verbose ("Not matched: {0}" -f $_)
}
}
Catch
{
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
Catch
{
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
Function ExportFolders
{
#================ Global Variables ================
#Path to folders
$Dir = "\\myFileServer\somedir\blah"
#Get all folders
$ParentDir = Get-ChildItem $Dir | Where-Object {$_.PSIsContainer -eq $True}
#Export file to our destination
$ExportedFile = "c:\temp\dirFolders.csv"
#Duration in Days+ the file hasn't triggered "LastAccessTime"
$duration = 800
$cutOffDate = (Get-Date).AddDays(-$duration)
#Used to hold our information
$results = #()
#=============== Done with Variables ===============
ForEach ($SubDir in $ParentDir)
{
$FolderPath = $SubDir.FullName
$folders = Get-ChildItem -Recurse $FolderPath -force -directory| Where-Object { ($_.LastAccessTimeUtc -le $cutOffDate)} | Select-Object FullName, LastAccessTime
ForEach ($folder in $folders)
{
$folderPath = $folder.fullname
$fixedFolderPaths = ($folderPath | Get-FolderItem).fullname
ForEach ($fixedFolderPath in $fixedFolderPaths)
{
#$fixedFolderPath
$getLastAccessTime = $(Get-Item $fixedFolderPath -force).lastaccesstime
#$getLastAccessTime
$details = #{ "Folder Path" = $fixedFolderPath; "LastAccessTime" = $getLastAccessTime}
$results += New-Object PSObject -Property $details
$results
}
}
}
}
ExportFolders
I updated my code a bit and simplified it. Here is the new code.
#Add the import and snapin in order to perform AD functions
Add-PSSnapin Quest.ActiveRoles.ADManagement -ea SilentlyContinue
Import-Module ActiveDirectory
#Clear Screen
CLS
Function ExportFolders
{
#================ Global Variables ================
#Path to user profiles in Barrington
$Dir = "\\myFileServer\somedir\blah"
#Get all user folders
$ParentDir = Get-ChildItem $Dir | Where-Object {$_.PSIsContainer -eq $True} | where {$_.GetFileSystemInfos().Count -eq 0 -or $_.GetFileSystemInfos().Count -gt 0}
#Export file to our destination
$ExportedFile = "c:\temp\dirFolders.csv"
#Duration in Days+ the file hasn't triggered "LastAccessTime"
$duration = 1
$cutOffDate = (Get-Date).AddDays(-$duration)
#Used to hold our information
$results = #()
$details = $null
#=============== Done with Variables ===============
ForEach ($SubDir in $ParentDir)
{
$FolderName = $SubDir.FullName
$FolderInfo = $(Get-Item $FolderName -force) | Select-Object FullName, LastAccessTime #| ft -HideTableHeaders
$FolderLeafs = gci -Recurse $FolderName -force -directory | Where-Object {$_.PSIsContainer -eq $True} | where {$_.GetFileSystemInfos().Count -eq 0 -or $_.GetFileSystemInfos().Count -gt 0} | Select-Object FullName, LastAccessTime #| ft -HideTableHeaders
$details = #{ "LastAccessTime" = $FolderInfo.LastAccessTime; "Folder Path" = $FolderInfo.FullName}
$results += New-Object PSObject -Property $details
ForEach ($FolderLeaf in $FolderLeafs.fullname)
{
$details = #{ "LastAccessTime" = $(Get-Item $FolderLeaf -force).LastAccessTime; "Folder Path" = $FolderLeaf}
$results += New-Object PSObject -Property $details
}
$results
}
}
ExportFolders
The FolderInfo variable is sometimes printing out multiple times, but the FolderLeaf variable is printing out once from what I can see. The problem is if I move or remove the results variable from usnder the details that print out the folderInfo, then the Parent directories don't get printed out. Only all the subdirs are shown. Also some directories are empty and don't get printed out, and I want all directories printed out including empty ones.
The updated code seems to print all directories fine, but as I mentioned I am still getting some duplicate $FolderInfo variables.
I think I have to put in a condition or something to check if it has already been processed, but I'm not sure which condition I would use to do that, so that it wouldn't print out multiple times.
In your ExportFolders you Get-ChildItem -Recurse and then loop over all of the subfolders calling Get-FolderItem. Then in Get-FolderItem you provide Robocopy with the /S flag in $params.AddRange(#("/L", "/S", "/NJH", "/BYTES", "/FP", "/NC", "/NFL", "/TS", "/XJ", "/R:0", "/W:0")) The /S flag meaning copy Subdirectories, but not empty ones. So you are recursing again. Likely you just need to remove the /S flag, so that you are doing all of your recursion in ExportFolders.
In response to the edit:
Your $results is inside of the loop. So you will have a n duplicates for the first $subdir then n-1 duplicates for the second and so forth.
ForEach ($SubDir in $ParentDir) {
#skipped code
ForEach ($FolderLeaf in $FolderLeafs.fullname) {
#skipped code
}
$results
}
should be
ForEach ($SubDir in $ParentDir) {
#skipped code
ForEach ($FolderLeaf in $FolderLeafs.fullname) {
#skipped code
}
}
$results

How can I get all the ad groups and users into a Excel worksheet

The problem with this code is that for each folder, I only get 1 group or 1 user depending on the folder (mostly the last one), while I want to get all the security groups, and members of the folder, for each folder in the drive.
$title = "ADPermissions.csv"
$title2 = "ADPermissions2.csv"
$ss =$Selection1 -replace '[\W]', ''
$subtitle = "HardDrive"+ $ss
$exclude = #("BUILTIN|NT AUTHORITY|EVERYONE|CREATOR OWNER|NT AUTHORITY\SYSTEM|SYSTEM")
$OutFile = ($Selection2+"\"+$subtitle+$title)
Write-Host = $OutFile
if(Test-Path $OutFile -PathType Leaf)
{
$result3 = [System.Windows.MessageBox]::Show("The file already exists in the selected path`n"+"Do you want to delete it and proceed ?",'File already exists','YesNoCancel','Exclamation')
if ($result3 = "Yes")
{
Remove-Item $OutFile
$Header = "Folder Path,IdentityReference,names"
$RootPath = $Selection
$Folders = dir $RootPath | where {$_.psiscontainer -eq $true}
#To the point:
try {
foreach ($Folder in $Folders){$ACLs = get-acl $Folder.fullname | ForEach-Object { $_.Access } | where {$_.identityreference -notmatch $exclude}
Foreach ($ACL in $ACLs){
$strAcls = $ACL.IdentityReference.ToString()
$strUsers=#()
$strNames=$strAcls.Remove(0,12)
$user = $(try {Get-ADUser $strNames} catch {$null})
if ($strNames -ne $null -and $user -eq $null) {
$A += Get-ADGroupMember -identity $strNames -Recursive | Get-ADUser -Property DisplayName | Select Name | Sort-Object Name
} else {
}
foreach ($env:USERNAME in $A){
$strUsers +=$env:USERNAME
}
$OutInfo = $Folder.fullname + "," + $ACL.IdentityReference + $strUsers
}
Add-Content -Value $OutInfo -Path $OutFile | sort-Object
}
}catch [System.IO.IOException] {
}
}
I:\Dropbox,GESCOEUROPE\GR_G-FCASB-INT-ALL#{Name=CAPPUCCILLI FEDERICO}
#{Name=De Fruyt Frederik}
I:\General,GESCOEUROPE\GR_G-FCASB-INT-ADMIN#{Name=CAPPUCCILLI
FEDERICO} #{Name=De Fruyt Frederik#{Name=VANDEWALLE MARIA}
#{Name=VANSTEELANDT LUCRECE}
I:\ICT,GESCOEUROPE\GR_G-FCASB-INT-ADMIN#{Name=CAPPUCCILLI FEDERICO}
#{Name=De Fruyt Frederik} #{Name=FREDERIK DE FRUYT (ADM)}
#{Name=GAILITE ZANETE} #{Name=Geldhof Francine} #{Name=GOEMAERE
GWENNY}
I:\PaymentFollow-Up,GESCOEUROPE\GR_G-FCASB-INT-ALL#{Name=CAPPUCCILLI
FEDERICO}
this is the output i get, as you see for each folder I have only 1
group, But thats incorrect because some folders have more than 1 group

How do I include the base folder in the list of sub-folders using get-childitem?

I would like to include the base folder in the list of sub-directories.
If I use Get-ChildItem and search for folders:
$startFolder = "C:\Scripts"
Get-ChildItem $startfolder -recurse |
Where-Object {$_.PSIsContainer -eq $True} |
Select FullName
I get a list of the sub-folders like this:
C:\Scripts\folder1
C:\Scripts\folder2
C:\Scripts\folder2\folderA
I would like to see:
C:\Scripts <-- include the starting folder
C:\Scripts\folder1
C:\Scripts\folder2
C:\Scripts\folder2\folderA
I saw an example on technet like this:
$startFolder = "C:\Scripts"
$colItems = (Get-ChildItem $startFolder | Measure-Object -property length -sum)
"$startFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB"
$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)
foreach ($i in $colItems)
{
$subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
}
They break it into two pieces. Process the starting folder, then process the sub-folders. Is that the only way or can the starting folder be included in one command?
It seems like such a simple thing...
One solution:
$startFolder = "C:\Scripts"
$(Get-Item $startFolder
Get-ChildItem $startfolder -recurse |
Where-Object {$_.PSIsContainer -eq $True}) |
Select FullName
Add a Get-Item for your $startFolder, and wrap that along with your Get-ChildItem in a sub-expression so it's all in the same collection.
If you don't like the behavior, change it. If you use these two lines, you can get the skeleton for a proxy function:
$MetaData = New-Object System.Management.Automation.CommandMetaData (Get-Command Get-ChildItem)
[System.Management.Automation.ProxyCommand]::Create($MetaData)
Then you can add this code in the process block:
$GetItemParams = #{
Force = $Force
}
switch ($PSCmdlet.ParameterSetName) {
Items { $GetItemParams.Path = $Path }
LiteralItems { $GetItemParams.LiteralPath = $LiteralPath }
default {
Write-Error "Unable to call Get-Item on base: Unknown ParameterSetName"
}
}
Get-Item #GetItemParams
After doing that, your final proxy function would look like this:
function Get-ChildItem {
[CmdletBinding(DefaultParameterSetName='Items', SupportsTransactions=$true, HelpUri='http://go.microsoft.com/fwlink/?LinkID=113308')]
param(
[Parameter(ParameterSetName='Items', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${Path},
[Parameter(ParameterSetName='LiteralItems', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath},
[Parameter(Position=1)]
[string]
${Filter},
[string[]]
${Include},
[string[]]
${Exclude},
[Alias('s')]
[switch]
${Recurse},
[switch]
${Force},
[switch]
${Name})
begin
{
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
# Modification starts here
$GetItemParams = #{
Force = $Force
}
switch ($PSCmdlet.ParameterSetName) {
Items { $GetItemParams.Path = $Path }
LiteralItems { $GetItemParams.LiteralPath = $LiteralPath }
default {
Write-Error "Unable to call Get-Item on base: Unknown ParameterSetName"
}
}
Get-Item #GetItemParams
# Modification ends here
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Get-ChildItem
.ForwardHelpCategory Cmdlet
#>
}
Of course, you could name the function anything you want if you don't want your function to get called every time you're trying to use Get-ChildItem.
I like the File System Security PowerShell Module 3.2.3 (NTFSSecurity) which permits long foldernames and includes the -Directory flag, so my solution works as:
((Get-Item2 -Path $startFolder -ErrorAction SilentlyContinue),
(Get-ChildItem2 -Path $startFolder -Recurse -Directory -ErrorAction SilentlyContinue)).FullName |
ForEach-Object {
process-folder $_ ...
}

PowerShell to get Folder Owner 3 Folders Deep

I need to get a list of all the folders owners on a shared network drive. However, I want to limit the recursion to just 3 folders deep (some of our users will create folders several levels deep, despite us telling them not to). I've found the below script, and slightly modified it to just give folder owner (it originally returned a lot more information for ACLs), but it still goes down through every folder level. How can I modify this to only return 3 folder levels?
$OutFile = "C:\temp\FolderOwner.csv" # indicates where to input your logfile#
$Header = "Folder Path;Owner"
Add-Content -Value $Header -Path $OutFile
$RootPath = "G:\" # which directory/folder you would like to extract the acl permissions#
$Folders = dir $RootPath -recurse | where {$_.psiscontainer -eq $true}
foreach ($Folder in $Folders){
$Owner = (get-acl $Folder.fullname).owner
Foreach ($ACL in $Owner){
$OutInfo = $Folder.Fullname + ";" + $owner
Add-Content -Value $OutInfo -Path $OutFile
}
}
You should be able to add a '*' to your path for each level. For example, this should return items three levels deep under C:\Temp:
dir c:\temp\*\*\*
Here's a sample function you can use (it's written for PowerShell v3 or higher, but it can be modified to work for version 2):
function Get-FolderOwner {
param(
[string] $Path = "."
)
Get-ChildItem $Path -Directory | ForEach-Object {
# Get-Acl throws terminating errors, so we need to wrap it in
# a ForEach-Object block; included -ErrorAction Stop out of habit
try {
$Owner = $_ | Get-Acl -ErrorAction Stop | select -exp Owner
}
catch {
$Owner = "Error: {0}" -f $_.Exception.Message
}
[PSCustomObject] #{
Path = $_.FullName
Owner = $Owner
}
}
}
Then you could use it like this:
Get-FolderOwner c:\temp\*\*\* | Export-Csv C:\temp\FolderOwner.csv
If you're after all items up to and including 3 levels deep, you can modify the function like this:
function Get-FolderOwner {
param(
[string] $Path = ".",
[int] $RecurseDepth = 1
)
$RecurseDepth--
Get-ChildItem $Path -Directory | ForEach-Object {
# Get-Acl throws terminating errors, so we need to wrap it in
# a ForEach-Object block; included -ErrorAction Stop out of habit
try {
$Owner = $_ | Get-Acl -ErrorAction Stop | select -exp Owner
}
catch {
$Owner = "Error: {0}" -f $_.Exception.Message
}
[PSCustomObject] #{
Path = $_.FullName
Owner = $Owner
}
if ($RecurseDepth -gt 0) {
Get-FolderOwner -Path $_.FullName -RecurseDepth $RecurseDepth
}
}
}
And use it like this:
Get-FolderOwner c:\temp -RecurseDepth 3 | Export-Csv C:\temp\FolderOwner.csv
Any help?
resolve-path $RootPath\*\* |
where { (Get-Item $_).PSIsContainer } -PipelineVariable Path |
Get-Acl |
Select #{l='Folder';e={$Path}},Owner