ftpslib.psm1

# PowerShell FTP(S) Toolkit v1.0
#
#
# Copyright 2020 Rob Kalmar
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# Functions to connect to FTP(S) server.
#
# Tested platforms:
# Windows Server 2008 R2
# Windows Server 2012
# Windows Server 2012 R2
# Windows Server 2016
# Windows Server 2019
#
# Notes:
# - Only supports passive mode
# - Does not support TLS session resumption on the data connection.

function Show-Error
{
  param
  (
    [parameter(Mandatory=$true)][Management.Automation.ErrorRecord]$ThrownError
  )

  Write-Verbose -Message "[$ThrownError]"
  Return $False
}

Function Connect-FTPServer
{
  [CmdletBinding()]
  param ()
  
  [int]$BufferSize = 16384
  $Buffer = New-Object -TypeName byte[] -ArgumentList $BufferSize
  
  Try
  {
    $This.CommandConnection = New-Object -TypeName System.Net.Sockets.TcpClient -ArgumentList $This.FQDN, $This.Port
  }
  Catch
  {
    $ErrorMessage = $_.Exception.Message
    Write-Verbose -Message $ErrorMessage
    Return $False
  }

  $This.CommandConnection.ReceiveTimeout = $This.Timeout
  $This.CommandConnection.SendTimeout = $This.Timeout
  
  $This.CommandStream = $This.CommandConnection.GetStream()
  
  $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
  $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
  $Response = $ResponseText.ToString().Trim("`r`n").Split("`n", 2)[0]
  Write-Verbose -Message "[$Response]"
  
  If ($This.SSL)
  {
    $CommandBytes = [Text.Encoding]::ASCII.GetBytes("AUTH TLS`r`n")
    $null = $This.CommandStream.Write($CommandBytes,0,$CommandBytes.Length)
    $null = $This.CommandStream.Flush()
    
    $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
    $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
    $Response = $ResponseText.ToString().Trim("`r`n")
    Write-Verbose -Message "[$Response]"
    
    If ($This.VerifyCertificate)
    {
      $This.CommandStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.CommandStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = $null) 
    }
    Else
    {
      $This.CommandStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.CommandStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = { Return $True })
    }
    
    $null = $This.CommandStream.AuthenticateAsClient("$($This.FQDN)")
  }
  
  Return $True
}

Function Disconnect-FTPServer
{
  [CmdletBinding()]
  param ()

  Try
  {
    $null = $This.SubmitCommand('QUIT','')
    $null = $This.CommandStream.Dispose()
    $null = $This.CommandConnection.Close()
  }
  Catch
  {
    Return $False
  }
  
  Return $True
}

