Search code examples
powershellduplicatesdirectoryget-childitempsobject

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.


Solution

  • 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