FtpHandling/Get-FTPChildItem.ps1

<#
.SYNOPSIS
    Gets the item and child items from ftp location.
 
.DESCRIPTION
    The Get-FTPChildItem cmdlet gets the items from ftp locations. If the item is a container, it gets the items inside the container, known as child items.
     
.PARAMETER Path
    Specifies a path to ftp location or file.
         
.PARAMETER Session
    Specifies a friendly name for the ftp session. Default session name is 'DefaultFTPSession'.
     
.PARAMETER Recurse
    Get recurse child items.
 
.PARAMETER Depth
    Define depth of folder in recurse mode. Autoenable recurse mode.
 
.PARAMETER Filter
    Specifies a filter parameter to return only this objects that have proper name. This parameter allow to use of wildcards. Default value is *.
     
.EXAMPLE
    PS P:\> Get-FTPChildItem -path ftp://ftp.contoso.com/folder
 
 
        Parent: ftp://ftp.contoso.com/folder
 
    Dir Right Ln User Group Size ModifiedDate Name
    --- ----- -- ---- ----- ---- ------------ ----
    d rwxr-xr-x 3 ftp ftp 2012-06-19 12:58:00 subfolder1
    d rwxr-xr-x 2 ftp ftp 2012-06-19 12:58:00 subfolder2
    - rw-r--r-- 1 ftp ftp 1KB 2012-06-15 12:49:00 textitem.txt
 
.EXAMPLE
    PS P:\> Get-FTPChildItem -path ftp://ftp.contoso.com/folder -Filter "subfolder*"
 
 
        Parent: ftp://ftp.contoso.com/folder
 
    Dir Right Ln User Group Size ModifiedDate Name
    --- ----- -- ---- ----- ---- ------------ ----
    d rwxr-xr-x 3 ftp ftp 2012-06-19 12:58:00 subfolder1
    d rwxr-xr-x 2 ftp ftp 2012-06-19 12:58:00 subfolder2
 
.EXAMPLE
    PS P:\> Get-FTPChildItem -path folder -Recurse
 
 
        Parent: ftp://ftp.contoso.com/folder
 
    Dir Right Ln User Group Size ModifiedDate Name
    --- ----- -- ---- ----- ---- ------------ ----
    d rwxr-xr-x 3 ftp ftp 2012-06-19 12:58:00 subfolder1
    d rwxr-xr-x 2 ftp ftp 2012-06-19 12:58:00 subfolder2
    - rw-r--r-- 1 ftp ftp 1KB 2012-06-15 12:49:00 textitem.txt
 
 
        Parent: ftp://ftp.contoso.com/folder/subfolder1
 
    Dir Right Ln User Group Size ModifiedDate Name
    --- ----- -- ---- ----- ---- ------------ ----
    d rwxr-xr-x 2 ftp ftp 2012-06-19 12:58:00 subfolder11
    - rw-r--r-- 1 ftp ftp 21KB 2012-06-19 09:20:00 test.xlsx
    - rw-r--r-- 1 ftp ftp 14KB 2012-06-19 11:27:00 ziped.zip
 
 
        Parent: ftp://ftp.contoso.com/folder/subfolder1/subfolder11
 
    Dir Right Ln User Group Size ModifiedDate Name
    --- ----- -- ---- ----- ---- ------------ ----
    - rw-r--r-- 1 ftp ftp 14KB 2012-06-19 11:27:00 ziped.zip
 
 
        Parent: ftp://ftp.contoso.com/folder/subfolder2
 
    Dir Right Ln User Group Size ModifiedDate Name
    --- ----- -- ---- ----- ---- ------------ ----
    - rw-r--r-- 1 ftp ftp 1KB 2012-06-15 12:49:00 textitem.txt
    - rw-r--r-- 1 ftp ftp 14KB 2012-06-19 11:27:00 ziped.zip
 
.EXAMPLE
    PS P:\> $ftpFile = Get-FTPChildItem -path /folder/subfolder1/test.xlsx
    PS P:\> $ftpFile | Select-Object Parent, Name, ModifiedDate
 
    Parent Name ModifiedDate
    ------ ---- ------------
    ftp://ftp.contoso.com/folder/subfolder1 test.xlsx 2012-06-19 09:20:00
     
.NOTES
    Author: Michal Gajda
    Blog : http://commandlinegeeks.com/
 
.LINK
    Set-FTPConnection
#>
     