Function Submit-FTPCommand
{
  param
  (
    [parameter(Mandatory=$true)][string]$Command,
    [string]$Parameter = ''
  )

  # some FTP servers cannot handle it when we send commands too fast.
  $null = Start-Sleep -Milliseconds 100

  [int]$BufferSize = 16384
  $Buffer = New-Object -TypeName byte[] -ArgumentList $BufferSize
    
  switch ($Command)
  {
    'MLSD' {
      $CommandText = "$Command $Parameter".Trim()
      Write-Verbose -Message "[$CommandText]"
      $CommandBytes = [Text.Encoding]::ASCII.GetBytes("$CommandText`r`n")
      $null = $This.CommandStream.Write($CommandBytes,0,$CommandBytes.Length)
      $null = $This.CommandStream.Flush()
      
      Try
      {
        $This.DataConnection = New-Object -TypeName System.Net.Sockets.TcpClient -ArgumentList $This.DataStreamIP, $This.DataStreamPort
      }
      Catch
      {
        $ErrorMessage = $_.Exception.Message
        Write-Verbose -Message $ErrorMessage
        Return $False
      }
    
      $This.DataConnection.ReceiveTimeout = $This.Timeout
      $This.DataConnection.SendTimeout = $This.Timeout
    
      $This.DataStream = $This.DataConnection.GetStream()
              
      If ($This.SSL)
      {
        If ($This.VerifyCertificate)
        {
          $This.DataStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.DataStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = $null) 
        }
        Else
        {
          $This.DataStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.DataStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = { Return $True })
        }
    
        Try
        {
          $null = $This.DataStream.AuthenticateAsClient("$($This.FQDN)")
        }
        Catch
        {
          $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
          $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
          $Response = $ResponseText.ToString().Trim("`r`n")
          Write-Verbose -Message "[$Response]"
          
          $This.CommandOutput = $Response
          
          Return $False
        }
      }
      Else
      {
        $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
        $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
        $Response = $ResponseText.ToString().Trim("`r`n")
        Write-Verbose -Message "[$Response]"
        
        $This.CommandOutput = $Response
        
        If ($Response.SubString(0,1) -eq '5')
        {
          Return $False
        }
      }
      
      $NumberOfBytes = $This.DataStream.Read($Buffer, 0, $Buffer.Length)
      $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
      $Response = $ResponseText.ToString().Trim("`r`n")
      
      $This.CommandOutput = $Response
      Write-Verbose -Message "[$Response]"
      
      $null = $This.DataStream.Dispose()
      $null = $This.DataConnection.Close()
      
      $Response = $null
      while (($Response -eq $null) -OR ($Response.SubString(0,3) -ne '226'))
      {
        $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
        $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
        $Response = $ResponseText.ToString().Trim("`r`n")
        Write-Verbose -Message "[$Response]"
      }
      
      $This.CommandOutput = $Response
      
      Return $True
    }
    
    'STOR' {
      $FileName = Split-Path -Path $Parameter -Leaf
      $CommandText = "$Command $FileName".Trim()
      Write-Verbose -Message "[$CommandText]"
      $CommandBytes = [Text.Encoding]::ASCII.GetBytes("$CommandText`r`n")
      $null = $This.CommandStream.Write($CommandBytes,0,$CommandBytes.Length)
      $null = $This.CommandStream.Flush()
     
      Try
      {
        $This.DataConnection = New-Object -TypeName System.Net.Sockets.TcpClient -ArgumentList $This.DataStreamIP, $This.DataStreamPort
      }
      Catch
      {
        $ErrorMessage = $_.Exception.Message
        Write-Verbose -Message $ErrorMessage
        Return $False
      }

      $This.DataConnection.ReceiveTimeout = $This.Timeout
      $This.DataConnection.SendTimeout = $This.Timeout

      $This.DataStream = $This.DataConnection.GetStream()
              
      If ($This.SSL)
      {
        If ($This.VerifyCertificate)
        {
          $This.DataStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.DataStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = $null) 
        }
        Else
        {
          $This.DataStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.DataStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = { Return $True })
        }
    
        Try
        {
          $null = $This.DataStream.AuthenticateAsClient("$($This.FQDN)")
        }
        Catch
        {
          $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
          $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
          $Response = $ResponseText.ToString().Trim("`r`n")
          Write-Verbose -Message "[$Response]"
          
          $This.CommandOutput = $Response
          
          Return $False
        }
      }
      Else
      {
        $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
        $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
        $Response = $ResponseText.ToString().Trim("`r`n")
        Write-Verbose -Message "[$Response]"
        
        $This.CommandOutput = $Response
        
        If ($Response.SubString(0,1) -eq '5')
        {
          Return $False
        }
      }
        
      $fileStream = [IO.File]::OpenRead($Parameter)
      $filesize = (Get-Item -Path $Parameter).Length
      $totalread = 0
      $starttime = Get-Date
      
      While( $bytesRead = $fileStream.Read($Buffer,0,$BufferSize) )
      {
        $null = $This.DataStream.Write($Buffer, 0, $bytesRead)
        $null = $This.DataStream.Flush()
        $totalread = $totalread + $bytesRead
        $currenttime = Get-Date
        $elapsedtime = $currenttime - $starttime
        $elapsedseconds = $elapsedtime.TotalSeconds
        $speed = [math]::Round($totalread / ($elapsedseconds * 1024 * 1024),3)
        $completed = (($totalread/$filesize)*100)
        Write-Progress -Activity "Uploading file $Parameter" -Status "Total average upload speed in MB/s: $speed " -CurrentOperation 'Uploading...' -PercentComplete $completed -Id 1
      }
      
      Write-Verbose -Message "Completed upload of: $Parameter. Average speed: $speed MB/s." 
      
      $null = $This.DataStream.Dispose()
      $null = $This.DataConnection.Close()
              
      $Response = $null
      while (($Response -eq $null) -OR ($Response.SubString(0,3) -ne '226'))
      {
        $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
        $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
        $Response = $ResponseText.ToString().Trim("`r`n") 
        Write-Verbose -Message "[$Response]"
      }
      
      $This.CommandOutput = $Response
      
      Return $True               
    }
    
    'RETR' {
      $FileName = $Parameter
      
      $CommandBytes = [Text.Encoding]::ASCII.GetBytes("SIZE $FileName`r`n")
      Write-Verbose -Message "[SIZE $FileName]"
      $null = $This.CommandStream.Write($CommandBytes,0,$CommandBytes.Length)
      $null = $This.CommandStream.Flush()
    
      $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
      $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
      $Response = $ResponseText.ToString().Trim("`r`n")
      Write-Verbose -Message "[$Response]"
      
      $This.CommandOutput = $Response
        
      If ($Response.SubString(0,1) -eq '5')
      {
        Return $False
      }
      
      $FileSize = $Response.Split(' ')[1]
      
      $CommandText = "$Command $FileName".Trim()
      Write-Verbose -Message "[$CommandText]"
      $CommandBytes = [Text.Encoding]::ASCII.GetBytes("$CommandText`r`n")
      $null = $This.CommandStream.Write($CommandBytes,0,$CommandBytes.Length)
      $null = $This.CommandStream.Flush()
     
      Try
      {
        $This.DataConnection = New-Object -TypeName System.Net.Sockets.TcpClient -ArgumentList $This.DataStreamIP, $This.DataStreamPort
      }
      Catch
      {
        $ErrorMessage = $_.Exception.Message
        Write-Verbose -Message $ErrorMessage
        Return $False
      }

      $This.DataConnection.ReceiveTimeout = $This.Timeout
      $This.DataConnection.SendTimeout = $This.Timeout

      $This.DataStream = $This.DataConnection.GetStream()
              
      If ($This.SSL)
      {
        If ($This.VerifyCertificate)
        {
          $This.DataStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.DataStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = $null) 
        }
        Else
        {
          $This.DataStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList $This.DataStream, $True, ([Net.ServicePointManager]::ServerCertificateValidationCallback = { Return $True })
        }
        
        Try
        {
          $null = $This.DataStream.AuthenticateAsClient("$($This.FQDN)")
        }
        Catch
        {
          $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
          $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
          $Response = $ResponseText.ToString().Trim("`r`n")
          Write-Verbose -Message "[$Response]"
          
          $This.CommandOutput = $Response
          
          Return $False
        }
      }
      Else
      {
        $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
        $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
        $Response = $ResponseText.ToString().Trim("`r`n")
        Write-Verbose -Message "[$Response]"
        
        $This.CommandOutput = $Response
        
        If ($Response.SubString(0,1) -eq '5')
        {
          Return $False
        }
      }
      
      $totalwrite = 0
      $starttime = Get-Date
        
      $fileStream = [IO.File]::OpenWrite($($This.DownloadLocation + '\' + $Parameter))
      
      $NumberOfBytes = $This.DataStream.Read($Buffer, 0, $Buffer.Length)
      While ($NumberOfBytes -gt 0)
      {
        $totalwrite = $totalwrite + $NumberOfBytes
        $currenttime = Get-Date
        $elapsedtime = $currenttime - $starttime
        $elapsedseconds = $elapsedtime.TotalSeconds
        $speed = [math]::Round($totalwrite / ($elapsedseconds * 1024 * 1024),3)
        $completed = (($totalwrite/$filesize)*100)
        Write-Progress -Activity "Downloading file $FileName" -Status "Total average upload speed in MB/s: $speed " -CurrentOperation 'Downloading...' -PercentComplete $completed -Id 1
        $null = $fileStream.Write($Buffer, 0, $Buffer.Length)
        $NumberOfBytes = $This.DataStream.Read($Buffer, 0, $Buffer.Length)
      }
      $null = $fileStream.Close()
      
      Write-Verbose -Message "Completed download of: $FileName. Average speed: $speed MB/s." 
      
      $null = $This.DataStream.Dispose()
      $null = $This.DataConnection.Close()
              
      $Response = $null
      while (($Response -eq $null) -OR ($Response.SubString(0,3) -ne '226'))
      {
        $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
        $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
        $Response = $ResponseText.ToString().Trim("`r`n")
        Write-Verbose -Message "[$Response]"
      }
      
      $This.CommandOutput = $Response
      
      Return $True               
    }
           
    Default {
      $CommandText = "$Command $Parameter".Trim()
      Write-Verbose -Message "[$CommandText]"
      $CommandBytes = [Text.Encoding]::ASCII.GetBytes("$CommandText`r`n")
      $null = $This.CommandStream.Write($CommandBytes,0,$CommandBytes.Length)
      $null = $This.CommandStream.Flush()
    
      $NumberOfBytes = $This.CommandStream.Read($Buffer, 0, $Buffer.Length)
      $ResponseText = [Text.Encoding]::ASCII.GetString($Buffer, 0, $NumberOfBytes)
      $Response = $ResponseText.ToString().Trim("`r`n") 

      If (($Response -eq $null) -OR ($Response.SubString(0,1) -eq '5'))
      {
        Write-Verbose -Message "[$Response]"
        $This.CommandOutput = $Response
        Return $False
      }

      If ($Command -eq 'PASV')
      {
        $array = (($Response -Split '\(')[1] -split '\)')[0] -split ','
        $This.DataStreamPort = ([int]$array[4] * 256) + [int]$array[5]
        $This.DataStreamIP = $($array[0] + '.' + $array[1] + '.' + $array[2] + '.' + $array[3])
      }

      $This.CommandOutput = $Response
      Write-Verbose -Message "[$Response]"
      
      Return $True
    }
  }
}

