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 { [cmdletBinding()] param([Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient, [string] $RemotePath, [switch] $Suppress) if ($SftpClient -and $SftpClient.IsConnected) { try { $SftpClient.RenameFile($RemotePath) $Status = [PSCustomObject] @{Action = 'RenameFile' LocalPath = '' RemotePath = $RemotePath Status = $true Message = "" } } catch { $Status = [PSCustomObject] @{Action = 'RenameFile' Status = $false LocalPath = '' RemotePath = $RemotePath 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 # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU4MaXhwnN0JVSUOtcRuFwMFZj # 3eWgghhnMIIDtzCCAp+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 # CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFGQwG8p0kVx5dgMwXGya # gxrQym+6MA0GCSqGSIb3DQEBAQUABIIBABxCPcw+a7iNw9lvNXyRSlZQwdtVU2PX # pU8jXXMqcU0WCnnIJbwLAFPhF01mXQAiP9cHCfoLSWcLi/rtvis7ylZ2lOZ+QVvY # X/jM0nQ+iJbw2gSii1dAlmjy84XTPRdCGzPCh31p2Bxy5R1i7QyQuvkNFy6TElkl # UYschLYcY+KLkyUQ1U+igni2o4ufIt9bVp304Odg+CUZAxoEu97LYXqq+rlu060O # RLo6kd5ZnPk7++CO/0fzV/q5nZ5W2CidywV7eesRas+PZ4KIYgFVwZyBilhHmwMk # yprhIXZnfvYl+C5CNwl0O1i/gMrQLCKvsp7t1RI13wSUuxKokxN4w06hggIwMIIC # LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G # A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ # DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL # BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMDkxNTE5MjgyMFowLwYJKoZI # hvcNAQkEMSIEIEBeA9OMi+6y+lyHGyn8i+y2TN4Zai5IK8zz1RqJqDBOMA0GCSqG # SIb3DQEBAQUABIIBAMEnczOm40rh978PF8AMrT5V2JDR+E0oJqyBijwxQuTYrSi+ # ykZ8q2/8B7ENaxBnhtstXMHqknEnN6t22BKM6CqvLOGrhy2vIE34BSZB7TsyG1hL # 7QjReCpgQYTfjyzsgTb3XT0eFbqiJB+305K9PdZ9SDaZjeSwn/9bmsiiN13+NSbu # S8hwBHDfMvBFIQpBVowCe/5ABBPaND+BWwcGcgSzeO9QoFimJR+dKSW/OabubOnE # fmjwdTYsmnEAR2pZudefLH5PlSaJONlkfdMDVmNJ5XCj8N8RjGqEGRzL7XJf5ReR # fMjBJdU2eaQhzyj3ff5knStD1Xdv+TLhznY4IUY= # SIG # End signature block |