Function Get-FTPChildItem
{
    [OutputType('PSFTP.Item')]
    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="Low"
    )]
    Param(
        [parameter(ValueFromPipelineByPropertyName=$true,
            ValueFromPipeline=$true)]
        [String]$Path = "",
        $Session = "DefaultFTPSession",
        [parameter(ValueFromPipelineByPropertyName=$true)]
        [Switch]$Recurse,    
        [Int]$Depth = 0,        
        [String]$Filter = "*"
    )
    
    Begin
    {
        if($Session -isnot [String])
        {
            $CurrentSession = $Session
        }
        else
        {
            $CurrentSession = Get-Variable -Scope Global -Name $Session -ErrorAction SilentlyContinue -ValueOnly
        }
        
        if($null -eq $CurrentSession)
        {
            Write-Warning "Add-FTPItem: Cannot find session $Session. First use Set-FTPConnection to config FTP connection."
            Break
            Return
        }    
    }
    
    Process
    {
        Write-Debug "Native path: $Path"
        
        if($Path -match "ftp://")
        {
            $RequestUri = $Path
            Write-Verbose "Use original path: $RequestUri"
            
        }
        else
        {
            $RequestUri = $CurrentSession.RequestUri.OriginalString+"/"+$Path
            Write-Verbose "Add ftp:// at start: $RequestUri"
        }
        $RequestUri = [regex]::Replace($RequestUri, '/$', '')
        $RequestUri = [regex]::Replace($RequestUri, '/+', '/')
        $RequestUri = [regex]::Replace($RequestUri, '^ftp:/', 'ftp://')
        Write-Verbose "Remove additonal slash: $RequestUri"

        if($Depth -gt 0)
        {
            $CurrentDepth = [regex]::matches($RequestUri,"/").count
            if($null -eq (Get-Variable -Scope Script -Name MaxDepth -ErrorAction SilentlyContinue))
            {
                New-Variable -Scope Script -Name MaxDepth -Value ([Int]$CurrentDepth +$Depth)
            }
        
            Write-Verbose "Auto enable recurse mode. Current depth / Max Depth: $CurrentDepth / $($Script:MaxDepth)"
            $Recurse = $true
        }

        
        if ($pscmdlet.ShouldProcess($RequestUri,"Get child items from ftp location")) 
        {    
            if((Get-FTPItemSize $RequestUri -Session $Session -Silent) -eq -1)
            {
                Write-Verbose "Path is directory"
                $ParentPath = $RequestUri
            }
            else
            {
                Write-Verbose "Path is file. Delete last file name to get parent path."
                $LastIndex = $RequestUri.LastIndexOf("/")
                $ParentPath = $RequestUri.SubString(0,$LastIndex)
            }
                        
            [System.Net.FtpWebRequest]$Request = [System.Net.WebRequest]::Create($RequestUri)
            $Request.Credentials = $CurrentSession.Credentials
            $Request.EnableSsl = $CurrentSession.EnableSsl
            $Request.KeepAlive = $CurrentSession.KeepAlive
            $Request.UseBinary = $CurrentSession.UseBinary
            $Request.UsePassive = $CurrentSession.UsePassive
            
            $Request.Method = [System.Net.WebRequestMethods+FTP]::ListDirectoryDetails
            Write-Verbose "Use WebRequestMethods: $($Request.Method)"
            Try
            {
                $mode = "Unknown"
                [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$CurrentSession.ignoreCert}
                $Response = $Request.GetResponse()
                
                [System.IO.StreamReader]$Stream = New-Object System.IO.StreamReader($Response.GetResponseStream(),[System.Text.Encoding]::Default)

                $DirList = @()
                $ItemsCollection = @()
                Try
                {
                    [string]$Line = $Stream.ReadLine()
                    Write-Debug "Read Line: $Line"
                }
                Catch
                {
                    $Line = $null
                    Write-Debug "Line is null"
                }
                
                While ($Line)
                {
                    if($mode -eq "Compatible" -or $mode -eq "Unknown")
                    {
                        $null, [string]$IsDirectory, [string]$Flag, [string]$Link, [string]$UserName, [string]$GroupName, [string]$Size, [string]$Date, [string]$Name = `
                        [regex]::split($Line,'^([d-])([rwxt-]{9})\s+(\d{1,})\s+([.@A-Za-z0-9-]+)\s+([A-Za-z0-9-]+)\s+(\d{1,})\s+(\w+\s+\d{1,2}\s+\d{1,2}:?\d{2})\s+(.+?)\s?$',"SingleLine,IgnoreCase,IgnorePatternWhitespace")

                        if($IsDirectory -eq "" -and $mode -eq "Unknown")
                        {
                            $mode = "IIS6"
                        }
                        elseif($mode -ne "Compatible")
                        {
                            $mode = "Compatible" #IIS7/Linux
                        }
                        
                        if($mode -eq "Compatible")
                        {
                            $DatePart = $Date -split "\s+"
                            $NewDateString = "$($DatePart[0]) $('{0:D2}' -f [int]$DatePart[1]) $($DatePart[2])"
                            
                            Try
                            {
                                if($DatePart[2] -match ":")
                                {
                                    $Month = ([DateTime]::ParseExact($DatePart[0],"MMM",[System.Globalization.CultureInfo]::InvariantCulture)).Month
                                    if((Get-Date).Month -ge $Month)
                                    {
                                        $NewDate = [DateTime]::ParseExact($NewDateString,"MMM dd HH:mm",[System.Globalization.CultureInfo]::InvariantCulture)
                                    }
                                    else
                                    {
                                        $NewDate = ([DateTime]::ParseExact($NewDateString,"MMM dd HH:mm",[System.Globalization.CultureInfo]::InvariantCulture)).AddYears(-1)
                                    }
                                }
                                else
                                {
                                    $NewDate = [DateTime]::ParseExact($NewDateString,"MMM dd yyyy",[System.Globalization.CultureInfo]::InvariantCulture)
                                }
                            }
                            Catch
                            {
                                Write-Verbose "Can't parse date: $Date"
                            }                            
                        }
                    }
                    
                    if($mode -eq "IIS6")
                    {
                        $null, [string]$NewDate, [string]$IsDirectory, [string]$Size, [string]$Name = `
                        [regex]::split($Line,'^(\d{2}-\d{2}-\d{2}\s+\d{2}:\d{2}[AP]M)\s+<*([DIR]*)>*\s+(\d*)\s+(.+).*$',"SingleLine,IgnoreCase")
                        
                        if($IsDirectory -eq "")
                        {
                            $IsDirectory = "-"
                        }
                    }
                    
                    Switch($Size)
                    {
                        {[int64]$_ -lt 1024} { $HFSize = $_+"B"; break }
                        {[System.Math]::Round([int64]$_/1KB,0) -lt 1024} { $HFSize = [String]([System.Math]::Round($_/1KB,0))+"KB"; break }
                        {[System.Math]::Round([int64]$_/1MB,0) -lt 1024} { $HFSize = [String]([System.Math]::Round($_/1MB,0))+"MB"; break }
                        {[System.Math]::Round([int64]$_/1GB,0) -lt 1024} { $HFSize = [String]([System.Math]::Round($_/1GB,0))+"GB"; break }
                        {[System.Math]::Round([int64]$_/1TB,0) -lt 1024} { $HFSize = [String]([System.Math]::Round($_/1TB,0))+"TB"; break }
                        {[System.Math]::Round([int64]$_/1PB,0) -lt 1024} { $HFSize = [String]([System.Math]::Round($_/1PB,0))+"PB"; break }
                    } 
                    
                    if($IsDirectory -eq "d" -or $IsDirectory -eq "DIR")
                    {
                        $HFSize = ""
                    }
                    
                    if($ParentPath -match "\*|\?")
                    {
                        $LastIndex = $ParentPath.LastIndexOf("/")
                        $ParentPath = $ParentPath.SubString(0,$LastIndex)
                        $ParentPath.Trim() + "/" + $Name.Trim()
                    }
                    
                    $LineObj = New-Object PSObject -Property @{
                        Dir = $IsDirectory
                        Right = $Flag
                        Ln = $Link
                        User = $UserName
                        Group = $GroupName
                        Size = $HFSize
                        SizeInByte = $Size
                        OrgModifiedDate = $Date
                        ModifiedDate = $NewDate
                        Name = $Name.Trim()
                        FullName = $ParentPath.Trim() + "/" + $Name.Trim()
                        Parent = $ParentPath.Trim()
                    }
                    
                    $LineObj.PSTypeNames.Clear()
                    $LineObj.PSTypeNames.Add('PSFTP.Item')
            
                    if($Recurse -and ($LineObj.Dir -eq "d" -or $LineObj.Dir -eq "DIR"))
                    {
                        $DirList += $LineObj
                    }
                    
                    
                    if($LineObj.Dir)
                    {
                        if($LineObj.Name -like $Filter)
                        {
                            Write-Debug "Filter accepted: $Filter"
                            $ItemsCollection += $LineObj
                        }
                    }
                    $Line = $Stream.ReadLine()
                    Write-Debug "Read Line: $Line"
                }
                
                $Response.Close()
                
                if($Recurse -and ($CurrentDepth -lt $Script:MaxDepth -or $Depth -eq 0))
                {
                    $RecurseResult = @()
                    $DirList | ForEach-Object {
                        Write-Debug "Recurse is active and go to: $($_.FullName)"
                        $RecurseResult += Get-FTPChildItem -Path ($_.FullName) -Session $Session -Recurse -Filter $Filter -Depth $Depth
                        
                    }    

                    $ItemsCollection += $RecurseResult
                }    
                
                if($ItemsCollection.count -eq 0)
                {
                    Return 
                }
                else
                {
                    Return $ItemsCollection | Sort-Object -Property @{Expression="Parent";Descending=$false}, @{Expression="Dir";Descending=$true}, @{Expression="Name";Descending=$false} 
                }
            }
            Catch
            {
                Write-Error $_.Exception.Message -ErrorAction Stop 
            }
        }
        
        if($CurrentDepth -ge $Script:MaxDepth)
        {
            Remove-Variable -Scope Script -Name CurrentDepth 
        }        
    }
    
    End{}
}
Export-ModuleMember Get-FTPChildItem