Use PowerShell to work with files at folder level

I can think of so many scenarios where people need to work with files at the folder level. The get-childitem cmdlet is indeed very powerful but it does not work at the folder level without any additional work.

Here is what I mean:

Let us say that you have a folder structure like this to do your SQL Server backups


\\My_Network_Folder
   \Server_Main
      \DB1
         \DB1_backup_20140307_120940.bak
         \DB1_backup_20140205_121041.bak
         \DB1_backup_20140104_120042.bak
      \DB2
         \DB2_backup_20140307_120940.bak
         \DB2_backup_20140205_121041.bak
         \DB2_backup_20140104_120042.bak
      \DB3
         \DB3_backup_20140307_120940.bak
         \DB3_backup_20140205_121041.bak
         \DB3_backup_20140104_120042.bak

With this structure, you might want to

  1. Pick the latest backup file “from each folder”
  2. Remove backup files older than x days “within each folder”
  3. Archive files older than x days “within each folder”
  4. …any number of things only an administrator can conjure!

The below cmdlet helps to take care of the void created by the lack of the swith “-ByFolder” in Get-ChildItem

Get-ChildItemByFolder

<#
.SYNOPSIS
Gets qualifying files "by folder" for each qualifying folder and subfolders
.DESCRIPTION
This is similar in functionality to Get-ChildItem except when there is a need to
group everything at folder level before applying filtration criteria.
Here are some typical scenarios
1) Get latest *.bak file from each sub-folder
2) Get the last 5 *.txn file within each folder whose size is greater than x
3) Get all the files greater than x but only include files with pattern "*.trc" and exclude files with pattern "*.bak"
4) Look recursively through a folder and remove all files matching certain criteria like age, size, type etc..
5) Same as above but archive files meeting a certain criteria and then remove

.INPUTS
Folder to enumerate
+ other parameters that work the same as Get-ChildItem because the parameters of this function
are internally passed to that cmdlet
.OUTPUTS

.EXAMPLE
Get-ChildItemByFolder -Path "C:\_dblog\" -Recurse -Filter "*.log" -Newest 1

The above gets all the files named *.log within all sub-folders of "C:\_dblog\" and
gets the qualifying newest 1 file from each folder

.EXAMPLE
Get-ChildItemByFolder -Path "C:\_dblog\" -Recurse -Filter "*.log" -Newest 1 -Skip 1

Same as previous one except it finds the second newest one (-skip 1)

.EXAMPLE
Get-ChildItemByFolder -Path "C:\_dblog\" -Recurse -Filter "*.log" -Newest 1 -Skip 1

Same as previous one except it finds the second newest one (-skip 1)

.EXAMPLE
Get-ChildItemByFolder -Path "C:\_dblog\" -Filter "*.log" -Newest 1 -Recurse | ForEach-Object {Copy-Item -Path $_.FullName -Destination "c:\test"}

Finds the latest 1 file in each of the sub-folders of given folder with name matching "*.log"
and copies the files over to "c:\test"

.EXAMPLE
Get-ChildItemByFolder -Path "C:\_dblog\*" -Filter "*.bak" -Recurse -Newest 1 -SizeBelow 100MB

Finds the latest 1 file in each of the sub-folders of given folder with name matching "*.bak"
whose size is below 100 MB
.EXAMPLE
Get-ChildItemByFolder -Path "C:\_dblog\*" -Filter "*.bak" -Recurse -Newest 1 -SizeBelow 100MB |
ForEach-Object {Copy-Item -Path $_.FullName -Destination "c:\test"}

Same as previous example but also copies the qualifying files to "c:\test"

.EXAMPLE
Get-ChildItemByFolder -Path "C:\_dblog\*" -Filter "*.bak" -Recurse -Newest 1 -SizeBelow 100MB |
ForEach-Object {if (-not (Test-Path "c:\test\$_.Name" -PathType Leaf)) {Copy-Item -Path $_.FullName -Destination "c:\test"}}

Same as previous example only copies if the file does not already exist

.EXAMPLE
Get-ChildItemByFolder -Path "C:\~TMP\*" -Filter "*.log" -SizeAbove 5MB -Recurse | Remove-Item

Get all files matching "*.log" recursively inside "c:\temp" and remove them if they are above 5MB

.NOTES

Version History
v1.0 - Jana Sattainathan [Twitter: @SQLJana] [Blog: sqljana.wordpress.com] - Initial Release

.LINK