Function Invoke-Authentication
{
  [CmdletBinding()]
  param 
  (
    [string]$Username = $This.Username,
    [string]$Password = $This.Password
  )

  If ($This.SSL)
  {
    Try
    {
      (
        $This.SubmitCommand('USER',$Username) -and
        $This.SubmitCommand('PASS',$Password) -and
        $This.SubmitCommand('PBSZ','0') -and
        $This.SubmitCommand('PROT','P')
      )
    }
    Catch
    {
      Return $False
    }
  }
  Else
  {
    Try
    {
      (
        $This.SubmitCommand('USER',$Username) -and
        $This.SubmitCommand('PASS',$Password)
      )
    }
    Catch
    {
      Return $False
    }
  }
}

Function Invoke-ChangeDir
{
  param 
  (
    [parameter(Mandatory=$true)][string]$Directory
  )
  
  Try
  {
    (
      $This.SubmitCommand('TYPE','A') -and
      $This.SubmitCommand('CWD',"$Directory")
    )
  }
  Catch
  {
    Return $False
  }
}

Function Invoke-MakeDir
{
  param 
  (
    [parameter(Mandatory=$true)][string]$Directory
  )
  
  Try
  {
    (
      $This.SubmitCommand('TYPE','A') -and
      $This.SubmitCommand('MKD',"$Directory")
    )
  }
  Catch
  {
    Return $False
  }
}

