PowerShell to get Folder Owner 3 Folders Deep - powershell

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

Related

How can I detect the autorun process with powershell?

I'm going to check the PC using powershell.
The purpose is to detect automatic execution malware.
If there is a new process after execution, it shows a new process. Then, I want to create a code that allows users to identify and detect whether it is a malicious process.
Function Reg {
$key_1 = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
$key_2 = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
$key_3 = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
$key_4 = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
$p1 = Get-Item -Path $key_1 | Select-Object -ExpandProperty Property
$p2 = Get-Item -Path $key_2 | Select-Object -ExpandProperty Property
$p3 = Get-Item -Path $key_3 | Select-Object -ExpandProperty Property
$p4 = Get-Item -Path $key_4 | Select-Object -ExpandProperty Property
$result = $p1 + $p2 + $p3 + $p4
$result
}
Function Check {
$file = "C:\study\project\PC_Check\result.txt"
if ( -not (Test-Path $file)) {
Reg | Out-File -FilePath "C:\study\project\PC_Check\result.txt"
}
else {
if ((Reg) -eq (Get-Content $file)) {
Write-Host "No new process."
}
else {
Write-Host "New process detected."
Reg | Out-File -FilePath "C:\study\project\PC_Check\result.txt"
}
}
}
Check
The problem with my code is that there is no comparison between the executed output and the contents of the first file.
I want to print out a new process while comparing the current outputs and file contents.
(Reg) -eq (Get-Content $file)
I think this compare part is wrong, how should I correct it?
Thank you for your time to read this and Have a nice day!
As per my comment. One way to refactor this is as follows. Tweak as needed.
Clear-Host
# Refactor to get all Autorun details
Function Get-AutorunDetail
{
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' |
ForEach-Object {(Get-Item -Path $PSitem).Property}
}
Get-AutorunDetail |
Out-Null
Function Write-AutorunResultsFile
{
# Check if file path exists
$AutorunResultsFile = 'D:\study\project\PC_Check'
# If not, create the path and the new file
if ( -not (Test-Path -Path "$AutorunResultsFile\AutorunResultsFile.txt"))
{
New-Item -Path $AutorunResultsFile -ItemType File -Name 'AutorunResultsFile.txt' -Force |
Out-Null
# Add the Autorun detail to the new file
Get-AutorunDetail |
ForEach-Object {Add-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt" -Value $PSitem}
}
else
{
if (Compare-Object -ReferenceObject {Get-AutorunDetail} -DifferenceObject (Get-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt"))
{Add-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt" -Value 'No new process.'}
else
{
'New process detected.'
Get-AutorunDetail |
ForEach-Object {Add-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt" -Value $PSitem}
}
}
}
Write-AutorunResultsFile
Again, this is just one way, there are always more and/or better ways - but I'll leave them to you to research or others to chime in.
Updated
Clear-Host
# Refactor to get all Autorun details
Function Get-AutorunDetail
{
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' |
ForEach-Object {(Get-Item -Path $PSitem).Property}
}
Get-AutorunDetail |
Out-Null
Function Write-AutorunResultsFile
{
# Check if file path exists
$AutorunResultsFile = 'D:\study\project\PC_Check'
# If not, create the path and the new file
if ( -not (Test-Path -Path "$AutorunResultsFile\AutorunResultsFile.txt"))
{
New-Item -Path $AutorunResultsFile -ItemType File -Name 'AutorunResultsFile.txt' -Force |
Out-Null
# Add the Autorun detail to the new file
Get-AutorunDetail |
ForEach-Object {Add-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt" -Value $PSitem}
}
else
{
if ($AutorunDetails = (Compare-Object -ReferenceObject (Get-AutorunDetail) -DifferenceObject (Get-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt")) -match '<=')
{
Add-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt" -Value 'New process detected.'
Add-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt" -Value ($AutorunDetails.InputObject | Select-Object -Last 1)
}
else
{Add-Content -Path "$AutorunResultsFile\AutorunResultsFile.txt" -Value 'No new process detected.'}
}
}
Write-AutorunResultsFile
# Results when altering the registry key
<#
Security...
Tablet...
Display...
ms...
OneDrive
Micros...
CiscoM...
...
Docker Desktop
GoToMeeting
No new process detected.
New process detected.
test
No new process detected.
New process detected.
test1
No new process detected.
New process detected.
test2
#>

Powershell - How To Filter Output

I need assistance in filtering what is going in to my output.
Here is my code:
$FolderPath = Get-ChildItem -Directory -Path "Z:\D482\F11\SECURE" -Recurse -Force
$Output = #()
ForEach ($Folder in $FolderPath) {
$Acl = Get-Acl -Path $Folder.FullName
ForEach ($Access in $Acl.Access) {
$Properties = [ordered]#{'Folder Name'=$Folder.FullName;'Group/User'=$Access.IdentityReference }
$Output += New-Object -TypeName PSObject -Property $Properties
}
}
$Output | ConvertTo-Csv | Out-File C:\Temp\Secured.txt
My output looks like this:
#TYPE System.Management.Automation.PSCustomObject
"Folder Name","Group/User"
"Z:\D482\F11\SECURE\QA\CDM\To_Load\Duals\Load_Completed","S-1-5-21-1275210071-879983540-1801674531-105509"
"Z:\D482\F11\SECURE\QA\Files\CDM\To_Load\Duals\Load_Completed","S-1-5-21-1275210071-879983540-1801674531-121910"
"Z\D482\F11\SECURE\QA\D482\To_Load\Duals\Load_Completed","DOMAIN\CXL3708"
In my output, I only want lines that contain our domain name ( as illustrated by the line with DOMAIN in it.
I have not been successful - either I get nothing, or I get error messages in the console.
here is one way to do the job ... [grin]
what it does ...
sets the constants
builds a parameter splat for the Get-ChildItem call
grabs the dirs from the source path
iterates thru the dir list
gets the acl list for the current dir & filters for those that start with the required domain name
note that i don't have a .IdentityReference property in my ACL, so i used .Owner instead.
iterates thru those acl items
builds a [PSCustomObject] for the current acl
sends that out to the $AccessInfo collection
displays the content of the above on screen
saves the collection to a csv file
the code ...
$SourcePath = $env:TEMP
$DomainName = $env:USERDOMAIN
$ReportFile = "SO_Erich_Powershell - How To Filter Output.csv"
$FullReportFile = Join-Path -Path $env:TEMP -ChildPath $ReportFile
$GCI_Params = #{
LiteralPath = $SourcePath
Directory = $True
Force = $True
Recurse = $True
ErrorAction = 'SilentlyContinue'
}
$DirList = Get-ChildItem #GCI_Params
$AccessInfo = foreach ($DL_Item in $DirList)
{
$AclList = Get-Acl -LiteralPath $DL_Item.FullName |
Where-Object {
$_.Owner -match "^$DomainName"
}
foreach ($AL_Item in $AclList)
{
[PSCustomObject]#{
DirName = $DL_Item.FullName
# my single system has no ".IdentityReference" property
# so i used ".Owner"
GroupOrUser = $AL_Item.Owner
}
}
}
# display the data
$AccessInfo
# send to a csv file
$AccessInfo |
Export-Csv -LiteralPath $FullReportFile -NoTypeInformation
truncated screen output ...
DirName GroupOrUser
------- -----------
C:\Temp\1 MySysName\AnotherUserName
C:\Temp\2 MySysName\AnotherUserName
C:\Temp\3 MySysName\AnotherUserName
[*snip ...*]
C:\Temp\vscode-update-system-x64 MySysName\MyUserName
C:\Temp\WPF MySysName\MyUserName
C:\Temp\mbam\qt-jl-icons MySysName\MyUserName
truncated csv file content ...
"DirName","GroupOrUser"
"C:\Temp\1","MySysName\AnotherUserName"
"C:\Temp\2","MySysName\AnotherUserName"
[*snip ...*]
"C:\Temp\WPF","MySysName\MyUserName"
"C:\Temp\mbam\qt-jl-icons","MySysName\MyUserName"

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

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

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

Only recurse subfolder x numbers of levels using PowerShell

The PowerShell script below will list out all shared folders (excluding hidden shared folders), then list out all sub-folders and finally get the ACL information of each of them and export to a CSV file.
However, I'm trying to set the limit of the sub-folder it can drill into. For example, if I set it to 3, the script will get the ACL information of first three sub-folders. How can I do this?
Input:
path=\\server\sharefolder0\subfolder01\subfolder02
path=\\server\sharefolder1\subfolder11\subfolder12\subfolder13\subfolder14
path=\\server\sharefolder2
Expected result:
path=\\server\sharefolder0
path=\\server\sharefolder0\subfolder01
path=\\server\sharefolder0\subfolder01\subfolder02
path=\\server\sharefolder1
path=\\server\sharefolder1\subfolder11
path=\\server\sharefolder1\subfolder11\subfolder12
path=\\server\sharefolder2
This is the code:
$getSRVlist = Get-Content .\server.txt
$outputDirPath=".\DirPathList.txt"
$outputACLInfo=".\ACLInfo.CSV"
$header="FolderPath,IdentityReference,Rights"
Del $outputACLInfo
add-content -value $header -path $outputACLInfo
foreach ($readSRVlist in $getSRVlist)
{
foreach ($readShareInfoList in $getShareInfoList=Get-WmiObject Win32_Share
-computerName $readSRVlist | Where {$_.name -notlike "*$"} | %{$_.Name})
{
foreach ($readDirPathList in
$getDirPathList=get-childitem \\$readSRVlist\$readShareInfoList -recurse
| where {$_.PSIsContainer})# | %{$_.fullname})
{
$getACLList=get-ACL $readDirPathList.fullname | ForEach-Object
{$_.Access}
foreach ($readACLList in $getACLList)
{
$a = $readDirPathList.fullname + "," +
$readACLList.IdentityReference + "," + $readACLList.FileSystemRights
add-content -value $a -path $outputACLInfo
}
}
}
}
Recursion is your friend. Try this:
$maxDepth = 3
function TraverseFolders($folder, $remainingDepth) {
Get-ChildItem $folder | Where-Object { $_.PSIsContainer } | ForEach-Object {
if ($remainingDepth -gt 1) {
TraverseFolders $_.FullName ($remainingDepth - 1)
}
}
}
TraverseFolders "C:\BASE\PATH" $maxDepth
Edit: Now I see what you mean. For checking the first three parent folders of a given path try this:
$server = "\\server\"
$path = ($args[0] -replace [regex]::escape($server), "").Split("\\")[0..2]
for ($i = 0; $i -lt $path.Length; $i++) {
Get-ACL ($server + [string]::join("\", $path[0..$i])
}
In newer version of powershell one can use -DEPTH parameter,
One liner can help-
get-childitem -path \\server\folder -Depth 2 -Directory | Select-object -Property Name, Fullname
It will search for 2 nested folders and will provide folder name and full path of that particular folder. Tested in version- PSVersion 5.1.17134.858