FtpHandling/Get-FTPItem.ps1

Function Get-FTPItem
{
    <#
    .SYNOPSIS
        Send specific file from ftop server to location disk.

    .DESCRIPTION
        The Get-FTPItem cmdlet download file to specific location on local machine.

    .PARAMETER Path
        Specifies a path to ftp location.

    .PARAMETER LocalPath
        Specifies a local path.

    .PARAMETER RecreateFolders
        Recreate locally folders structure from ftp server.

    .PARAMETER BufferSize
        Specifies size of buffer. Default is 20KB.

    .PARAMETER Session
        Specifies a friendly name for the ftp session. Default session name is 'DefaultFTPSession'.

    .PARAMETER Overwrite
        Overwrite item in local path.

    .EXAMPLE
        PS P:\> Get-FTPItem -Path ftp://ftp.contoso.com/folder/subfolder1/test.xlsx -LocalPath P:\test
        226 File send OK.

        PS P:\> Get-FTPItem -Path ftp://ftp.contoso.com/folder/subfolder1/test.xlsx -LocalPath P:\test

        A File name already exists in location: P:\test
        What do you want to do?
        [C] Cancel [O] Overwrite [?] Help (default is "O"): O
        226 File send OK.

    .EXAMPLE
        PS P:\> Get-FTPChildItem -path folder/subfolder1 -Recurse | Get-FTPItem -localpath p:\test -RecreateFolders -Verbose
        VERBOSE: Performing operation "Download item: 'ftp://ftp.contoso.com/folder/subfolder1/test.xlsx'" on Target "p:\test\folder\subfolder1".
        VERBOSE: Creating folder: folder\subfolder1
        226 File send OK.

        VERBOSE: Performing operation "Download item: 'ftp://ftp.contoso.com/folder/subfolder1/ziped.zip'" on Target "p:\test\folder\subfolder1".
        226 File send OK.

        VERBOSE: Performing operation "Download item: 'ftp://ftp.contoso.com/folder/subfolder1/subfolder11/ziped.zip'" on Target "p:\test\folder\subfolder1\subfolder11".
        VERBOSE: Creating folder: folder\subfolder1\subfolder11
        226 File send OK.

    .NOTES
        Author: Michal Gajda
        Blog : http://commandlinegeeks.com/

    .LINK
        Get-FTPChildItem
    #>


    [CmdletBinding(
        SupportsShouldProcess=$True,
        ConfirmImpact="Low"
    )]
    Param(
        [parameter(Mandatory=$true,
            ValueFromPipelineByPropertyName=$true,
            ValueFromPipeline=$true)]
        [Alias("FullName")]
        [String]$Path,
        [String]$LocalPath = (Get-Location).Path,
        [Switch]$RecreateFolders,
        [Int]$BufferSize = 20KB,
        $Session = "DefaultFTPSession",
        [Switch]$Overwrite = $false
    )

    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-Debug "Use original path: $RequestUri"

        }
        else
        {
            $RequestUri = $CurrentSession.RequestUri.OriginalString+"/"+$Path
            Write-Debug "Add ftp:// at start: $RequestUri"
        }
        $RequestUri = [regex]::Replace($RequestUri, '/$', '')
        $RequestUri = [regex]::Replace($RequestUri, '/+', '/')
        $RequestUri = [regex]::Replace($RequestUri, '^ftp:/', 'ftp://')
        Write-Debug "Remove additonal slash: $RequestUri"

        if ($pscmdlet.ShouldProcess($LocalPath,"Download item: '$RequestUri'"))
        {
            $TotalData = Get-FTPItemSize $RequestUri -Session $Session -Silent
            if($TotalData -eq -1) { Return }
            if($TotalData -eq 0) { $TotalData = 1 }

            $AbsolutePath = ($RequestUri -split $CurrentSession.ServicePoint.Address.AbsoluteUri)[1]
            $LastIndex = $AbsolutePath.LastIndexOf("/")
            if($LastIndex -eq -1)
            {
                $FolderPath = "\"
            }
            else
            {
                $FolderPath = $AbsolutePath.SubString(0,$LastIndex) -replace "/","\"
            }
            $FileName = $AbsolutePath.SubString($LastIndex+1)

            if($RecreateFolders)
            {
                if(!(Test-Path (Join-Path -Path $LocalPath -ChildPath $FolderPath)))
                {
                    Write-Verbose "Creating folder: $FolderPath"
                    New-Item -Type Directory -Path $LocalPath -Name $FolderPath | Out-Null
                }
                $LocalDir = Join-Path -Path $LocalPath -ChildPath $FolderPath
            }
            else
            {
                $LocalDir = $LocalPath
            }

            [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]::DownloadFile
            Write-Debug "Use WebRequestMethods: $($Request.Method)"
            Try
            {
                [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$CurrentSession.ignoreCert}
                $SendFlag = 1

                if((Get-ItemProperty $LocalDir -ErrorAction SilentlyContinue).Attributes -match "Directory")
                {
                    $LocalDir = Join-Path -Path $LocalDir -ChildPath $FileName
                }

                if(Test-Path ($LocalDir))
                {
                    $FileSize = (Get-Item $LocalDir).Length

                    if($Overwrite -eq $false)
                    {
                        $Title = "A file ($RequestUri) already exists in location: $LocalDir"
                        $Message = "What do you want to do?"

                        $CDOverwrite = New-Object System.Management.Automation.Host.ChoiceDescription "&Overwrite"
                        $CDCancel = New-Object System.Management.Automation.Host.ChoiceDescription "&Cancel"
                        if($FileSize -lt $TotalData)
                        {
                            $CDResume = New-Object System.Management.Automation.Host.ChoiceDescription "&Resume"
                            $Options = [System.Management.Automation.Host.ChoiceDescription[]]($CDCancel, $CDOverwrite, $CDResume)
                            $SendFlag = $host.ui.PromptForChoice($Title, $Message, $Options, 2)
                        }
                        else
                        {
                            $Options = [System.Management.Automation.Host.ChoiceDescription[]]($CDCancel, $CDOverwrite)
                            $SendFlag = $host.ui.PromptForChoice($Title, $Message, $Options, 1)
                        }
                    }
                    else
                    {
                        $SendFlag = 1
                    }
                }

                if($SendFlag)
                {
                    [Byte[]]$Buffer = New-Object Byte[] $BufferSize

                    $ReadedData = 0
                    $AllReadedData = 0

                    if($SendFlag -eq 2)
                    {
                        $File = New-Object IO.FileStream ($LocalDir,[IO.FileMode]::Append)
                        $Request.UseBinary = $True
                        $Request.ContentOffset  = $FileSize
                        $AllReadedData = $FileSize
                        Write-Debug "Open File to append: $LocalDir"
                    }
                    else
                    {
                        $File = New-Object IO.FileStream ($LocalDir,[IO.FileMode]::Create)
                        Write-Debug "Create File: $LocalDir"
                    }

                    $Response = $Request.GetResponse()
                    $Stream  = $Response.GetResponseStream()

                    Do{
                        $ReadedData=$Stream.Read($Buffer,0,$Buffer.Length)
                        $AllReadedData +=$ReadedData
                        $File.Write($Buffer,0,$ReadedData)
                        if($TotalData)
                        {
                            Write-Progress -Activity "Download File: $Path" -Status "Downloading:" -Percentcomplete ([int]($AllReadedData/$TotalData * 100))
                        }
                    }
                    While ($ReadedData -ne 0)
                    $File.Close()
                    Write-Debug "Close File: $LocalDir"

                    $Status = $Response.StatusDescription
                    $Response.Close()
                    Return $Status
                }
            }
            Catch
            {
                Write-Error $_.Exception.Message -ErrorAction Stop
            }
        }
    }

    End{}
}