Function Invoke-ListDir
{
  [CmdletBinding()]
  param ()
  
  Try
  {
    (
      $This.SubmitCommand('TYPE','A') -and
      $This.SubmitCommand('PASV','') -and
      $This.SubmitCommand('MLSD','') 
    )
  }
  Catch
  {
    Return $False
  }
}

Function Invoke-UploadFile
{
  param
  (
    [parameter(Mandatory=$true)][string]$FilePath
  )
  
  Try
  {
    (
      $This.SubmitCommand('TYPE','I') -and
      $This.SubmitCommand('PASV','') -and
      $This.SubmitCommand('STOR',"$FilePath")
    )
    
  }
  Catch
  {
    Return $False
  }
}

Function Invoke-DownloadFile
{
  param
  (
    [parameter(Mandatory=$true)][string]$FileName
  )
  
  Try
  {
    (
      $This.SubmitCommand('TYPE','I') -and
      $This.SubmitCommand('PASV','') -and
      $This.SubmitCommand('RETR',"$FileName")
    )
  }
  Catch
  {
    Return $False
  }
}

Function Invoke-DeleteFile
{
  param
  (
    [parameter(Mandatory=$true)][string]$FileName
  )
  
  Try
  {
    (
      $This.SubmitCommand('TYPE','A') -and
      $This.SubmitCommand('DELE',"$FileName")
    )
  }
  Catch
  {
    Return $False
  }
}

