Transferetto.psm1

function Remove-EmptyValue {
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary)
    foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } }
    if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } }
}
function Add-PrivateFTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $RemotePath,
        [string] $LocalPath,
        [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip,
        [FluentFTP.FtpVerify] $VerifyOptions = [FluentFTP.FtpVerify]::None,
        [switch] $CreateRemoteDirectory)
    try {
        $Message = $Client.UploadFile($LocalPath, $RemotePath, $RemoteExists, $CreateRemoteDirectory.IsPresent, $VerifyOptions)
        if ($Message -eq 'success') { $State = $true } else { $State = $false }
        $Status = [PSCustomObject] @{Action = 'UploadFile'
            LocalPath                       = $LocalPath
            RemotePath                      = $RemotePath
            Status                          = $State
            Message                         = $Message
        }
    } catch {
        $Status = [PSCustomObject] @{Action = 'UploadFile'
            LocalPath                       = $LocalPath
            RemotePath                      = $RemotePath
            Status                          = $false
            Message                         = "Error: $($_.Exception.Message)"
        }
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Add-PrivateFTPFile - Error: $($_.Exception.Message)" }
    }
    $Status
}
function Add-PrivateFTPFiles {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $RemotePath,
        [string[]] $LocalPath,
        [System.IO.FileInfo[]] $LocalFile,
        [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip,
        [FluentFTP.FtpVerify] $VerifyOptions = [FluentFTP.FtpVerify]::None,
        [FluentFTP.FtpError] $ErrorHandling = [FluentFTP.FtpError]::None,
        [switch] $CreateRemoteDirectory)
    try {
        if ($LocalFile) { $Message = $Client.UploadFiles([System.IO.FileInfo[]] $LocalFile, $RemotePath, $RemoteExists, $CreateRemoteDirectory.IsPresent, $VerifyOptions, $ErrorHandling) } else { $Message = $Client.UploadFiles([string[]] $LocalPath, $RemotePath, $RemoteExists, $CreateRemoteDirectory.IsPresent, $VerifyOptions, $ErrorHandling) }
        if ($Message -gt 1) { $State = $true } else { $State = $false }
        $Status = [PSCustomObject] @{Action = 'UploadFile'
            LocalPath                       = $LocalPath
            RemotePath                      = $RemotePath
            Status                          = $State
            Message                         = $Message
        }
    } catch {
        $Status = [PSCustomObject] @{Action = 'UploadFile'
            LocalPath                       = $LocalPath
            RemotePath                      = $RemotePath
            Status                          = $false
            Message                         = "Error: $($_.Exception.Message)"
        }
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Add-PrivateFTPFiles - Error: $($_.Exception.Message)" }
    }
    $Status
}
function Get-PrivateFTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $LocalPath,
        [FluentFTP.FtpListItem] $RemoteFile,
        [string] $RemotePath,
        [FluentFTP.FtpLocalExists] $LocalExists,
        [FluentFTP.FtpVerify[]] $VerifyOptions,
        [FluentFTP.FtpError] $FtpError)
    if ($RemoteFile) {
        if ($RemoteFile.Type -eq 'File') { $FileToDownload = $RemoteFile.FullName } else {
            if (-not $Suppress) {
                return [PSCustomObject] @{Action = 'DownloadFile'
                    Status                       = $false
                    LocalPath                    = $LocalPath
                    RemotePath                   = $RemoteFile.FullName
                    Message                      = "Receive-FTPFile - Given path $($RemoteFile.FullName) is $($RemoteFile.Type). Skipping."
                }
            } else {
                Write-Warning "Receive-FTPFile - Given path $($RemoteFile.FullName) is a directory. Skipping."
                return
            }
        }
    } else { $FileToDownload = $RemotePath }
    try {
        $Message = $Client.DownloadFile($LocalPath, $FileToDownload, $LocalExists, $VerifyOptions)
        if ($Message -eq 'success') { $State = $true } else { $State = $false }
        $Status = [PSCustomObject] @{Action = 'DownloadFile'
            Status                          = $State
            LocalPath                       = $LocalPath
            RemotePath                      = $FileToDownload
            Message                         = $Message
        }
    } catch {
        $Status = [PSCustomObject] @{Action = 'DownloadFile'
            Status                          = $false
            LocalPath                       = $LocalPath
            RemotePath                      = $FileToDownload
            Message                         = "Error: $($_.Exception.Message)"
        }
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Receive-FTPFile - Error: $($_.Exception.Message)" }
    }
    $Status
}
function Get-PrivateFTPFiles {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $LocalPath,
        [FluentFTP.FtpListItem[]] $RemoteFile,
        [string[]] $RemotePath,
        [FluentFTP.FtpLocalExists] $LocalExists,
        [FluentFTP.FtpVerify[]] $VerifyOptions,
        [FluentFTP.FtpError] $FtpError)
    if ($RemoteFile) {
        $FileToDownload = foreach ($File in $RemoteFile) {
            if ($File.Type -eq 'File') { $File.FullName } else {
                if (-not $Suppress) {
                    [PSCustomObject] @{Action = 'DownloadFile'
                        Status                = $false
                        LocalPath             = $LocalPath
                        RemotePath            = $File.FullName
                        Message               = "Receive-FTPFile - Given path $($RemoteFile.FullName) is $($RemoteFile.Type). Skipping."
                    }
                } else { Write-Warning "Receive-FTPFile - Given path $($RemoteFile.FullName) is a directory. Skipping." }
            }
        }
    } else { $FileToDownload = $RemotePath }
    try {
        $Message = $Client.DownloadFiles($LocalPath, ([string[]] $FileToDownload), $LocalExists, $VerifyOptions, $FtpError)
        if ($Message -gt 0) { $State = $true } else { $State = $false }
        $Status = [PSCustomObject] @{Action = 'DownloadFile'
            Status                          = $State
            LocalPath                       = $LocalPath
            RemotePath                      = $FileToDownload
            Message                         = $Message
        }
    } catch {
        $Status = [PSCustomObject] @{Action = 'DownloadFile'
            Status                          = $false
            LocalPath                       = $LocalPath
            RemotePath                      = $FileToDownload
            Message                         = "Error: $($_.Exception.Message)"
        }
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Receive-FTPFile - Error: $($_.Exception.Message)" }
    }
    $Status
}
function Compare-FTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $LocalPath,
        [Parameter(Mandatory)][string] $RemotePath,
        [FluentFTP.FtpCompareOption] $CompareOption = [FluentFTP.FtpCompareOption]::Auto)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.CompareFile($LocalPath, $RemotePath, $CompareOption) }
}
function Connect-FTP {
    [cmdletBinding(DefaultParameterSetName = 'Password')]
    param([Parameter(ParameterSetName = 'FtpProfile')]
        [FluentFTP.FtpProfile] $FtpProfile,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [string] $Server,
        [Parameter(ParameterSetName = 'ClearText')]
        [string] $Username,
        [Parameter(ParameterSetName = 'ClearText')]
        [string] $Password,
        [Parameter(ParameterSetName = 'Password')]
        [pscredential] $Credential,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [FluentFTP.FtpEncryptionMode[]] $EncryptionMode,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [FluentFTP.FtpDataConnectionType] $DataConnectionType,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [FluentFTP.FtpsBuffering] $SslBuffering,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [switch] $DisableDataConnectionEncryption,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [switch] $DisableValidateCertificateRevocation,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [switch] $ValidateAnyCertificate,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [int] $Port,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [switch] $SendHost,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [switch] $SocketKeepAlive,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [switch] $AutoConnect)
    if ($FtpProfile) {
        $Client = [FluentFTP.FtpClient]::new()
        $Client.LoadProfile($FtpProfile)
    } else {
        $Client = [FluentFTP.FtpClient]::new($Server)
        if ($Username -and $Password) { $Client.Credentials = [System.Net.NetworkCredential]::new($Username, $Password) } elseif ($Credential) { $Client.Credentials = [System.Net.NetworkCredential]::new($Credential.Username, $Credential.Password) } else {}
    }
    if ($Port) { $Client.Port = $Port }
    if ($DataConnectionType) { $Client.DataConnectionType = $DataConnectionType }
    if ($DisableDataConnectionEncryption) { $Client.DataConnectionEncryption = $false }
    if ($EncryptionMode) { $Client.EncryptionMode = $EncryptionMode }
    if ($ValidateAnyCertificate) { $Client.ValidateAnyCertificate = $true }
    if ($DisableValidateCertificateRevocation) { $Client.ValidateCertificateRevocation = $false }
    if ($SendHost) { $Client.SendHost = $true }
    if ($SocketKeepAlive) { $Client.SocketKeepAlive = $true }
    if ($FtpsBuffering) { $Client.SslBuffering = $SslBuffering }
    try {
        if ($AutoConnect) {
            $TempFtpProfile = $Client.AutoConnect()
            if ($TempFtpProfile -and $Client.IsConnected) {
                Write-Verbose "Following options where used to autoconnect: "
                foreach ($Name in $TempFtpProfile.PSObject.Properties.Name) { Write-Verbose "[x] $Name -> $($TempFtpProfile.$Name)" }
            }
        } else { $Client.Connect() }
        $Client | Add-Member -Name 'Error' -Value $null -Force -MemberType NoteProperty
    } catch {
        $Client | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Connect-FTP - Error: $($_.Exception.Message)" }
    }
    $Client
}
function Connect-SFTP {
    [cmdletBinding(DefaultParameterSetName = 'Password')]
    param([Parameter(ParameterSetName = 'ClearText', Mandatory)]
        [Parameter(ParameterSetName = 'Password', Mandatory)]
        [Parameter(ParameterSetName = 'PrivateKey', Mandatory)]
        [string] $Server,
        [Parameter(ParameterSetName = 'ClearText', Mandatory)]
        [Parameter(ParameterSetName = 'PrivateKey', Mandatory)]
        [string] $Username,
        [Parameter(ParameterSetName = 'ClearText', Mandatory)]
        [string] $Password,
        [Parameter(ParameterSetName = 'Password', Mandatory)]
        [pscredential] $Credential,
        [Parameter(Mandatory, ParameterSetName = 'PrivateKey')]
        [string] $PrivateKey,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [Parameter(ParameterSetName = 'PrivateKey')]
        [int] $Port)
    try {
        if ($Username -and $Password) { $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Username, $Password) } elseif ($Credential) { $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Credential.Username, $Credential.GetNetworkCredential().Password) } elseif ($PrivateKey) {
            if (Test-Path -LiteralPath $PrivateKey) {
                [string]$PrivateKey = Resolve-Path -LiteralPath $PrivateKey -ErrorAction Stop | Select-Object -ExpandProperty ProviderPath
                $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Username, [Renci.SshNet.PrivateKeyFile]$PrivateKey)
            } else {
                if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                    throw "PrivateKey $PrivateKey doesn't exists."
                    return
                } else {
                    Write-Warning "Connect-SFTP - PrivateKey $PrivateKey doesn't exists."
                    return
                }
            }
        } else {
            throw 'Not implemented and unexepected.'
            return
        }
    } catch {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Connect-SFTP - Error: $($_.Exception.Message)" }
    }
    if ($Port) { $SftpClient.Port = $Port }
    try {
        $SftpClient.Connect()
        $SftpClient | Add-Member -Name 'Error' -Value $null -Force -MemberType NoteProperty
    } catch {
        $SftpClient | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Connect-SFTP - Error: $($_.Exception.Message)" }
    }
    $SftpClient
}
function Connect-SSH {
    [cmdletBinding(DefaultParameterSetName = 'Password')]
    param([Parameter(Mandatory, ParameterSetName = 'ClearText')]
        [Parameter(Mandatory, ParameterSetName = 'Password')]
        [Parameter(Mandatory, ParameterSetName = 'PrivateKey')]
        [string] $Server,
        [Parameter(Mandatory, ParameterSetName = 'ClearText')]
        [Parameter(Mandatory, ParameterSetName = 'PrivateKey')]
        [string] $Username,
        [Parameter(Mandatory, ParameterSetName = 'ClearText')]
        [string] $Password,
        [Parameter(Mandatory, ParameterSetName = 'Password')]
        [pscredential] $Credential,
        [Parameter(Mandatory, ParameterSetName = 'PrivateKey')]
        [string] $PrivateKey,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [Parameter(ParameterSetName = 'PrivateKey')]
        [int] $Port)
    if ($Username -and $Password) { $SshClient = [Renci.SshNet.SshClient]::new($Server, $Username, $Password) } elseif ($Credential) { $SshClient = [Renci.SshNet.SshClient]::new($Server, $Credential.Username, $Credential.GetNetworkCredential().Password) } elseif ($PrivateKey) {
        [string]$PrivateKey = Resolve-Path $PrivateKey | Select-Object -ExpandProperty ProviderPath
        $SshClient = [Renci.SshNet.SshClient]::new($Server, $Username, [Renci.SshNet.PrivateKeyFile]$PrivateKey)
    } else { throw 'Not implemented and unexpected.' }
    if ($Port) { $SshClient.Port = $Port }
    try {
        $SshClient.Connect()
        $SshClient | Add-Member -Name 'Error' -Value $null -Force -MemberType NoteProperty
    } catch {
        $SshClient | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Connect-SSH - Error: $($_.Exception.Message)" }
    }
    $SshClient
}
function Disconnect-FTP {
    [cmdletBinding()]
    param([FluentFTP.FtpClient] $Client)
    if ($Client -and $Client.IsConnected) {
        try { $Client.Disconnect() } catch {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Write-Error $_
                return
            } else { Write-Warning "Disconnect-FTP - Error: $($_.Exception.Message)" }
        }
    }
}
function Disconnect-SFTP {
    [cmdletBinding()]
    param([Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient)
    if ($SftpClient -and $SftpClient.IsConnected) {
        try { $SftpClient.Disconnect() } catch {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Write-Error $_
                return
            } else { Write-Warning "Disconnect-SFTP - Error: $($_.Exception.Message)" }
        }
    }
}
function Get-FTPChecksum {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $RemotePath,
        [FluentFTP.FtpHashAlgorithm] $HashAlgorithm = [FluentFTP.FtpHashAlgorithm]::MD5)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.GetChecksum($RemotePath, $HashAlgorithm) }
}
function Get-FTPChmod {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $RemotePath)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.GetChmod($RemotePath) }
}
function Get-FTPList {
    [cmdletBinding()]
    param([alias('FtpPath')][string] $Path,
        [FluentFTP.FtpListOption] $Options,
        [Parameter(Mandatory)][FluentFTP.FtpClient] $Client)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) {
        try { if ($Path -and $Options) { $Client.GetListing($Path, $Options) } elseif ($Path) { $Client.GetListing($Path) } else { $Client.GetListing() } } catch {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Write-Error $_
                return
            } else { Write-Warning "Get-FTPList - Error: $($_.Exception.Message)" }
        }
    } else { Write-Warning "Get-FTPList - Skipped (IsConnected $($Client.IsConnected) / Error: $($Client.Error))" }
}
function Get-SFTPList {
    [cmdletBinding()]
    param([alias('FtpPath')][string] $Path,
        [Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient)
    if ($SftpClient -and $SftpClient.IsConnected -and -not $SftpClient.Error) {
        try { if ($Path) { $SftpClient.ListDirectory($Path) } else { $SftpClient.ListDirectory('') } } catch {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Write-Error $_
                return
            } else { Write-Warning "Get-SFTPList - Error: $($_.Exception.Message)" }
        }
    } else { Write-Warning "Get-SFTPList - Skipped (IsConnected $($SftpClient.IsConnected) / Error: $($SftpClient.Error))" }
}
function Move-FTPDirectory {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $RemoteSource,
        [Parameter(Mandatory)][string] $RemoteDestination,
        [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.MoveDirectory($RemoteSource, $RemoteDestination, $RemoteExists) }
}
function Move-FTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $RemoteSource,
        [Parameter(Mandatory)][string] $RemoteDestination,
        [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.MoveFile($RemoteSource, $RemoteDestination, $RemoteExists) }
}
function Receive-FTPDirectory {
    [alias('Get-FTPDirectory')]
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $LocalPath,
        [Parameter(Mandatory)][string] $RemotePath,
        [FluentFTP.FtpFolderSyncMode] $FolderSyncMode = [FluentFTP.FtpFolderSyncMode]::Update,
        [FluentFTP.FtpLocalExists] $LocalExists,
        [FluentFTP.FtpVerify] $VerifyOptions,
        [FluentFTP.Rules.FtpRule[]] $Rules)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.DownloadDirectory($LocalPath, $RemotePath, $FolderSyncMode) }
}
function Receive-FTPFile {
    [alias('Get-FTPFile')]
    [cmdletBinding(DefaultParameterSetName = 'Text')]
    param([Parameter(ParameterSetName = 'Text')]
        [Parameter(ParameterSetName = 'Native')]
        [Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(ParameterSetName = 'Native')]
        [FluentFTP.FtpListItem[]] $RemoteFile,
        [Parameter(ParameterSetName = 'Text')]
        [Parameter(ParameterSetName = 'Native')]
        [string[]] $RemotePath,
        [Parameter(ParameterSetName = 'Text')]
        [Parameter(ParameterSetName = 'Native')]
        [string] $LocalPath,
        [Parameter(ParameterSetName = 'Text')]
        [Parameter(ParameterSetName = 'Native')]
        [FluentFTP.FtpLocalExists] $LocalExists = [FluentFTP.FtpLocalExists]::Skip,
        [Parameter(ParameterSetName = 'Text')]
        [Parameter(ParameterSetName = 'Native')]
        [FluentFTP.FtpVerify[]] $VerifyOptions = [FluentFTP.FtpVerify]::None,
        [Parameter(ParameterSetName = 'Text')]
        [Parameter(ParameterSetName = 'Native')]
        [FluentFTP.FtpError] $FtpError = [FluentFTP.FtpError]::Stop,
        [Parameter(ParameterSetName = 'Text')]
        [Parameter(ParameterSetName = 'Native')]
        [switch] $Suppress)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) {
        $Path = Get-Item -LiteralPath $LocalPath -ErrorAction SilentlyContinue
        if ($Path -is [System.IO.DirectoryInfo]) { Get-PrivateFTPFiles -Client $Client -LocalPath $LocalPath -RemoteFile $RemoteFile -RemotePath $RemotePath -LocalExists $LocalExists -VerifyOptions $VerifyOptions -FtpError $FtpError } else {
            if ($RemoteFile.Count -gt 1 -or $RemotePath.Count -gt 1) {
                Write-Warning "Receive-FTPFile - Multiple files detected, but $LocalPath is not a directory or directory doesn't exists. "
                if ($RemoteFile) { $FileToDownload = $RemoteFile.FullName } else { $FileToDownload = $RemotePath }
                $Status = [PSCustomObject] @{Action = 'DownloadFile'
                    Status                          = $false
                    LocalPath                       = $LocalPath
                    RemotePath                      = $FileToDownload
                    Message                         = "Multiple files detected, but $LocalPath is not a directory or directory doesn't exists."
                }
            } else {
                $Splat = @{Client = $Client
                    LocalExists   = $LocalExists
                    VerifyOptions = $VerifyOptions
                    FtpError      = $FtpError
                    LocalPath     = $LocalPath
                }
                if ($RemoteFile) { $Splat.RemoteFile = $RemoteFile[0] } else { $Splat.RemotePath = $RemotePath[0] }
                Get-PrivateFTPFile @Splat
            }
        }
    } else {
        $Status = [PSCustomObject] @{Action = 'DownloadFile'
            Status                          = $false
            LocalPath                       = $LocalPath
            RemotePath                      = $FileToDownload
            Message                         = "Not connected."
        }
    }
    if (-not $Suppress) { $Status }
}
function Receive-SFTPFile {
    [alias('Get-SFTPFile')]
    [cmdletBinding()]
    param([Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient,
        [string] $RemotePath,
        [string] $LocalPath)
    if ($SftpClient -and $SftpClient.IsConnected) {
        try {
            $FileStream = [System.IO.FileStream]::new($LocalPath, [System.IO.FileMode]::OpenOrCreate)
            $SftpClient.DownloadFile($RemotePath, $FileStream)
            $Status = [PSCustomObject] @{Action = 'DownloadFile'
                Status                          = $true
                LocalPath                       = $LocalPath
                RemotePath                      = $RemotePath
                Message                         = ""
            }
        } catch {
            $Status = [PSCustomObject] @{Action = 'DownloadFile'
                Status                          = $false
                LocalPath                       = $LocalPath
                RemotePath                      = $RemotePath
                Message                         = "Error: $($_.Exception.Message)"
            }
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Write-Error $_
                return
            } else { Write-Warning "Receive-SFTPFile - Error: $($_.Exception.Message)" }
        } finally {
            $FileStream.Close()
            if ($Status.Status -eq $false) { Remove-Item -LiteralPath $LocalPath }
        }
        $Status
    }
}
function Remove-FTPDirectory {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $RemotePath,
        [FluentFTP.FtpListOption] $FtpListOption)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { if (-not $FtpListOption) { $Client.DeleteDirectory($RemotePath) } else { $Client.DeleteDirectory($RemotePath, $FtpListOption) } }
}
function Remove-FTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $RemotePath)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.DeleteFile($RemotePath) }
}
function Remove-SFTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient,
        [string] $RemotePath,
        [switch] $Suppress)
    if ($SftpClient -and $SftpClient.IsConnected) {
        try {
            $SftpClient.DeleteFile($RemotePath)
            $Status = [PSCustomObject] @{Action = 'RemoveFile'
                Status                          = $true
                Message                         = ""
            }
        } catch {
            $Status = [PSCustomObject] @{Action = 'RemoveFile'
                Status                          = $false
                Message                         = "Error $($_.Exception.Message)"
            }
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Write-Error $_
                return
            } else { Write-Warning "Remove-SFTPFile - Error: $($_.Exception.Message)" }
        }
        if (-not $Suppress) { $Status }
    }
}
function Rename-FTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $Path,
        [Parameter(Mandatory)][string] $DestinationPath)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.Rename($Path, $DestinationPath) }
}
function Rename-SFTPFile {
    <#
    .SYNOPSIS
    Allows renaming remote file over SFTP protocol
 
    .DESCRIPTION
    Allows renaming remote file over SFTP protocol
 
    .PARAMETER SftpClient
    Parameter that contains the SFTP client object
 
    .PARAMETER SourcePath
    Path to file that is going to be renamed
 
    .PARAMETER DestinationPath
    New path to file, with new name
 
    .PARAMETER Suppress
    Suppress returning an object with information about the operation
 
    .EXAMPLE
    $SftpClient = Connect-SFTP -Server '192.168.240.29' -Username 'przemek' -Password 'Password'
    Rename-SFTPFile -SftpClient $SftpClient -SourcePath '/home/przemek/mmm.txt' -DestinationPath '/home/przemek/mmm1.txt'
    Disconnect-SFTP -SftpClient $SftpClient
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient,
        [alias('OldPath')][string] $SourcePath,
        [alias('NewPath')][string] $DestinationPath,
        [switch] $Suppress)
    if ($SftpClient -and $SftpClient.IsConnected) {
        try {
            $SftpClient.RenameFile($SourcePath, $DestinationPath)
            $Status = [PSCustomObject] @{Action = 'RenameFile'
                Status                          = $true
                OldPath                         = $SourcePath
                NewPath                         = $DestinationPath
                Message                         = ""
            }
        } catch {
            $Status = [PSCustomObject] @{Action = 'RenameFile'
                Status                          = $false
                OldPath                         = $SourcePath
                NewPath                         = $DestinationPath
                Message                         = "Error $($_.Exception.Message)"
            }
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Write-Error $_
                return
            } else { Write-Warning "Rename-SFTPFile - Error: $($_.Exception.Message)" }
        }
        if (-not $Suppress) { $Status }
    }
}
function Request-FTPConfiguration {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Automatically discover working FTP connection settings and return those connection profiles. This method will try every possible connection type combination in a loop until it finds a working combination, and it will return the first found combination or all found combinations. The connection types are tried in this order of preference.
 
    Auto connection attempts to find working connection settings in this order of preference:
 
    Protocol Preference:
    1. None - Let the OS decide which TLS/SSL version to use
    2. Tls12 - TLS 1.2 (TLS 1.3 is not yet stable in .NET Framework)
    3. Tls11 - TLS 1.1
    4. Tls - TLS 1.0
    5. Ssl3 - SSL 3.0 (obsolete, need to use TLS instead)
    6. Ssl2 - SSL 2.0 (obsolete, need to use TLS instead)
    7. Default - Undefined/weird behaviour
 
    Data Connection Type Preference:
 
    1. PASV - We prefer passive as its the most reliable
    2. EPSV - Enhanced passive is not as well supported on servers
    3. PORT - PORT is an older connection type
    4. EPRT - Enhanced PORT is not as well supported on servers
    5. PASVEX
 
    Encoding Type Preference:
 
    1. UTF8 - We prefer Unicode encoding as there will be no issues with file and folder names
    2. ASCII - ASCII/ANSI is a fallback used for older servers
 
    .PARAMETER Server
    Server Name or IP Address to Connect
 
    .PARAMETER Username
    UserName for FTP Connection
 
    .PARAMETER Password
    Password for FTP Connection (cleartext)
 
    .PARAMETER Credential
    UserName and Password in form of Credentials
 
    .PARAMETER FirstOnly
    Returns first working profile
 
    .EXAMPLE
    # Login via UserName/Password
    $ProfileFtp1 = Request-FTPConfiguration -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password'
    $ProfileFtp1 | Format-Table
 
    .EXAMPLE
    # Anonymous login
    $ProfileFtp2 = Request-FTPConfiguration -Server 'speedtest.tele2.net' -Verbose
    $ProfileFtp2 | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'Password')]
    param([Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [string] $Server,
        [Parameter(ParameterSetName = 'ClearText')]
        [string] $Username,
        [Parameter(ParameterSetName = 'ClearText')]
        [string] $Password,
        [Parameter(ParameterSetName = 'Password')]
        [pscredential] $Credential,
        [Parameter(ParameterSetName = 'ClearText')]
        [Parameter(ParameterSetName = 'Password')]
        [switch] $FirstOnly)
    $Client = [FluentFTP.FtpClient]::new($Server)
    if ($Username -and $Password) { $Client.Credentials = [System.Net.NetworkCredential]::new($Username, $Password) } elseif ($Credential) { $Client.Credentials = [System.Net.NetworkCredential]::new($Credential.Username, $Credential.Password) } else {}
    try { $Client.AutoDetect($FirstOnly.IsPresent) } catch {
        $Client | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        } else { Write-Warning "Request-FTPConfiguration - Error: $($_.Exception.Message)" }
    }
}
function Send-FTPDirectory {
    [alias('Add-FTPDirectory')]
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $LocalPath,
        [Parameter(Mandatory)][string] $RemotePath,
        [FluentFTP.FtpFolderSyncMode] $FolderSyncMode = [FluentFTP.FtpFolderSyncMode]::Update,
        [FluentFTP.FtpRemoteExists] $RemoteExists,
        [FluentFTP.FtpVerify] $VerifyOptions,
        [FluentFTP.Rules.FtpRule[]] $Rules)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.UploadDirectory($LocalPath, $RemotePath, $FolderSyncMode) }
}
function Send-FTPFile {
    [alias('Add-FTPFile')]
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [string] $RemotePath,
        [System.IO.FileInfo[]] $LocalFile,
        [string[]] $LocalPath,
        [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip,
        [FluentFTP.FtpVerify] $VerifyOptions = [FluentFTP.FtpVerify]::None,
        [FluentFTP.FtpError] $ErrorHandling = [FluentFTP.FtpError]::None,
        [switch] $CreateRemoteDirectory)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) {
        if ($LocalPath.Count -gt 1 -or $LocalFile.Count -gt 1) {
            $Splat = @{Client         = $Client
                RemoteExists          = $RemoteExists
                VerifyOptions         = $VerifyOptions
                LocalPath             = $LocalPath
                LocalFile             = $LocalFile
                RemotePath            = $RemotePath
                CreateRemoteDirectory = $CreateRemoteDirectory.IsPresent
                ErrorHandling         = $ErrorHandling
            }
            Remove-EmptyValue -Hashtable $Splat
            $Status = Add-PrivateFTPFiles @Splat
            $Status
        } else {
            foreach ($Path in $LocalPath) {
                $Splat = @{Client         = $Client
                    RemoteExists          = $RemoteExists
                    VerifyOptions         = $VerifyOptions
                    LocalPath             = $Path
                    RemotePath            = $RemotePath
                    CreateRemoteDirectory = $CreateRemoteDirectory.IsPresent
                }
                $Status = Add-PrivateFTPFile @Splat
                $Status
            }
        }
    }
}
function Send-SFTPFile {
    [alias('Add-SFTPFile')]
    [cmdletBinding()]
    param([Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient,
        [string] $RemotePath,
        [string] $LocalPath,
        [switch] $AllowOverride)
    if ($SftpClient -and $SftpClient.IsConnected) {
        if (Test-Path -LiteralPath $LocalPath) {
            try {
                $FileStream = [System.IO.FileStream]::new($LocalPath, [System.IO.FileMode]::OpenOrCreate)
                $SftpClient.UploadFile($FileStream, $RemotePath, $AllowOverride)
                $Status = [PSCustomObject] @{Action = 'UploadFile'
                    Status                          = $true
                    LocalPath                       = $LocalPath
                    RemotePath                      = $RemotePath
                    Message                         = ""
                }
            } catch {
                $Status = [PSCustomObject] @{Action = 'UploadFile'
                    Status                          = $false
                    LocalPath                       = $LocalPath
                    RemotePath                      = $RemotePath
                    Message                         = "Error: $($_.Exception.Message)"
                }
                if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                    Write-Error $_
                    return
                } else { Write-Warning "Send-SFTPFile - Error: $($_.Exception.Message)" }
            } finally { $FileStream.Close() }
        } else {
            Write-Warning "Send-SFTPFile - File $LocalPath doesn't exists."
            $Status = [PSCustomObject] @{Action = 'UploadFile'
                Status                          = $false
                LocalPath                       = $LocalPath
                RemotePath                      = $RemotePath
                Message                         = "LocalPath doesn't exists $LocalPath"
            }
        }
        $Status
    }
}
function Send-SSHCommand {
    [cmdletBinding()]
    param([Parameter(Mandatory)][Renci.SshNet.SshClient] $SshClient,
        [scriptblock] $Command,
        [switch] $Status)
    if ($SshClient -and $SshClient.IsConnected -and -not $SshClient.Error) {
        if ($Command) {
            $CommandsToExecute = & $Command
            [string] $SendCommand = foreach ($C in $CommandsToExecute) { if ($C.Trim().EndsWith(';')) { $C } else { "$C;" } }
            try {
                Write-Verbose -Message "Send-SSHCommand - Executing command: $SendCommand"
                if ($Status) {
                    [PSCustomObject] @{Status = $true
                        Output                = $SshClient.CreateCommand($SendCommand).Execute()
                        Error                 = $null
                    }
                } else { $SshClient.CreateCommand($SendCommand).Execute() }
            } catch {
                if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                    Write-Error $_
                    return
                } else { Write-Warning "Send-SSHCommand - Error: $($_.Exception.Message)" }
                if ($Status) {
                    [PSCustomObject] @{Status = $false
                        Output                = ''
                        Error                 = "Error: $($_.Exception.Message)"
                    }
                }
            }
        }
    }
}
function Set-FTPChmod {
    [cmdletBinding(DefaultParameterSetName = 'ByInt')]
    param([Parameter(Mandatory, ParameterSetName = 'ByInt')]
        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory, ParameterSetName = 'ByInt')]
        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [string] $RemotePath,
        [Parameter(Mandatory, ParameterSetName = 'ByInt')]
        [nullable[int]] $Permissions,
        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [FluentFTP.FtpPermission] $Owner,
        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [FluentFTP.FtpPermission] $Group,
        [Parameter(Mandatory, ParameterSetName = 'Explicit')]
        [FluentFTP.FtpPermission] $Other)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { if ($Permissions) { $Client.Chmod($RemotePath, $Permissions) } else { $Client.Chmod($RemotePath, $Owner, $Group, $Other) } }
}
function Set-FTPOption {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [nullable[int]] $RetryAttempts,
        [nullable[bool]] $DownloadZeroByteFiles)
    if ($RetryAttempts) { $Client.RetryAttempts = $RetryAttempts }
    if ($DownloadZeroByteFiles) { $Client.DownloadZeroByteFiles = $DownloadZeroByteFiles }
}
function Set-FTPTracing {
    [cmdletBinding()]
    param([string] $LogPath,
        [switch] $Enable,
        [switch] $Disable,
        [switch] $ShowPassword,
        [switch] $ShowUsername,
        [switch] $HideIP,
        [switch] $HideFunctions,
        [switch] $DisplayConsole)
    if ($Enable) { [FluentFTP.FtpTrace]::EnableTracing = $true }
    if ($Disable) { [FluentFTP.FtpTrace]::EnableTracing = $false }
    if ($LogPath) { [FluentFTP.FtpTrace]::LogToFile = $LogPath }
    if (-not $HideFunctions) { [FluentFTP.FtpTrace]::LogFunctions = $true }
    if ($DisplayConsole) { [FluentFTP.FtpTrace]::LogToConsole = $true }
    if ($ShowUsername) { [FluentFTP.FtpTrace]::LogUserName = $true }
    if ($ShowPassword) { [FluentFTP.FtpTrace]::LogPassword = $false }
    if (-not $HideIP) { [FluentFTP.FtpTrace]::LogIP = $true }
}
function Start-FXPDirectoryTransfer {
    [alias('Start-FXPDirectory')]
    [cmdletBinding()]
    param([alias('SourceClient')][Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $SourcePath,
        [Parameter(Mandatory)][FluentFTP.FtpClient] $DestinationClient,
        [Parameter(Mandatory)][string] $DestinationPath,
        [FluentFTP.FtpFolderSyncMode] $FolderSyncMode = [FluentFTP.FtpFolderSyncMode]::Update,
        [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip,
        [FluentFTP.FtpVerify[]] $VerifyOptions = [FluentFTP.FtpVerify]::None)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.TransferDirectory($SourcePath, $DestinationClient, $DestinationPath, $FolderSyncMode, $RemoteExists, $VerifyOptions) }
}
function Start-FXPFileTransfer {
    [alias('Start-FXPFile')]
    [cmdletBinding()]
    param([alias('SourceClient')][Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $SourcePath,
        [Parameter(Mandatory)][FluentFTP.FtpClient] $DestinationClient,
        [Parameter(Mandatory)][string] $DestinationPath,
        [switch] $CreateRemoteDirectory,
        [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip,
        [FluentFTP.FtpVerify[]] $VerifyOptions = [FluentFTP.FtpVerify]::None)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.TransferFile($SourcePath, $DestinationClient, $DestinationPath, $CreateRemoteDirectory.IsPresent, $RemoteExists, $VerifyOptions) }
}
function Test-FTPDirectory {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $RemotePath)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.DirectoryExists($RemotePath) }
}
function Test-FTPFile {
    [cmdletBinding()]
    param([Parameter(Mandatory)][FluentFTP.FtpClient] $Client,
        [Parameter(Mandatory)][string] $RemotePath)
    if ($Client -and $Client.IsConnected -and -not $Client.Error) { $Client.FileExists($RemotePath) }
}
Export-ModuleMember -Function @('Compare-FTPFile', 'Connect-FTP', 'Connect-SFTP', 'Connect-SSH', 'Disconnect-FTP', 'Disconnect-SFTP', 'Get-FTPChecksum', 'Get-FTPChmod', 'Get-FTPList', 'Get-SFTPList', 'Move-FTPDirectory', 'Move-FTPFile', 'Receive-FTPDirectory', 'Receive-FTPFile', 'Receive-SFTPFile', 'Remove-FTPDirectory', 'Remove-FTPFile', 'Remove-SFTPFile', 'Rename-FTPFile', 'Rename-SFTPFile', 'Request-FTPConfiguration', 'Send-FTPDirectory', 'Send-FTPFile', 'Send-SFTPFile', 'Send-SSHCommand', 'Set-FTPChmod', 'Set-FTPOption', 'Set-FTPTracing', 'Start-FXPDirectoryTransfer', 'Start-FXPFileTransfer', 'Test-FTPDirectory', 'Test-FTPFile') -Alias @('Add-FTPDirectory', 'Add-FTPFile', 'Add-SFTPFile', 'Get-FTPDirectory', 'Get-FTPFile', 'Get-SFTPFile', 'Start-FXPDirectory', 'Start-FXPFile')
# SIG # Begin signature block
# MIIdWQYJKoZIhvcNAQcCoIIdSjCCHUYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUGxDDeDiH4zoScAFsi7VwP9vM
# DBagghhnMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAhzhQA8N0wDQYJKoZIhvcNAQELBQAw
# cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk
# IElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAxMDYwMDAw
# MDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4G
# A1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjEwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXKY2tR1zoRQr0KdXVNlLQMULUmEP4d
# yG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj/TZqgfop+N0rcIXeAhjzeG28ffnH
# bQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2j8aj4S8bOrdh1nPsTm0zinxdRS1L
# sVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDrDo4s1SPa9E14RuMDgzEpSlwMMYpK
# jIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1uxHTDCm2mCtNv8VlS8H6GHq756Wwog
# L0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1JhL0BAgMBAAGjggG4MIIBtDAOBgNV
# HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGlsqIl
# ssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwoLR1ENt3janq8MHEGA1UdHwRqMGgw
# MqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMu
# Y3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk
# LXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURUaW1lc3RhbXBpbmdDQS5jcnQw
# DQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO85xrnIA6OZ0b9QnJRdAojR6OrktIl
# xHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQCZk3taaQP9rhwz2Lo9VFKeHk2eie3
# 8+dSn5On7UOee+e03UEiifuHokYDTvz0/rdkd2NfI1Jpg4L6GlPtkMyNoRdzDfTz
# ZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO+MDhXM/EEXLnG2RJ2CKadRVC9S0y
# OIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j1h9bm/cuG08THfdKDXF+l7f0P4Tr
# weOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5NpjhyyjaW4emii8wggUwMIIEGKADAgEC
# AhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEw
# MjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7
# RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p
# 0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj
# 6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grk
# V7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHy
# DxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMB
# AAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT
# BgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG
# GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh
# Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB
# gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgG
# CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
# Y29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1
# DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEL
# BQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q
# 3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/
# kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dc
# IFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6
# dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT
# +hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHk
# Bdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMT
# G0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0z
# MTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0
# IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5
# fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb
# 6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU
# 46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mI
# UF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfx
# FwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAd
# BgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SS
# y4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j
# cnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkw
# RzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj
# ZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLp
# UYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQd
# aq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC
# 4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+
# tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6H
# USHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIv
# IjayS6JKldj1po5SMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkq
# hkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT
# SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoX
# DTIzMDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tp
# ZTERMA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlz
# IEVWT1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqD
# Bqlnr3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfF
# jVye3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub
# +3tii0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf
# 3tZZzO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6
# Ea41zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQAB
# o4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0O
# BBYEFBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
# DDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2Ny
# bDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUw
# QzA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl
# cnQuY29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNp
# Z25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1s
# z4lsLARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsR
# XPHUF/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHI
# NrTCvPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8
# Rj9yG4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6
# o6ESJre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNp
# ezQug9ufqExx6lHYDjGCBFwwggRYAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYD
# VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAv
# BgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EC
# EATV3B9I6snYUgC6zZqbKqcwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI
# oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB
# CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFAbU+MmX0NvLa36X4lzo
# 16s50SaIMA0GCSqGSIb3DQEBAQUABIIBAFxevUeEsXE5ZRI6seDyK1RzLO+Itp80
# eXNpKjErqgeyl8nUIX8U5GLa7OQHjg4B/ReRpqvwik65r3eipxgh1IZJJA/Ut6Mn
# yFJoXvKbq4taHTFEDuwu7xym+zvpxQF2IPaIscSOsj2UqGP5KS2ojxs1OFkJs/U2
# WbAo+ObChj3DQrjAPEmnxwaW0PAGRu/LrhM38PwDy6oFXNoKUGq/k/ozB7Irig4h
# JEgxcp/LhNdnQNv8gqGqsx6hf4xIqHPB+XGXejzJioOcXo/D5CImbMTpNnc5SbkA
# ldrmd4CDao8e70sJVdQdLE1EJ1CHlXPeQsVJH5u8/jOsD0EsXkkobi+hggIwMIIC
# LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G
# A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ
# DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL
# BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMTAyOTE4NTgzMlowLwYJKoZI
# hvcNAQkEMSIEIDgWhSby4+uJsz4S40BrQ+v8ly8vIRxoVx0tCVhY4aOdMA0GCSqG
# SIb3DQEBAQUABIIBAHnHPP6nUcie2+021E+kt/CSbNT7oDNrKblEYY6pmqdnTyEq
# ++VQuvFFvwmckN1yBBeTVBEKWXOgdr28l2Kwsw8LSn6svoFi6rtc1n0tzVH0z9BF
# c7gmKs+Ye552qAiFLEzccKKBoI36RpMmcYLl8jOSdOyZRmw8BjzF/KqvhrSYZ++f
# kCCi+dyec2oWc3pd3xMP3GdAOUQxaiieS69JmsgXHvHx9e6PNKGm06sM3S6szDMO
# DwWT3Id16Z4VpIQLuXGqw3N6dL/MWTAY5RSe+rA2hxp3dCKPC76ezkQzxIq0SboL
# skJ/qyUR9fuhJfIkd2ToeV0RrgYuggEStGsaM3E=
# SIG # End signature block