#> 
function Get-ChildItemByFolder
{
    [CmdletBinding()]
    param
    (
		[Parameter(ParameterSetName='Items', Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
		[System.String[]]
		$Path,

		[Parameter(Position=1)]
		[System.String]
		$Filter,

		[System.String[]]
		$Include,

		[System.String[]]
		$Exclude,

        [Switch]
		$Recurse,

		[Switch]
		$Force,

        [ValidateRange(0, 2147483647)]
        [System.Int32]
        $Newest,

        [ValidateRange(0, 2147483647)]
        [System.Int32]
        $Oldest,

        [ValidateRange(0, 2147483647)]
        [System.Int32]
        $Skip,

        [System.DateTime]
        $AfterDate,

        [System.DateTime]
        $BeforeDate,

        [ValidateRange(0, 9223372036854775807)]
        [System.Int64]
        $SizeAbove,

        [ValidateRange(0, 9223372036854775807)]
        [System.Int64]
        $SizeBelow
    )

    Begin
    {
        $result=@()
        $getChildItemsParams=@{}
        $selectObjectParams=@{}

        #Splatting is the only way to pass only the parameters that are not-null
        #  ($Path and $Recurse are not being splatted since we change the value based on our logic below)
        #if ($Path) { $getChildItemsParams.Add("Path", $Path) }
        if ($Filter) { $getChildItemsParams.Add('Filter', $Filter) }
        if ($Include) { $getChildItemsParams.Add('Include', $Include) }
        if ($Exclude) { $getChildItemsParams.Add('Exclude', $Exclude) }
        #if ($Recurse) { $getChildItemsParams.Add("Recurse", $Recurse) }
        if ($Force) { $getChildItemsParams.Add('Force', $Force) }        

        #Notice the remapping from First->Newest, Last->Oldest
        if ($Newest) { $selectObjectParams.Add('First', $Newest) }
        if ($Oldest) { $selectObjectParams.Add('Last', $Oldest) }
        if ($Skip) { $selectObjectParams.Add('Skip', $Skip) }
    }
    Process
    { 

        foreach ($pathItem in $Path)
        {
            #--- 1 --- Get the root folder level leaf items that qualify
            $items = Get-ChildItem -Path $Path -Recurse:$false @getChildItemsParams |
                                    # Essentially, we are picking up only leaf-level items
                                    Where-Object {!$_.PsIsContainer} |
                                        Where-Object {if ($AfterDate) {$_.CreationTime -ge $AfterDate} else {$true}}  |
                                        Where-Object {if ($BeforeDate) {$_.CreationTime -le $BeforeDate} else {$true}} |
                                        Where-Object {if ($SizeAbove) {($_.Length) -ge $SizeAbove} else {$true}}  |
                                        Where-Object {if ($SizeBelow) {($_.Length) -le $SizeBelow} else {$true}} |
                                        Sort-Object -Descending -Property {$_.CreationTime} |
                                        Select-Object @selectObjectParams

            foreach ($item in $items)
            {
                $result += $item
            }

            #--- 2 --- Get the sub-folder level leaf items that qualify
            if ($Recurse)
            {
                # Get only the folders
			    $items = Get-ChildItem -Path $Path -Recurse:$Recurse <# -Directory in PS 3 only! #> |
                                # Loop through each folder (get it? Get-ChildItemByFolder!!)
                                Where-Object {$_.PsIsContainer} |
                                foreach-object `
                                { `
                                    # Essentially, we are picking up selective items within this folder
                                    Get-ChildItemByFolder -Path $_.FullName -Recurse:$false @getChildItemsParams  |
                                        # If it is not a folder (i.e., a file matching our criteria)
                                        Where-Object {!$_.PsIsContainer} |
                                        Where-Object {if ($AfterDate) {$_.CreationTime -ge $AfterDate} else {$true}}  |
                                        Where-Object {if ($BeforeDate) {$_.CreationTime -le $BeforeDate} else {$true}} |
                                        Where-Object {if ($SizeAbove) {($_.Length) -ge $SizeAbove} else {$true}}  |
                                        Where-Object {if ($SizeBelow) {($_.Length) -le $SizeBelow} else {$true}} |
                                        Sort-Object -Descending -Property {$_.CreationTime} |
                                        Select-Object @selectObjectParams
                                }

                foreach ($item in $items)
                {
                    $result += $item
                }
            }            

        }
    }  

    End
    {
        Write-Output (@($result))
        #@($result)
    } 

 } #Get-ChildItemByFolder

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s