Function New-FTPObject
{
  param
  (
    [parameter(Mandatory=$true)][string]$FQDN,
    [string]$Port = '21',
    [string]$Username = '',
    [string]$Password = '',
    [int]$Timeout = 10000,
    [bool]$SSL = $False,
    [bool]$VerifyCertificate = $False
  )

  Write-Verbose -Message 'Creating new FTP Server object.'

  $FTPServer = new-object -TypeName PSObject
  $FTPServer | Add-Member -Name FQDN -Value $FQDN -MemberType NoteProperty
  $FTPServer | Add-Member -Name Port -Value $Port -MemberType NoteProperty
  $FTPServer | Add-Member -Name Username -Value $Username -MemberType NoteProperty
  $FTPServer | Add-Member -Name Password -Value $Password -MemberType NoteProperty
  $FTPServer | Add-Member -Name Timeout -Value $Timeout -MemberType NoteProperty
  $FTPServer | Add-Member -Name SSL -Value $SSL -MemberType NoteProperty
  $FTPServer | Add-Member -Name VerifyCertificate -Value $VerifyCertificate -MemberType NoteProperty
  $FTPServer | Add-Member -Name DownloadLocation -Value $($(Split-Path -Path $script:MyInvocation.MyCommand.Path) + '\download') -MemberType NoteProperty
  
  $FTPServer | Add-Member -Name CommandConnection -Value $null -MemberType NoteProperty
  $FTPServer | Add-Member -Name DataConnection -Value $null -MemberType NoteProperty
  $FTPServer | Add-Member -Name CommandStream -Value $null -MemberType NoteProperty
  $FTPServer | Add-Member -Name DataStream -Value $null -MemberType NoteProperty
  $FTPServer | Add-Member -Name DataStreamIP -Value $null -MemberType NoteProperty
  $FTPServer | Add-Member -Name DataStreamPort -Value $null -MemberType NoteProperty
  
  $FTPServer | Add-Member -Name CommandOutput -Value $null -MemberType NoteProperty
  
  $guid = [guid]::NewGuid()
  $FTPServer | Add-Member -Name guid -Value $guid -MemberType NoteProperty
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name Connect -Force -Value { 
    [CmdletBinding()]
    param ()
    
    Try
    {
      $Result = Connect-FTPServer -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  }
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name Close -Force -Value { 
    [CmdletBinding()]
    param ()
    
    Try
    {
      $Result = Disconnect-FTPServer -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  }
   
  $FTPServer | Add-Member -MemberType ScriptMethod -Name SubmitCommand -Force -Value { 
    [CmdletBinding()]
    param 
    (
      [string]$Command = '',
      [string]$Parameter = ''
    )
    
    Try
    {
      $Result = Submit-FTPCommand -Command $Command -Parameter $Parameter -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  }
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name Authenticate -Force -Value { 
    [CmdletBinding()]
    param 
    (
      [string]$Username = $This.Username,
      [string]$Password = $This.Password
    )

    Try
    {
      $Authenticated = Invoke-Authentication -Username $Username -Password $Password -Verbose
    }
    Catch
    {
      $Authenticated = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Authenticated
  }
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name ChangeDir -Force -Value { 
    param 
    (
      [parameter(Mandatory=$true)][string]$Directory
    )

    Try
    {
      $Result = Invoke-ChangeDir -Directory $Directory -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  }
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name MakeDir -Force -Value { 
    param 
    (
      [parameter(Mandatory=$true)][string]$Directory
    )

    Try
    {
      $Result = Invoke-MakeDir -Directory $Directory -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  }
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name ListDir -Force -Value { 
    [CmdletBinding()]
    param ()

    Try
    {
      $Result = Invoke-ListDir -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  } 
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name UploadFile -Force -Value { 
    param
    (
      [parameter(Mandatory=$true)][string]$FilePath
    )

    Try
    {
      $Result = Invoke-UploadFile -FilePath $FilePath -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  } 
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name DownloadFile -Force -Value { 
    param
    (
      [parameter(Mandatory=$true)][string]$FileName
    )

    Try
    {
      $Result = Invoke-DownloadFile -FileName $FileName -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  }
  
  $FTPServer | Add-Member -MemberType ScriptMethod -Name DeleteFile -Force -Value { 
    param
    (
      [parameter(Mandatory=$true)][string]$FileName
    )

    Try
    {
      $Result = Invoke-DeleteFile -FileName $FileName -Verbose
    }
    Catch
    {
      $Result = Show-Error -ThrownError $_ -Verbose
    }
    
    Return $Result
  }
  
  Return $FTPServer
}

Export-ModuleMember -Function 'New-FTPObject'