├── .github └── FUNDING.yml ├── Examples ├── Example09-TestFile.ps1 ├── Example03-AutoDetectFTPConfiguration.ps1 ├── Example05-ConnectWithAutoConnect.ps1 ├── Example04-ConnectWithAutoDetect.ps1 ├── Example08-ConnectSSH.ps1 ├── Example02-BasicExampleSCP.ps1 ├── Example12-Receive-SFTPFile.ps1 ├── Example13-UploadFTPSDirectory.ps1 ├── Example10-SetChmodViaFTP.ps1 ├── Example11-ReceiveFiles.ps1 ├── Example01-BasicExampleFTP.ps1 ├── Example06-UploadFTPS.ps1 ├── Example07-UploadSFTP.ps1 └── Example15-BasicExampleFTPwithProxy.ps1 ├── Public ├── Get-FTPChmod.ps1 ├── Remove-FTPFile.ps1 ├── Test-FTPFile.ps1 ├── Test-FTPDirectory.ps1 ├── Rename-FTPFile.ps1 ├── Get-FTPChecksum.ps1 ├── Set-FTPOption.ps1 ├── Compare-FTPFile.ps1 ├── Move-FTPFile.ps1 ├── Move-FTPDirectory.ps1 ├── Disconnect-FTP.ps1 ├── Remove-FTPDirectory.ps1 ├── Disconnect-SFTP.ps1 ├── Get-SFTPList.ps1 ├── Get-FTPList.ps1 ├── Remove-SFTPFile.ps1 ├── Start-FXPFileTransfer.ps1 ├── Start-FXPDirectoryTransfer.ps1 ├── Set-FTPChmod.ps1 ├── Receive-FTPDirectory.ps1 ├── Receive-SFTPFile.ps1 ├── Send-SSHCommand.ps1 ├── Send-FTPFile.ps1 ├── Send-SFTPFile.ps1 ├── Set-FTPTracing.ps1 ├── Rename-SFTPFile.ps1 ├── Send-FTPDirectory.ps1 ├── Connect-SSH.ps1 ├── Receive-FTPFile.ps1 ├── Connect-SFTP.ps1 ├── Request-FTPConfiguration.ps1 └── Connect-FTP.ps1 ├── Sources ├── Transferetto │ ├── Initialize.cs │ ├── Transferetto.csproj │ └── OnImportAndRemove.cs └── Transferetto.sln ├── Tests ├── Test-FTPDirectory.Tests.ps1 ├── Download-FTP.Tests.ps1 └── Connect-FTP.Tests.ps1 ├── LICENSE ├── Transferetto.AzurePipelines.yml ├── Private ├── Add-PrivateFTPFile.ps1 ├── Add-PrivateFTPFiles.ps1 ├── Get-PrivateFTPFile.ps1 └── Get-PrivateFTPFiles.ps1 ├── Transferetto.psd1 ├── Transferetto.Tests.ps1 ├── CHANGELOG.MD ├── README.md ├── Transferetto.psm1 └── Build └── Manage-Module.ps1 /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: PrzemyslawKlys 4 | custom: https://paypal.me/PrzemyslawKlys -------------------------------------------------------------------------------- /Examples/Example09-TestFile.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | $Client = Connect-FTP -Server '192.168.241.187' -Verbose -Username 'test' -Password 'BiPassword90A' -EncryptionMode Explicit -ValidateAnyCertificate 4 | # List files 5 | Test-FTPFile -Client $Client -RemotePath '/Temporary' -------------------------------------------------------------------------------- /Public/Get-FTPChmod.ps1: -------------------------------------------------------------------------------- 1 | function Get-FTPChmod { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [string] $RemotePath 6 | ) 7 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 8 | $Client.GetChmod($RemotePath) 9 | } 10 | } -------------------------------------------------------------------------------- /Public/Remove-FTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Remove-FTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $RemotePath 6 | ) 7 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 8 | $Client.DeleteFile($RemotePath) 9 | } 10 | } -------------------------------------------------------------------------------- /Public/Test-FTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Test-FTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $RemotePath 6 | ) 7 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 8 | $Client.FileExists($RemotePath) 9 | } 10 | } -------------------------------------------------------------------------------- /Public/Test-FTPDirectory.ps1: -------------------------------------------------------------------------------- 1 | function Test-FTPDirectory { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $RemotePath 6 | ) 7 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 8 | $Client.DirectoryExists($RemotePath) 9 | } 10 | } -------------------------------------------------------------------------------- /Sources/Transferetto/Initialize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Transferetto { 8 | /// This is a dummy class that is used to initialize the module in PowerShell 9 | /// Doesnt do anything at all 10 | public class Initialize { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example03-AutoDetectFTPConfiguration.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | # Login via UserName/Password 4 | $ProfileFtp1 = Request-FTPConfiguration -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' 5 | $ProfileFtp1 | Format-Table 6 | 7 | # Anonymous login 8 | $ProfileFtp2 = Request-FTPConfiguration -Server 'speedtest.tele2.net' -Verbose 9 | $ProfileFtp2 | Format-Table -------------------------------------------------------------------------------- /Public/Rename-FTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Rename-FTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $Path, 6 | [Parameter(Mandatory)][string] $DestinationPath 7 | ) 8 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 9 | $Client.Rename($Path, $DestinationPath) 10 | } 11 | } -------------------------------------------------------------------------------- /Public/Get-FTPChecksum.ps1: -------------------------------------------------------------------------------- 1 | function Get-FTPChecksum { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $RemotePath, 6 | [FluentFTP.FtpHashAlgorithm] $HashAlgorithm = [FluentFTP.FtpHashAlgorithm]::MD5 7 | ) 8 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 9 | $Client.GetChecksum($RemotePath, $HashAlgorithm) 10 | } 11 | } -------------------------------------------------------------------------------- /Public/Set-FTPOption.ps1: -------------------------------------------------------------------------------- 1 | function Set-FTPOption { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [nullable[int]] $RetryAttempts, 6 | [nullable[bool]] $DownloadZeroByteFiles 7 | ) 8 | if ($RetryAttempts) { 9 | $Client.RetryAttempts = $RetryAttempts 10 | } 11 | if ($DownloadZeroByteFiles) { 12 | $Client.DownloadZeroByteFiles = $DownloadZeroByteFiles 13 | } 14 | } -------------------------------------------------------------------------------- /Examples/Example05-ConnectWithAutoConnect.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | # Login via UserName/Password via autoconnect - this will try all options and connect, while displaying Verbose what settings were used to achieve connection 4 | # Useful for trying out every possible combination 5 | $Client = Connect-FTP -Server 'test.rebex.net' -Username 'demo' -Password 'password' -AutoConnect -Verbose 6 | Get-FTPList -Client $Client | Format-Table 7 | Disconnect-FTP -Client $Client -------------------------------------------------------------------------------- /Examples/Example04-ConnectWithAutoDetect.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | # Login via UserName/Password via autodetect - keep in mind using Connect-FTP directly will be faster... 4 | $ProfileFtp1 = Request-FTPConfiguration -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' 5 | $ProfileFtp1 | Format-Table 6 | 7 | # use first profile 8 | $Client = Connect-FTP -FtpProfile $ProfileFtp1[0] 9 | Get-FTPList -Client $Client | Format-Table 10 | Disconnect-FTP -Client $Client -------------------------------------------------------------------------------- /Examples/Example08-ConnectSSH.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | $SshClient = Connect-SSH -Server '192.168.240.29' -Username 'przemek' -Password 'sdsd' 4 | 5 | $Command = { 6 | 'cd /' 7 | 'ls -la' 8 | 'cd /etc' 9 | 'ls -la' 10 | } 11 | $SshCommand = Send-SSHCommand -SSHClient $SshClient -Command $Command -Verbose 12 | $SshCommand 13 | 14 | 15 | $Command = { 16 | 'cat /etc/hosts.allow' 17 | } 18 | $SshCommand = Send-SSHCommand -SSHClient $SshClient -Command $Command -Verbose 19 | $SshCommand -------------------------------------------------------------------------------- /Public/Compare-FTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Compare-FTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $LocalPath, 6 | [Parameter(Mandatory)][string] $RemotePath, 7 | [FluentFTP.FtpCompareOption] $CompareOption = [FluentFTP.FtpCompareOption]::Auto 8 | 9 | ) 10 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 11 | $Client.CompareFile($LocalPath, $RemotePath, $CompareOption) 12 | } 13 | } -------------------------------------------------------------------------------- /Public/Move-FTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Move-FTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $RemoteSource, 6 | [Parameter(Mandatory)][string] $RemoteDestination, 7 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip 8 | ) 9 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 10 | $Client.MoveFile($RemoteSource, $RemoteDestination, $RemoteExists) 11 | } 12 | } -------------------------------------------------------------------------------- /Public/Move-FTPDirectory.ps1: -------------------------------------------------------------------------------- 1 | function Move-FTPDirectory { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $RemoteSource, 6 | [Parameter(Mandatory)][string] $RemoteDestination, 7 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip 8 | ) 9 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 10 | $Client.MoveDirectory($RemoteSource, $RemoteDestination, $RemoteExists) 11 | } 12 | } -------------------------------------------------------------------------------- /Public/Disconnect-FTP.ps1: -------------------------------------------------------------------------------- 1 | function Disconnect-FTP { 2 | [cmdletBinding()] 3 | param( 4 | [FluentFTP.FtpClient] $Client 5 | ) 6 | if ($Client -and $Client.IsConnected) { 7 | try { 8 | $Client.Disconnect() 9 | } catch { 10 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 11 | Write-Error $_ 12 | return 13 | } else { 14 | Write-Warning "Disconnect-FTP - Error: $($_.Exception.Message)" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Public/Remove-FTPDirectory.ps1: -------------------------------------------------------------------------------- 1 | function Remove-FTPDirectory { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [Parameter(Mandatory)][string] $RemotePath, 6 | [FluentFTP.FtpListOption] $FtpListOption 7 | ) 8 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 9 | if (-not $FtpListOption) { 10 | $Client.DeleteDirectory($RemotePath) 11 | } else { 12 | $Client.DeleteDirectory($RemotePath, $FtpListOption) 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Public/Disconnect-SFTP.ps1: -------------------------------------------------------------------------------- 1 | function Disconnect-SFTP { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient 5 | ) 6 | if ($SftpClient -and $SftpClient.IsConnected) { 7 | try { 8 | $SftpClient.Disconnect() 9 | } catch { 10 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 11 | Write-Error $_ 12 | return 13 | } else { 14 | Write-Warning "Disconnect-SFTP - Error: $($_.Exception.Message)" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Examples/Example02-BasicExampleSCP.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | $SftpClient = Connect-SFTP -Server '192.168.240.29' -Username 'przemek' -Password 'YourPassword' 4 | Get-SFTPList -SftpClient $SftpClient | Format-Table 5 | Get-SFTPList -SftpClient $SftpClient -Path "/home" | Format-Table 6 | Receive-SFTPFile -SftpClient $SftpClient -RemotePath '/home/przemek/test1.txt' -LocalPath "$PSScriptRoot\Downloads\mmm.txt" 7 | Send-SFTPFile -SftpClient $SftpClient -LocalPath "$PSScriptRoot\Downloads\mmm.txt" -RemotePath '/home/przemek/mmm.txt' -AllowOverride 8 | Disconnect-SFTP -SftpClient $SftpClient -------------------------------------------------------------------------------- /Tests/Test-FTPDirectory.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Test-FTPDirectory' { 2 | It 'Given an existing path it should return $True' { 3 | # Anonymous login 4 | $Client = $Null 5 | $Client = Connect-FTP -Server 'ftp.gnu.org' -Verbose 6 | $Client.IsConnected | Should -Be $True 7 | $ExistingDirectory = Test-FTPDirectory -RemotePath '/tmp' -Client $Client | Should -Be $true 8 | $NonExistingDirectory = Test-FTPDirectory -RemotePath '/foobar1' -Client $Client | Should -Be $false 9 | Disconnect-FTP -Client $Client 10 | $Client.IsConnected | Should -Be $false 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/Example12-Receive-SFTPFile.ps1: -------------------------------------------------------------------------------- 1 | Clear-Host 2 | Import-Module .\Transferetto.psd1 -Force 3 | 4 | # Connect to SFTP Server 5 | $SftpClient = Connect-SFTP -Server 'test.rebex.net' -Username demo -Password password 6 | 7 | # Get All Files in '/test' for Export 8 | $Export_Files = Get-SFTPList -SftpClient $SftpClient -Path '/pub/example' | Where-Object { $_.IsDirectory -eq $false } 9 | 10 | # Set Export Directory 11 | $ExportPathLocal = "C:\Temp" 12 | 13 | # Download Each File 14 | $Output = ForEach ($RemoteFile in $Export_Files) { 15 | Receive-SFTPFile -SftpClient $SftpClient -RemotePath $RemoteFile.FullName -LocalPath "$ExportPathLocal\$($RemoteFile.Name)" 16 | } 17 | $Output | Format-Table 18 | 19 | # Disconnect 20 | Disconnect-FTP -Client $Client 21 | -------------------------------------------------------------------------------- /Examples/Example13-UploadFTPSDirectory.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | Set-FTPTracing -Enable 4 | 5 | $Client = Connect-FTP -Server '192.168.241.187' -Verbose -Username 'test' -Password 'BiPassword90A' -EncryptionMode Explicit -ValidateAnyCertificate 6 | # List files 7 | $List = Get-FTPList -Client $Client 8 | #$List | Format-Table 9 | # List files within Temporary directory 10 | $List = Get-FTPList -Client $Client -Path '/Temporary' 11 | #$List | Format-Table 12 | 13 | # Get local files 14 | $ListFiles = Get-ChildItem -LiteralPath $PSScriptRoot\Upload 15 | 16 | $Upload = Send-FTPDirectory -Client $Client -LocalPath $PSScriptRoot\Upload -RemotePath '/Temporary' -Verbose -FolderSyncMode Update 17 | $Upload | Format-Table * 18 | 19 | Disconnect-FTP -Client $Client 20 | 21 | Set-FTPTracing -Disable -------------------------------------------------------------------------------- /Public/Get-SFTPList.ps1: -------------------------------------------------------------------------------- 1 | function Get-SFTPList { 2 | [cmdletBinding()] 3 | param( 4 | [alias('FtpPath')][string] $Path, 5 | [Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient 6 | ) 7 | if ($SftpClient -and $SftpClient.IsConnected -and -not $SftpClient.Error) { 8 | try { 9 | if ($Path) { 10 | $SftpClient.ListDirectory($Path) 11 | } else { 12 | $SftpClient.ListDirectory('') 13 | } 14 | } catch { 15 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 16 | Write-Error $_ 17 | return 18 | } else { 19 | Write-Warning "Get-SFTPList - Error: $($_.Exception.Message)" 20 | } 21 | } 22 | } else { 23 | Write-Warning "Get-SFTPList - Skipped (IsConnected $($SftpClient.IsConnected) / Error: $($SftpClient.Error))" 24 | } 25 | } -------------------------------------------------------------------------------- /Examples/Example10-SetChmodViaFTP.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | # If you want to track responses from FTP 4 | Set-FTPTracing -Enable 5 | 6 | $Client = Connect-FTP -Server '192.168.241.187' -Verbose -Username 'test' -Password 'BiPassword90A' -EncryptionMode Explicit -ValidateAnyCertificate 7 | # List files 8 | Get-FTPList -Client $Client -Path '/Temporary' | Format-Table * 9 | Get-FTPChmod -Client $Client -RemotePath '/Temporary' 10 | 11 | # Set / Read Chmod - you need to have permissions for this to work properly 12 | Set-FTPChmod -Client $Client -RemotePath '/Temporary/OrgChart (1).pdf' -Permissions 666 13 | Set-FTPChmod -Client $Client -RemotePath '/Temporary/CreateDir' -Permissions 666 14 | 15 | # Set / Read Chmod - you need to have permissions for this to work properly 16 | Get-FTPChmod -Client $Client -RemotePath '/Temporary/OrgChart (1).pdf' 17 | Get-FTPChmod -Client $Client -RemotePath '/Temporary/CreateDir' 18 | 19 | Set-FTPTracing -Disable -------------------------------------------------------------------------------- /Tests/Download-FTP.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Connect-FTP / Disconnect-FTP' { 2 | It 'Given FTP server should be able to download at least one file' { 3 | # Anonymous login 4 | $Client = $Null 5 | $Client = Connect-FTP -Server 'ftp.gnu.org' -Verbose 6 | $Client.IsConnected | Should -Be $True 7 | $List = Get-FTPList -Client $Client 8 | $List.Count | Should -BeGreaterThan 5 9 | $ListVideo = Get-FTPList -Client $Client -Path "/video" 10 | $ListVideo.Count | Should -BeGreaterThan 20 11 | $TestPath = [io.path]::Combine("$($TestDrive), $($ListVideo[0].Name)") 12 | 13 | Receive-FTPFile -Client $Client -RemotePath $ListVideo[0].FullName -LocalPath $TestPath 14 | 15 | Test-Path -LiteralPath $TestPath | Should -Be $True 16 | (Get-Item -LiteralPath $TestPath).Length | Should -BeGreaterThan 100000 17 | 18 | Disconnect-FTP -Client $Client 19 | $Client.IsConnected | Should -Be $false 20 | } 21 | } -------------------------------------------------------------------------------- /Examples/Example11-ReceiveFiles.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | Set-FTPTracing -Enable 4 | 5 | # Connect to FTP 6 | $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' 7 | 8 | # Get list of files on FTP 9 | $List = Get-FTPList -Client $Client -Path '/pub/example' 10 | 11 | # Find latest file on FTP server 12 | $FindLatestFile = $List | Where-Object { $_.Type -eq 'File' } | Sort-Object -Property Modified -Descending | Select-Object -First 2 13 | 14 | # Download that file 15 | foreach ($RemoteFile in $FindLatestFile) { 16 | Receive-FTPFile -Client $Client -RemoteFile $RemoteFile -LocalPath "$PSScriptRoot\Download\$($RemoteFile.Name)" -LocalExists Overwrite -VerifyOptions Retry, None 17 | } 18 | # Download multiple files into directory 19 | Receive-FTPFile -Client $Client -RemoteFile $FindLatestFile -LocalPath "$PSScriptRoot\Download" -LocalExists Overwrite -VerifyOptions Retry, None | Format-Table 20 | 21 | # Disconnect 22 | Disconnect-FTP -Client $Client -------------------------------------------------------------------------------- /Public/Get-FTPList.ps1: -------------------------------------------------------------------------------- 1 | function Get-FTPList { 2 | [cmdletBinding()] 3 | param( 4 | [alias('FtpPath')][string] $Path, 5 | [FluentFTP.FtpListOption] $Options, 6 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client 7 | ) 8 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 9 | try { 10 | if ($Path -and $Options) { 11 | $Client.GetListing($Path, $Options) 12 | } elseif ($Path) { 13 | $Client.GetListing($Path) 14 | } else { 15 | $Client.GetListing() 16 | } 17 | } catch { 18 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 19 | Write-Error $_ 20 | return 21 | } else { 22 | Write-Warning "Get-FTPList - Error: $($_.Exception.Message)" 23 | } 24 | } 25 | } else { 26 | Write-Warning "Get-FTPList - Skipped (IsConnected $($Client.IsConnected) / Error: $($Client.Error))" 27 | } 28 | } -------------------------------------------------------------------------------- /Public/Remove-SFTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Remove-SFTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient, 5 | [string] $RemotePath, 6 | [switch] $Suppress 7 | ) 8 | if ($SftpClient -and $SftpClient.IsConnected) { 9 | try { 10 | $SftpClient.DeleteFile($RemotePath) 11 | $Status = [PSCustomObject] @{ 12 | Action = 'RemoveFile' 13 | Status = $true 14 | Message = "" 15 | } 16 | } catch { 17 | $Status = [PSCustomObject] @{ 18 | Action = 'RemoveFile' 19 | Status = $false 20 | Message = "Error $($_.Exception.Message)" 21 | } 22 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 23 | Write-Error $_ 24 | return 25 | } else { 26 | Write-Warning "Remove-SFTPFile - Error: $($_.Exception.Message)" 27 | } 28 | } 29 | if (-not $Suppress) { 30 | $Status 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-2022 Evotec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Public/Start-FXPFileTransfer.ps1: -------------------------------------------------------------------------------- 1 | function Start-FXPFileTransfer { 2 | [alias('Start-FXPFile')] 3 | [cmdletBinding()] 4 | param( 5 | [alias('SourceClient')][Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 6 | [Parameter(Mandatory)][string] $SourcePath, 7 | [Parameter(Mandatory)][FluentFTP.FtpClient] $DestinationClient, 8 | [Parameter(Mandatory)][string] $DestinationPath, 9 | [switch] $CreateRemoteDirectory, 10 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip, 11 | [FluentFTP.FtpVerify[]] $VerifyOptions = [FluentFTP.FtpVerify]::None 12 | ) 13 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 14 | # FluentFTP.FtpStatus TransferFile(string sourcePath, FluentFTP.FtpClient remoteClient, string remotePath, bool createRemoteDir, FluentFTP.FtpRemoteExists existsMode, FluentFTP.FtpVerify verifyOptions, System.Action[FluentFTP.FtpProgress] progress, FluentFTP.FtpProgress metaProgress) 15 | $Client.TransferFile($SourcePath, $DestinationClient, $DestinationPath, $CreateRemoteDirectory.IsPresent, $RemoteExists, $VerifyOptions) 16 | } 17 | } -------------------------------------------------------------------------------- /Examples/Example01-BasicExampleFTP.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | # Login via UserName/Password 4 | $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' -EncryptionMode Auto 5 | $List = Get-FTPList -Client $Client 6 | $List | Format-Table 7 | Get-FTPDirectory -Client $Client -LocalPath 'C:\Temp\FTP' -RemotePath "/" -Verbose 8 | Disconnect-FTP -Client $Client 9 | 10 | # Login via UserName/Password 11 | $Client = Connect-FTP -Server 'ftp.dlptest.com' -Verbose -Username 'dlpuser' -Password 'rNrKYTX9g7z3RgJRmxWuGHbeu' 12 | $List = Get-FTPList -Client $Client 13 | $List | Format-Table 14 | Disconnect-FTP -Client $Client 15 | 16 | # Anonymous login 17 | $Client = Connect-FTP -Server 'speedtest.tele2.net' -Verbose 18 | $List = Get-FTPList -Client $Client 19 | $List | Format-Table 20 | Disconnect-FTP -Client $Client 21 | 22 | # Login via credentials 23 | $Credential = Get-Credential -UserName 'demo' -Message 'Please enter password' # password is password 24 | $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Credential $Credential 25 | $List = Get-FTPList -Client $Client 26 | $List | Format-Table 27 | Disconnect-FTP -Client $Client -------------------------------------------------------------------------------- /Examples/Example06-UploadFTPS.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | $Client = Connect-FTP -Server '192.168.241.187' -Verbose -Username 'test' -Password 'BiPassword90A' -EncryptionMode Explicit -ValidateAnyCertificate 4 | # List files 5 | $List = Get-FTPList -Client $Client 6 | $List | Format-Table 7 | # List files within Temporary directory 8 | $List = Get-FTPList -Client $Client -Path '/Temporary' 9 | $List | Format-Table 10 | 11 | # Get local files 12 | $ListFiles = Get-ChildItem -LiteralPath $PSScriptRoot\Upload -File 13 | 14 | # Upload file by file 15 | $Output = foreach ($File in $ListFiles) { 16 | # To temporary 17 | Send-FTPFile -Client $Client -LocalPath $File.FullName -RemotePath "/Temporary/$($File.Name)" -RemoteExists Overwrite 18 | # to directory within Temporary that may not exists 19 | Send-FTPFile -Client $Client -LocalPath $File.FullName -RemotePath "/Temporary/CreateDir/$($File.Name)" -RemoteExists Skip -CreateRemoteDirectory 20 | } 21 | 22 | $Output | Format-Table 23 | 24 | # Upload all files at once to FTP 25 | Send-FTPFile -Client $Client -LocalPath $ListFiles.FullName -RemotePath "/Temporary" -RemoteExists Overwrite | Format-Table 26 | 27 | Disconnect-FTP -Client $Client -------------------------------------------------------------------------------- /Examples/Example07-UploadSFTP.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | $SftpClient = Connect-SFTP -Server '192.168.241.187' -Verbose -Username 'test' -Password 'BiPassword90A' 4 | Get-SFTPList -SftpClient $SftpClient | Format-Table 5 | Get-SFTPList -SftpClient $SftpClient -Path "/Temporary" | Format-Table * 6 | 7 | $ListFiles = Get-ChildItem -LiteralPath $PSScriptRoot\Upload -Recurse -File 8 | foreach ($File in $ListFiles) { 9 | $Directory = [io.path]::GetDirectoryName($File.FullName) 10 | if ($Directory -eq "$PSScriptRoot\Upload") { 11 | Send-SFTPFile -SftpClient $SftpClient -LocalPath $File.FullName -RemotePath "/Temporary/$($File.Name)" -AllowOverride 12 | } else { 13 | #$RemotePath = "/Temporary/$($Directory.Split('\')[-1])/$($File.Name)" 14 | $RemoteFolder = "/Temporary/$($Directory.Split('\')[-1])" 15 | $List = Get-SFTPList -SftpClient $SftpClient -Path $RemoteFolder -WarningAction SilentlyContinue 16 | if (-not $List) { 17 | $SftpClient.CreateDirectory($RemoteFolder) 18 | } 19 | Send-SFTPFile -SftpClient $SftpClient -LocalPath $File.FullName -RemotePath "$RemoteFolder/$($File.Name)" -AllowOverride 20 | } 21 | } 22 | 23 | Disconnect-SFTP -SftpClient $SftpClient -------------------------------------------------------------------------------- /Public/Start-FXPDirectoryTransfer.ps1: -------------------------------------------------------------------------------- 1 | function Start-FXPDirectoryTransfer { 2 | [alias('Start-FXPDirectory')] 3 | [cmdletBinding()] 4 | param( 5 | [alias('SourceClient')][Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 6 | [Parameter(Mandatory)][string] $SourcePath, 7 | [Parameter(Mandatory)][FluentFTP.FtpClient] $DestinationClient, 8 | [Parameter(Mandatory)][string] $DestinationPath, 9 | [FluentFTP.FtpFolderSyncMode] $FolderSyncMode = [FluentFTP.FtpFolderSyncMode]::Update, 10 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip, 11 | [FluentFTP.FtpVerify[]] $VerifyOptions = [FluentFTP.FtpVerify]::None 12 | ) 13 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 14 | # System.Collections.Generic.List[FluentFTP.FtpResult] TransferDirectory(string sourceFolder, FluentFTP.FtpClient remoteClient, string remoteFolder, FluentFTP.FtpFolderSyncMode mode, FluentFTP.FtpRemoteExists existsMode, FluentFTP.FtpVerify verifyOptions, System.Collections.Generic.List[FluentFTP.Rules.FtpRule] rules, System.Action[FluentFTP.FtpProgress] progress) 15 | $Client.TransferDirectory($SourcePath, $DestinationClient, $DestinationPath, $FolderSyncMode, $RemoteExists, $VerifyOptions) 16 | } 17 | } -------------------------------------------------------------------------------- /Public/Set-FTPChmod.ps1: -------------------------------------------------------------------------------- 1 | function Set-FTPChmod { 2 | [cmdletBinding(DefaultParameterSetName = 'ByInt')] 3 | param( 4 | [Parameter(Mandatory, ParameterSetName = 'ByInt')] 5 | [Parameter(Mandatory, ParameterSetName = 'Explicit')] 6 | [FluentFTP.FtpClient] $Client, 7 | 8 | [Parameter(Mandatory, ParameterSetName = 'ByInt')] 9 | [Parameter(Mandatory, ParameterSetName = 'Explicit')] 10 | [string] $RemotePath, 11 | 12 | [Parameter(Mandatory, ParameterSetName = 'ByInt')] 13 | [nullable[int]] $Permissions, 14 | 15 | [Parameter(Mandatory, ParameterSetName = 'Explicit')] 16 | [FluentFTP.FtpPermission] $Owner, 17 | 18 | [Parameter(Mandatory, ParameterSetName = 'Explicit')] 19 | [FluentFTP.FtpPermission] $Group, 20 | 21 | [Parameter(Mandatory, ParameterSetName = 'Explicit')] 22 | [FluentFTP.FtpPermission] $Other 23 | ) 24 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 25 | #void Chmod(string path, int permissions) 26 | #void Chmod(string path, FluentFTP.FtpPermission owner, FluentFTP.FtpPermission group, FluentFTP.FtpPermission other) 27 | if ($Permissions) { 28 | $Client.Chmod($RemotePath, $Permissions) 29 | } else { 30 | $Client.Chmod($RemotePath, $Owner, $Group, $Other) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Public/Receive-FTPDirectory.ps1: -------------------------------------------------------------------------------- 1 | function Receive-FTPDirectory { 2 | [alias('Get-FTPDirectory')] 3 | [cmdletBinding()] 4 | param( 5 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 6 | [string] $LocalPath, 7 | [Parameter(Mandatory)][string] $RemotePath, 8 | [FluentFTP.FtpFolderSyncMode] $FolderSyncMode = [FluentFTP.FtpFolderSyncMode]::Update, 9 | [FluentFTP.FtpLocalExists] $LocalExists, 10 | [FluentFTP.FtpVerify] $VerifyOptions, 11 | [FluentFTP.Rules.FtpRule[]] $Rules 12 | ) 13 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 14 | #System.Collections.Generic.List[FluentFTP.FtpResult] DownloadDirectory(string localFolder, string remoteFolder, FluentFTP.FtpFolderSyncMode mode, FluentFTP.FtpLocalExists existsMode, FluentFTP.FtpVerify verifyOptions, System.Collections.Generic.List[FluentFTP.Rules.FtpRule] rules, System.Action[FluentFTP.FtpProgress] progress) 15 | #System.Collections.Generic.List[FluentFTP.FtpResult] IFtpClient.DownloadDirectory(string localFolder, string remoteFolder, FluentFTP.FtpFolderSyncMode mode, FluentFTP.FtpLocalExists existsMode, FluentFTP.FtpVerify verifyOptions, System.Collections.Generic.List[FluentFTP.Rules.FtpRule] rules, System.Action[FluentFTP.FtpProgress] progress) 16 | $Client.DownloadDirectory($LocalPath, $RemotePath, $FolderSyncMode) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Transferetto.AzurePipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: Build_PS_Win 3 | pool: 4 | vmImage: windows-latest 5 | steps: 6 | - powershell: | 7 | .\Transferetto.Tests.ps1 8 | displayName: "Run Pester Tests - PowerShell 5" 9 | 10 | - job: Windows_PowerShell_7 11 | pool: 12 | vmImage: windows-latest 13 | steps: 14 | - pwsh: '.\Transferetto.Tests.ps1' 15 | env: 16 | TEAMSPESTERID: $(TEAMSPESTERID) 17 | displayName: "Run Pester Tests" 18 | 19 | 20 | - job: Build_PSCore_Ubuntu 21 | pool: 22 | vmImage: ubuntu-latest 23 | steps: 24 | - script: | 25 | curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - 26 | curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list 27 | sudo apt-get update 28 | sudo apt-get install -y powershell 29 | displayName: "Install PowerShell Core" 30 | - script: | 31 | pwsh -c '.\Transferetto.Tests.ps1' 32 | displayName: "Run Pester Tests" 33 | 34 | - job: Build_PSCore_MacOS 35 | pool: 36 | vmImage: macOS-latest 37 | steps: 38 | - script: | 39 | brew update 40 | brew tap caskroom/cask 41 | brew install mono-libgdiplus 42 | brew install --cask powershell 43 | displayName: "Install PowerShell Core" 44 | - script: | 45 | pwsh -c '.\Transferetto.Tests.ps1' 46 | displayName: "Run Pester Tests" 47 | -------------------------------------------------------------------------------- /Public/Receive-SFTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Receive-SFTPFile { 2 | [alias('Get-SFTPFile')] 3 | [cmdletBinding()] 4 | param( 5 | [Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient, 6 | [string] $RemotePath, 7 | [string] $LocalPath 8 | ) 9 | if ($SftpClient -and $SftpClient.IsConnected) { 10 | try { 11 | $FileStream = [System.IO.FileStream]::new($LocalPath, [System.IO.FileMode]::OpenOrCreate) 12 | $SftpClient.DownloadFile($RemotePath, $FileStream) 13 | $Status = [PSCustomObject] @{ 14 | Action = 'DownloadFile' 15 | Status = $true 16 | LocalPath = $LocalPath 17 | RemotePath = $RemotePath 18 | Message = "" 19 | } 20 | } catch { 21 | $Status = [PSCustomObject] @{ 22 | Action = 'DownloadFile' 23 | Status = $false 24 | LocalPath = $LocalPath 25 | RemotePath = $RemotePath 26 | Message = "Error: $($_.Exception.Message)" 27 | } 28 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 29 | Write-Error $_ 30 | return 31 | } else { 32 | Write-Warning "Receive-SFTPFile - Error: $($_.Exception.Message)" 33 | } 34 | } finally { 35 | $FileStream.Close() 36 | if ($Status.Status -eq $false) { 37 | Remove-Item -LiteralPath $LocalPath 38 | } 39 | } 40 | $Status 41 | } 42 | } -------------------------------------------------------------------------------- /Sources/Transferetto/Transferetto.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Evotec 5 | Przemyslaw Klys 6 | 1.0.0 7 | netstandard2.0 8 | Transferetto 9 | 10 | (c) 2011 - 2022 Przemyslaw Klys @ Evotec. All rights reserved. 11 | 10.0 12 | False 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | $(DefineConstants);FRAMEWORK 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Public/Send-SSHCommand.ps1: -------------------------------------------------------------------------------- 1 | function Send-SSHCommand { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][Renci.SshNet.SshClient] $SshClient, 5 | [scriptblock] $Command, 6 | [switch] $Status 7 | ) 8 | if ($SshClient -and $SshClient.IsConnected -and -not $SshClient.Error) { 9 | if ($Command) { 10 | $CommandsToExecute = & $Command 11 | [string] $SendCommand = foreach ($C in $CommandsToExecute) { 12 | if ($C.Trim().EndsWith(';')) { 13 | $C 14 | } else { 15 | "$C;" 16 | } 17 | } 18 | try { 19 | Write-Verbose -Message "Send-SSHCommand - Executing command: $SendCommand" 20 | if ($Status) { 21 | [PSCustomObject] @{ 22 | Status = $true 23 | Output = $SshClient.CreateCommand($SendCommand).Execute() 24 | Error = $null 25 | } 26 | } else { 27 | $SshClient.CreateCommand($SendCommand).Execute() 28 | } 29 | } catch { 30 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 31 | Write-Error $_ 32 | return 33 | } else { 34 | Write-Warning "Send-SSHCommand - Error: $($_.Exception.Message)" 35 | } 36 | if ($Status) { 37 | [PSCustomObject] @{ 38 | Status = $false 39 | Output = '' 40 | Error = "Error: $($_.Exception.Message)" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Examples/Example15-BasicExampleFTPwithProxy.ps1: -------------------------------------------------------------------------------- 1 | Import-Module .\Transferetto.psd1 -Force 2 | 3 | Set-FTPTracing -Enable 4 | # # Login via UserName/Password via proxy 5 | # $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' -ProxyHost '192.252.216.81' -ProxyPort 4145 -ProxyType FtpClientSocks5Proxy 6 | # $List = Get-FTPList -Client $Client 7 | # $List | Format-Table 8 | # Disconnect-FTP -Client $Client 9 | 10 | 11 | # $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' 12 | # $List = Get-FTPList -Client $Client 13 | # $List | Format-Table 14 | # Receive-FTPFile -Client $Client -RemotePath '/readme.txt' -LocalPath 'C:\Temp\readme.txt' 15 | # Disconnect-FTP -Client $Client 16 | 17 | 18 | # Login via UserName/Password with Proxy 19 | $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' -ProxyHost '104.37.135.145' -ProxyPort 4145 -ProxyType FtpClientSocks4aProxy 20 | $List = Get-FTPList -Client $Client 21 | $List | Format-Table 22 | #Remove-Item -LiteralPath 'C:\Temp\readme.txt' 23 | #Receive-FTPFile -Client $Client -RemotePath '/readme.txt' -LocalPath 'C:\Temp\readme.txt' 24 | Disconnect-FTP -Client $Client 25 | 26 | 27 | # Set-FTPTracing -Enable 28 | # # Login via UserName/Password 29 | # $Client = Connect-FTP -Server 'ftp.dlptest.com' -Verbose -Username 'dlpuser' -Password 'rNrKYTX9g7z3RgJRmxWuGHbeu' 30 | # $List = Get-FTPList -Client $Client 31 | # $List | Format-Table 32 | # $List.Count 33 | # Disconnect-FTP -Client $Client 34 | 35 | # # Login via UserName/Password 36 | # $Client = Connect-FTP -Server 'ftp.dlptest.com' -Verbose -Username 'dlpuser' -Password 'rNrKYTX9g7z3RgJRmxWuGHbeu' -ProxyHost '104.37.135.145' -ProxyPort 4145 -ProxyType FtpClientSocks4aProxy 37 | # $List = Get-FTPList -Client $Client 38 | # $List | Format-Table 39 | # $List.Count 40 | # Disconnect-FTP -Client $Client -------------------------------------------------------------------------------- /Sources/Transferetto.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32804.467 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Transferetto", "Transferetto\Transferetto.csproj", "{EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EEF3BFAD-C8A5-474C-BA52-5C20E66EF0F9}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {7391AC3E-2156-49C6-A228-15B6A7946FBA}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {C3349FCA-EF1E-4AFF-8F4C-522530B18D54}.Release|Any CPU.Build.0 = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ExtensibilityGlobals) = postSolution 31 | SolutionGuid = {F37BB785-3AB1-4498-8102-689C0E4C3924} 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /Public/Send-FTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Send-FTPFile { 2 | [alias('Add-FTPFile')] 3 | [cmdletBinding()] 4 | param( 5 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 6 | [string] $RemotePath, 7 | [System.IO.FileInfo[]] $LocalFile, 8 | [string[]] $LocalPath, 9 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip, 10 | [FluentFTP.FtpVerify] $VerifyOptions = [FluentFTP.FtpVerify]::None, 11 | [FluentFTP.FtpError] $ErrorHandling = [FluentFTP.FtpError]::None, 12 | [switch] $CreateRemoteDirectory 13 | ) 14 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 15 | if ($LocalPath.Count -gt 1 -or $LocalFile.Count -gt 1) { 16 | $Splat = @{ 17 | Client = $Client 18 | RemoteExists = $RemoteExists 19 | VerifyOptions = $VerifyOptions 20 | LocalPath = $LocalPath 21 | LocalFile = $LocalFile 22 | RemotePath = $RemotePath 23 | CreateRemoteDirectory = $CreateRemoteDirectory.IsPresent 24 | ErrorHandling = $ErrorHandling 25 | } 26 | Remove-EmptyValue -Hashtable $Splat 27 | $Status = Add-PrivateFTPFiles @Splat 28 | $Status 29 | } else { 30 | foreach ($Path in $LocalPath) { 31 | $Splat = @{ 32 | Client = $Client 33 | RemoteExists = $RemoteExists 34 | VerifyOptions = $VerifyOptions 35 | LocalPath = $Path 36 | RemotePath = $RemotePath 37 | CreateRemoteDirectory = $CreateRemoteDirectory.IsPresent 38 | } 39 | $Status = Add-PrivateFTPFile @Splat 40 | $Status 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Public/Send-SFTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Send-SFTPFile { 2 | [alias('Add-SFTPFile')] 3 | [cmdletBinding()] 4 | param( 5 | [Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient, 6 | [string] $RemotePath, 7 | [string] $LocalPath, 8 | [switch] $AllowOverride 9 | ) 10 | if ($SftpClient -and $SftpClient.IsConnected) { 11 | if (Test-Path -LiteralPath $LocalPath) { 12 | try { 13 | $FileStream = [System.IO.FileStream]::new($LocalPath, [System.IO.FileMode]::OpenOrCreate) 14 | $SftpClient.UploadFile($FileStream, $RemotePath, $AllowOverride) 15 | $Status = [PSCustomObject] @{ 16 | Action = 'UploadFile' 17 | Status = $true 18 | LocalPath = $LocalPath 19 | RemotePath = $RemotePath 20 | Message = "" 21 | } 22 | } catch { 23 | $Status = [PSCustomObject] @{ 24 | Action = 'UploadFile' 25 | Status = $false 26 | LocalPath = $LocalPath 27 | RemotePath = $RemotePath 28 | Message = "Error: $($_.Exception.Message)" 29 | } 30 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 31 | Write-Error $_ 32 | return 33 | } else { 34 | Write-Warning "Send-SFTPFile - Error: $($_.Exception.Message)" 35 | } 36 | } finally { 37 | $FileStream.Close() 38 | } 39 | } else { 40 | Write-Warning "Send-SFTPFile - File $LocalPath doesn't exists." 41 | $Status = [PSCustomObject] @{ 42 | Action = 'UploadFile' 43 | Status = $false 44 | LocalPath = $LocalPath 45 | RemotePath = $RemotePath 46 | Message = "LocalPath doesn't exists $LocalPath" 47 | } 48 | } 49 | $Status 50 | } 51 | } -------------------------------------------------------------------------------- /Public/Set-FTPTracing.ps1: -------------------------------------------------------------------------------- 1 | function Set-FTPTracing { 2 | <# 3 | .SYNOPSIS 4 | Allows enabling/disabling tracing ftp commands being sent and received during communucation with the server. 5 | 6 | .DESCRIPTION 7 | Allows enabling/disabling tracing ftp commands being sent and received during communucation with the server. 8 | 9 | .PARAMETER Enable 10 | Enable tracing 11 | 12 | .PARAMETER Disable 13 | Disable tracing 14 | 15 | .PARAMETER ShowPassword 16 | Include FTP passwords in logs? Default: false. 17 | 18 | .PARAMETER HideUserName 19 | Hide FTP usernames in logs? Default: false. 20 | 21 | .PARAMETER HideIP 22 | Hide server IP addresses in logs? Default: true. 23 | 24 | .EXAMPLE 25 | Set-FTPTracing -Enable 26 | 27 | .NOTES 28 | General notes 29 | #> 30 | [cmdletBinding()] 31 | param( 32 | [switch] $Enable, 33 | [switch] $Disable, 34 | [switch] $ShowPassword, 35 | [switch] $HideUserName, 36 | [switch] $HideIP 37 | ) 38 | $Script:GlobalFTPLogging = [ordered] @{} 39 | if ($Enable) { 40 | $Script:GlobalFTPLogging.LogToConsole = $true 41 | } elseif ($Disable) { 42 | #$Script:GlobalFTPLogging.LogToConsole = $false 43 | $Script:GlobalFTPLogging = $null 44 | return 45 | } else { 46 | Write-Warning -Message 'Please specify either -Enable or -Disable' 47 | return 48 | } 49 | if ($HideUserName) { 50 | $Script:GlobalFTPLogging.LogUserName = $false; # hide FTP user names 51 | } else { 52 | $Script:GlobalFTPLogging.LogUserName = $true; # show FTP user names 53 | } 54 | if ($ShowPassword) { 55 | $Script:GlobalFTPLogging.LogPassword = $false; # hide FTP passwords 56 | } else { 57 | $Script:GlobalFTPLogging.LogPassword = $true; # show FTP passwords 58 | } 59 | if (-not $HideIP) { 60 | $Script:GlobalFTPLogging.LogHost = $true; # hide FTP server IP addresses 61 | } else { 62 | $Script:GlobalFTPLogging.LogHost = $false; # show FTP server IP addresses 63 | } 64 | } -------------------------------------------------------------------------------- /Private/Add-PrivateFTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Add-PrivateFTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [string] $RemotePath, 6 | [string] $LocalPath, 7 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip, 8 | [FluentFTP.FtpVerify] $VerifyOptions = [FluentFTP.FtpVerify]::None, 9 | [switch] $CreateRemoteDirectory 10 | ) 11 | try { 12 | $Message = $Client.UploadFile($LocalPath, $RemotePath, $RemoteExists, $CreateRemoteDirectory.IsPresent, $VerifyOptions) 13 | if ($Message -eq 'success') { 14 | $State = $true 15 | } else { 16 | if ($Message -eq 'Skipped') { 17 | $State = $true 18 | } else { 19 | $State = $false 20 | } 21 | } 22 | $Status = [PSCustomObject] @{ 23 | Action = 'UploadFile' 24 | Status = $State 25 | IsSuccess = if ($State) { $true } else { $false } 26 | IsSkipped = $Message -eq 'Skipped' 27 | IsSkippedByRule = $false 28 | IsFailed = if ($State) { $false } else { $true } 29 | LocalPath = $LocalPath 30 | RemotePath = $RemotePath 31 | Message = $Message 32 | } 33 | } catch { 34 | $Status = [PSCustomObject] @{ 35 | Action = 'UploadFile' 36 | Status = $false 37 | IsSuccess = $false 38 | IsSkipped = $false 39 | IsSkippedByRule = $false 40 | IsFailed = $true 41 | LocalPath = $LocalPath 42 | RemotePath = $RemotePath 43 | Message = "Error: $($_.Exception.Message)" 44 | } 45 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 46 | Write-Error $_ 47 | return 48 | } else { 49 | Write-Warning "Add-PrivateFTPFile - Error: $($_.Exception.Message)" 50 | } 51 | } 52 | $Status 53 | } -------------------------------------------------------------------------------- /Transferetto.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | AliasesToExport = @('Get-FTPDirectory', 'Get-FTPFile', 'Get-SFTPFile', 'Add-FTPDirectory', 'Add-FTPFile', 'Add-SFTPFile', 'Start-FXPDirectory', 'Start-FXPFile') 3 | Author = 'Przemyslaw Klys' 4 | CmdletsToExport = @() 5 | CompanyName = 'Evotec' 6 | CompatiblePSEditions = @('Desktop', 'Core') 7 | Copyright = '(c) 2011 - 2024 Przemyslaw Klys @ Evotec. All rights reserved.' 8 | Description = 'Module which allows ftp, ftps, sftp file transfers with advanced features. It also allows to transfer files and directorires between servers using fxp protocol. As a side feature it allows to conenct to SSH and executes commands on it. ' 9 | DotNetFrameworkVersion = '4.7.2' 10 | FunctionsToExport = @('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') 11 | GUID = '7d61db15-9efe-41d1-a1c0-81d738975dec' 12 | ModuleVersion = '1.0.0' 13 | PowerShellVersion = '5.1' 14 | PrivateData = @{ 15 | PSData = @{ 16 | ExternalModuleDependencies = @('Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Utility') 17 | IconUri = 'https://evotec.xyz/wp-content/uploads/2021/03/Transferetto.png' 18 | ProjectUri = 'https://github.com/EvotecIT/Transferetto' 19 | Tags = @('Windows', 'Linux', 'MacOs', 'ftp', 'sftp', 'ftps', 'scp', 'winscp', 'ssh') 20 | } 21 | } 22 | RequiredModules = @('Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Utility') 23 | RootModule = 'Transferetto.psm1' 24 | } -------------------------------------------------------------------------------- /Public/Rename-SFTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Rename-SFTPFile { 2 | <# 3 | .SYNOPSIS 4 | Allows renaming remote file over SFTP protocol 5 | 6 | .DESCRIPTION 7 | Allows renaming remote file over SFTP protocol 8 | 9 | .PARAMETER SftpClient 10 | Parameter that contains the SFTP client object 11 | 12 | .PARAMETER SourcePath 13 | Path to file that is going to be renamed 14 | 15 | .PARAMETER DestinationPath 16 | New path to file, with new name 17 | 18 | .PARAMETER Suppress 19 | Suppress returning an object with information about the operation 20 | 21 | .EXAMPLE 22 | $SftpClient = Connect-SFTP -Server '192.168.240.29' -Username 'przemek' -Password 'Password' 23 | Rename-SFTPFile -SftpClient $SftpClient -SourcePath '/home/przemek/mmm.txt' -DestinationPath '/home/przemek/mmm1.txt' 24 | Disconnect-SFTP -SftpClient $SftpClient 25 | 26 | .NOTES 27 | General notes 28 | #> 29 | [cmdletBinding()] 30 | param( 31 | [Parameter(Mandatory)][Renci.SshNet.SftpClient] $SftpClient, 32 | [alias('OldPath')][string] $SourcePath, 33 | [alias('NewPath')][string] $DestinationPath, 34 | [switch] $Suppress 35 | ) 36 | if ($SftpClient -and $SftpClient.IsConnected) { 37 | try { 38 | $SftpClient.RenameFile($SourcePath, $DestinationPath) 39 | $Status = [PSCustomObject] @{ 40 | Action = 'RenameFile' 41 | Status = $true 42 | OldPath = $SourcePath 43 | NewPath = $DestinationPath 44 | Message = "" 45 | } 46 | } catch { 47 | $Status = [PSCustomObject] @{ 48 | Action = 'RenameFile' 49 | Status = $false 50 | OldPath = $SourcePath 51 | NewPath = $DestinationPath 52 | Message = "Error $($_.Exception.Message)" 53 | } 54 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 55 | Write-Error $_ 56 | return 57 | } else { 58 | Write-Warning "Rename-SFTPFile - Error: $($_.Exception.Message)" 59 | } 60 | } 61 | if (-not $Suppress) { 62 | $Status 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Transferetto.Tests.ps1: -------------------------------------------------------------------------------- 1 | $ModuleName = (Get-ChildItem $PSScriptRoot\*.psd1).BaseName 2 | $PrimaryModule = Get-ChildItem -Path $PSScriptRoot -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1 3 | if (-not $PrimaryModule) { 4 | throw "Path $PSScriptRoot doesn't contain PSD1 files. Failing tests." 5 | } 6 | if ($PrimaryModule.Count -ne 1) { 7 | throw 'More than one PSD1 files detected. Failing tests.' 8 | } 9 | $PSDInformation = Import-PowerShellDataFile -Path $PrimaryModule.FullName 10 | $RequiredModules = @( 11 | 'Pester' 12 | 'PSWriteColor' 13 | if ($PSDInformation.RequiredModules) { 14 | $PSDInformation.RequiredModules 15 | } 16 | ) 17 | foreach ($Module in $RequiredModules) { 18 | if ($Module -is [System.Collections.IDictionary]) { 19 | $Exists = Get-Module -ListAvailable -Name $Module.ModuleName 20 | if (-not $Exists) { 21 | Write-Warning "$ModuleName - Downloading $($Module.ModuleName) from PSGallery" 22 | Install-Module -Name $Module.ModuleName -Force -SkipPublisherCheck -AllowClobber 23 | } 24 | } else { 25 | $Exists = Get-Module -ListAvailable $Module -ErrorAction SilentlyContinue 26 | if (-not $Exists) { 27 | Install-Module -Name $Module -Force -SkipPublisherCheck -AllowClobber 28 | } 29 | } 30 | } 31 | 32 | Write-Color 'ModuleName: ', $ModuleName, ' Version: ', $PSDInformation.ModuleVersion -Color Yellow, Green, Yellow, Green -LinesBefore 2 33 | Write-Color 'PowerShell Version: ', $PSVersionTable.PSVersion -Color Yellow, Green 34 | Write-Color 'PowerShell Edition: ', $PSVersionTable.PSEdition -Color Yellow, Green 35 | Write-Color 'Required modules: ' -Color Yellow 36 | foreach ($Module in $PSDInformation.RequiredModules) { 37 | if ($Module -is [System.Collections.IDictionary]) { 38 | Write-Color ' [>] ', $Module.ModuleName, ' Version: ', $Module.ModuleVersion -Color Yellow, Green, Yellow, Green 39 | } else { 40 | Write-Color ' [>] ', $Module -Color Yellow, Green 41 | } 42 | } 43 | Write-Color 44 | 45 | Import-Module $PSScriptRoot\*.psd1 -Force 46 | $result = Invoke-Pester -Script $PSScriptRoot\Tests -Verbose -PassThru 47 | 48 | if ($result.FailedCount -gt 0) { 49 | throw "$($result.FailedCount) tests failed." 50 | } -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | #### 1.0.0 - 2024.02.03 2 | - Downgraded `SSH.NET` to `2023.0.0` as issues with PS 7 3 | - Improved `Send-FTPFile`, `Receive-FTPFile` output objects 4 | - Added `TLS 1.3` support for `Connect-FTP` - hopefully it works 5 | 6 | #### 0.0.19 - 2024.02.01 7 | - Updated `FluentFTP` to `49.0.2` 8 | - Updated `SSH.NET` to `2023.0.1` 9 | - Added `TLS 1.3` support 10 | 11 | #### 0.0.18 12 | - Upgraded `FluentFTP` to `48.0.1` 13 | - Upgraded `SSH.NET` to `2023.0.0` 14 | 15 | #### 0.0.17 - 2022.12.04 16 | - Upgraded `FluentFTP` to `42.1.0` 17 | 18 | #### 0.0.16 - 2022.11.05 19 | - Fixes `Connect-FTP` options due to new version change of FLuentFTP 20 | - Upgraded `FluentFTP` to `40.0.0` to `42.0.0` 21 | - Added additional tests to try and be proactive! 22 | - Added basic support for Proxy (maybe works) 23 | 24 | #### 0.0.15 - 2022.09.22 25 | - Upgraded `FluentFTP` to `40.0.0` 26 | 27 | #### 0.0.14 - 2022.08.04 28 | - Fixes problem with missing function in PSGallery 29 | 30 | #### 0.0.13 - 2022.07.11 31 | - Upgraded `FluentFTP` to `37.0.6` 32 | 33 | #### 0.0.12 - 2022.06.17 34 | - Upgraded `SSH.NET` to `2020.0.2` which fixes weak private key generation - [SSH.NET Advisory](https://github.com/sshnet/SSH.NET/security/advisories/GHSA-72p8-v4hg-v45p) 35 | - Upgraded `FluentFTP` to `37.0.3.0` 36 | - Fixes `Set-FTPTracing` 37 | 38 | #### 0.0.11 - 2022.04.10 39 | - Small docs update to `Set-FTPTracing` 40 | - Changed `ShowUsername` to `HideUsername` in `Set-FTPTracing` 41 | - Added detection for `netframework` to be minimum 4.7.2 42 | - Upgraded `FluentFTP` to `37.0.2.0` 43 | #### 0.0.10 - 2021.12.22 44 | - Fixes `Connect-SFTP` port functionality 45 | - Fixes `Connect-SSH` port functionality 46 | #### 0.0.9 - 2021.10.29 47 | - Fixes `Rename-SFTPFile` - reported [#7](https://github.com/EvotecIT/Transferetto/issues/7) 48 | 49 | #### 0.0.8 - 2021.09.15 50 | - Added command `Test-FTPDirectory` - thank you [Sidewinder53](https://github.com/EvotecIT/Transferetto/pull/5) 51 | 52 | #### 0.0.7 - 2021.09.15 53 | - Republished module to PowerShell Gallery 54 | 55 | #### 0.0.6 - 2021.09.14 56 | - Added support for key authentication in `Connect-SFTP` - thank you [Szeraax!](https://github.com/EvotecIT/Transferetto/pull/3) 57 | - Improved error handling of `Connect-SFTP` 58 | 59 | #### 0.0.5 - 2021.04.04 60 | - Small fixes 61 | 62 | #### 0.0.4 - 2021.03.29 63 | - First edition 64 | -------------------------------------------------------------------------------- /Private/Add-PrivateFTPFiles.ps1: -------------------------------------------------------------------------------- 1 | function Add-PrivateFTPFiles { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [string] $RemotePath, 6 | [string[]] $LocalPath, 7 | [System.IO.FileInfo[]] $LocalFile, 8 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip, 9 | [FluentFTP.FtpVerify] $VerifyOptions = [FluentFTP.FtpVerify]::None, 10 | [FluentFTP.FtpError] $ErrorHandling = [FluentFTP.FtpError]::None, 11 | [switch] $CreateRemoteDirectory 12 | ) 13 | $ErrorFound = $null 14 | try { 15 | if ($LocalFile) { 16 | $Message = $Client.UploadFiles([System.IO.FileInfo[]] $LocalFile, $RemotePath, $RemoteExists, $CreateRemoteDirectory.IsPresent, $VerifyOptions, $ErrorHandling) 17 | } else { 18 | $Message = $Client.UploadFiles([string[]] $LocalPath, $RemotePath, $RemoteExists, $CreateRemoteDirectory.IsPresent, $VerifyOptions, $ErrorHandling) 19 | } 20 | } catch { 21 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 22 | Write-Error $_ 23 | return 24 | } else { 25 | Write-Warning "Add-PrivateFTPFiles - Error: $($_.Exception.Message)" 26 | } 27 | $ErrorFound = $($_.Exception.Message) 28 | } 29 | if (-not $ErrorFound) { 30 | foreach ($M in $Message) { 31 | [PSCustomObject] @{ 32 | Action = 'UploadFile' 33 | Status = $M.IsSuccess 34 | IsSuccess = $M.IsSuccess 35 | IsSkipped = $M.IsSkipped 36 | IsSkippedByRule = $M.IsSkippedByRule 37 | IsFailed = $M.IsFailed 38 | LocalPath = $M.LocalPath 39 | RemotePath = $M.RemotePath 40 | Message = if ($M.IsSkipped -eq $true) { "Skipped" } else { 'Success' } 41 | } 42 | } 43 | } else { 44 | [PSCustomObject] @{ 45 | Action = 'UploadFile' 46 | Status = $false 47 | IsSuccess = $false 48 | IsSkipped = $false 49 | IsSkippedByRule = $false 50 | IsFailed = $true 51 | LocalPath = $LocalPath 52 | RemotePath = $RemotePath 53 | Message = $ErrorFound 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Public/Send-FTPDirectory.ps1: -------------------------------------------------------------------------------- 1 | function Send-FTPDirectory { 2 | <# 3 | .SYNOPSIS 4 | Uploads a directory to an FTP server. 5 | 6 | .DESCRIPTION 7 | Uploads a directory to an FTP server. 8 | 9 | .PARAMETER Client 10 | The Client to use for connection. 11 | 12 | .PARAMETER LocalPath 13 | Path on the local machine to upload to FTP Server 14 | 15 | .PARAMETER RemotePath 16 | Path on the FTP Server where to upload the content 17 | 18 | .PARAMETER FolderSyncMode 19 | Update - upload a folder and all its files 20 | Mirror - upload a folder and all its files, and delete extra files on the server 21 | 22 | .PARAMETER RemoteExists 23 | Provide decision what to do when file on the server exits. 24 | Options available: Append, AppendNoChek, NoCheck, Skip, , Overwrite 25 | Default: Skip. 26 | 27 | .PARAMETER VerifyOptions 28 | Provide options for verification of files on the remote server. 29 | Options available: Delete, OnlyChecksum, None, Retry 30 | Default: None. 31 | 32 | .PARAMETER Rules 33 | Provide rules and conditions for uploading files. 34 | 35 | .EXAMPLE 36 | Set-FTPTracing -Enable -DisplayConsole 37 | 38 | $Client = Connect-FTP -Server '192.168.241.187' -Verbose -Username 'test' -Password 'BiPassword90A' -EncryptionMode Explicit -ValidateAnyCertificate 39 | $Upload = Send-FTPDirectory -Client $Client -LocalPath $PSScriptRoot\Upload -RemotePath '/Temporary' -Verbose -FolderSyncMode Update 40 | $Upload | Format-Table * 41 | 42 | Disconnect-FTP -Client $Client 43 | 44 | Set-FTPTracing -Disable 45 | 46 | .NOTES 47 | General notes 48 | #> 49 | [alias('Add-FTPDirectory')] 50 | [cmdletBinding()] 51 | param( 52 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 53 | [string] $LocalPath, 54 | [Parameter(Mandatory)][string] $RemotePath, 55 | [FluentFTP.FtpFolderSyncMode] $FolderSyncMode = [FluentFTP.FtpFolderSyncMode]::Update, 56 | [FluentFTP.FtpRemoteExists] $RemoteExists = [FluentFTP.FtpRemoteExists]::Skip, 57 | [FluentFTP.FtpVerify] $VerifyOptions = [FluentFTP.FtpVerify]::None, 58 | [FluentFTP.Rules.FtpRule[]] $Rules 59 | ) 60 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 61 | if ($Rules) { 62 | $Client.UploadDirectory($LocalPath, $RemotePath, $FolderSyncMode, $RemoteExists, $VerifyOptions, @($Rules)) 63 | } else { 64 | $Client.UploadDirectory($LocalPath, $RemotePath, $FolderSyncMode, $RemoteExists, $VerifyOptions) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Public/Connect-SSH.ps1: -------------------------------------------------------------------------------- 1 | function Connect-SSH { 2 | [cmdletBinding(DefaultParameterSetName = 'Password')] 3 | param( 4 | [Parameter(Mandatory, ParameterSetName = 'ClearText')] 5 | [Parameter(Mandatory, ParameterSetName = 'Password')] 6 | [Parameter(Mandatory, ParameterSetName = 'PrivateKey')] 7 | [string] $Server, 8 | 9 | [Parameter(Mandatory, ParameterSetName = 'ClearText')] 10 | [Parameter(Mandatory, ParameterSetName = 'PrivateKey')] 11 | [string] $Username, 12 | 13 | [Parameter(Mandatory, ParameterSetName = 'ClearText')] 14 | [string] $Password, 15 | 16 | [Parameter(Mandatory, ParameterSetName = 'Password')] 17 | [pscredential] $Credential, 18 | 19 | [Parameter(Mandatory, ParameterSetName = 'PrivateKey')] 20 | [string] $PrivateKey, 21 | 22 | [Parameter(ParameterSetName = 'ClearText')] 23 | [Parameter(ParameterSetName = 'Password')] 24 | [Parameter(ParameterSetName = 'PrivateKey')] 25 | [int] $Port 26 | ) 27 | 28 | if ($Username -and $Password) { 29 | if ($Port) { 30 | $SshClient = [Renci.SshNet.SshClient]::new($Server, $Port, $Username, $Password) 31 | } else { 32 | $SshClient = [Renci.SshNet.SshClient]::new($Server, $Username, $Password) 33 | } 34 | } elseif ($Credential) { 35 | if ($Port) { 36 | $SshClient = [Renci.SshNet.SshClient]::new($Server, $Port, $Credential.Username, $Credential.GetNetworkCredential().Password) 37 | } else { 38 | $SshClient = [Renci.SshNet.SshClient]::new($Server, $Credential.Username, $Credential.GetNetworkCredential().Password) 39 | } 40 | } elseif ($PrivateKey) { 41 | [string]$PrivateKey = Resolve-Path $PrivateKey | Select-Object -ExpandProperty ProviderPath 42 | if ($Port) { 43 | $SshClient = [Renci.SshNet.SshClient]::new($Server, $Port, $Username, [Renci.SshNet.PrivateKeyFile]$PrivateKey ) 44 | } else { 45 | $SshClient = [Renci.SshNet.SshClient]::new($Server, $Username, [Renci.SshNet.PrivateKeyFile]$PrivateKey ) 46 | } 47 | } else { 48 | throw 'Not implemented and unexpected.' 49 | } 50 | 51 | try { 52 | $SshClient.Connect() 53 | $SshClient | Add-Member -Name 'Error' -Value $null -Force -MemberType NoteProperty 54 | } catch { 55 | $SshClient | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty 56 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 57 | Write-Error $_ 58 | return 59 | } else { 60 | Write-Warning "Connect-SSH - Error: $($_.Exception.Message)" 61 | } 62 | } 63 | $SshClient 64 | } 65 | -------------------------------------------------------------------------------- /Private/Get-PrivateFTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Get-PrivateFTPFile { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [string] $LocalPath, 6 | [FluentFTP.FtpListItem] $RemoteFile, 7 | [string] $RemotePath, 8 | [FluentFTP.FtpLocalExists] $LocalExists, 9 | [FluentFTP.FtpVerify[]] $VerifyOptions, 10 | [FluentFTP.FtpError] $FtpError 11 | ) 12 | if ($RemoteFile) { 13 | if ($RemoteFile.Type -eq 'File') { 14 | $FileToDownload = $RemoteFile.FullName 15 | } else { 16 | if (-not $Suppress) { 17 | return [PSCustomObject] @{ 18 | Action = 'DownloadFile' 19 | Status = $false 20 | IsSuccess = $false 21 | IsSkipped = $true 22 | IsSkippedByRule = $false 23 | LocalPath = $LocalPath 24 | RemotePath = $RemoteFile.FullName 25 | Message = "Receive-FTPFile - Given path $($RemoteFile.FullName) is $($RemoteFile.Type). Skipping." 26 | } 27 | } else { 28 | Write-Warning "Receive-FTPFile - Given path $($RemoteFile.FullName) is a directory. Skipping." 29 | return 30 | } 31 | } 32 | } else { 33 | $FileToDownload = $RemotePath 34 | } 35 | try { 36 | $Message = $Client.DownloadFile($LocalPath, $FileToDownload, $LocalExists, $VerifyOptions) 37 | if ($Message -eq 'success') { 38 | $State = $true 39 | } else { 40 | $State = $false 41 | } 42 | $Status = [PSCustomObject] @{ 43 | Action = 'DownloadFile' 44 | Status = $State 45 | IsSuccess = if ($State) { $true } else { $false } 46 | IsSkipped = $false 47 | IsSkippedByRule = $false 48 | IsFailed = if ($State) { $false } else { $true } 49 | LocalPath = $LocalPath 50 | RemotePath = $FileToDownload 51 | Message = $Message 52 | } 53 | } catch { 54 | $Status = [PSCustomObject] @{ 55 | Action = 'DownloadFile' 56 | Status = $false 57 | IsSuccess = $false 58 | IsSkipped = $false 59 | IsSkippedByRule = $false 60 | IsFailed = $true 61 | LocalPath = $LocalPath 62 | RemotePath = $FileToDownload 63 | Message = "Error: $($_.Exception.Message)" 64 | } 65 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 66 | Write-Error $_ 67 | return 68 | } else { 69 | Write-Warning "Receive-FTPFile - Error: $($_.Exception.Message)" 70 | } 71 | } 72 | $Status 73 | } -------------------------------------------------------------------------------- /Private/Get-PrivateFTPFiles.ps1: -------------------------------------------------------------------------------- 1 | function Get-PrivateFTPFiles { 2 | [cmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 5 | [string] $LocalPath, 6 | [FluentFTP.FtpListItem[]] $RemoteFile, 7 | [string[]] $RemotePath, 8 | [FluentFTP.FtpLocalExists] $LocalExists, 9 | [FluentFTP.FtpVerify[]] $VerifyOptions, 10 | [FluentFTP.FtpError] $FtpError 11 | ) 12 | 13 | if ($RemoteFile) { 14 | $FileToDownload = foreach ($File in $RemoteFile) { 15 | if ($File.Type -eq 'File') { 16 | $File.FullName 17 | } else { 18 | if (-not $Suppress) { 19 | [PSCustomObject] @{ 20 | Action = 'DownloadFile' 21 | Status = $false 22 | IsSuccess = $false 23 | IsSkipped = $true 24 | IsSkippedByRule = $false 25 | LocalPath = $LocalPath 26 | RemotePath = $File.FullName 27 | Message = "Receive-FTPFile - Given path $($RemoteFile.FullName) is $($RemoteFile.Type). Skipping." 28 | } 29 | } else { 30 | Write-Warning "Receive-FTPFile - Given path $($RemoteFile.FullName) is a directory. Skipping." 31 | } 32 | } 33 | } 34 | } else { 35 | $FileToDownload = $RemotePath 36 | } 37 | try { 38 | $Message = $Client.DownloadFiles($LocalPath, ([string[]] $FileToDownload), $LocalExists, $VerifyOptions, $FtpError) 39 | } catch { 40 | $Status = [PSCustomObject] @{ 41 | Action = 'DownloadFile' 42 | Status = $false 43 | IsSuccess = $false 44 | IsSkipped = $false 45 | IsSkippedByRule = $false 46 | IsFailed = $true 47 | LocalPath = $LocalPath 48 | RemotePath = $FileToDownload 49 | Message = "Error: $($_.Exception.Message)" 50 | } 51 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 52 | Write-Error $_ 53 | return 54 | } else { 55 | Write-Warning "Receive-FTPFile - Error: $($_.Exception.Message)" 56 | return $Status 57 | } 58 | } 59 | if (-not $Status) { 60 | foreach ($M in $Message) { 61 | [PSCustomObject] @{ 62 | Action = 'DownloadFile' 63 | Status = $M.IsSuccess 64 | IsSuccess = $M.IsSuccess 65 | IsSkipped = $M.IsSkipped 66 | IsSkippedByRule = $M.IsSkippedByRule 67 | IsFailed = $M.IsFailed 68 | LocalPath = $LocalPath 69 | RemotePath = $M.RemotePath 70 | Message = $M 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /Public/Receive-FTPFile.ps1: -------------------------------------------------------------------------------- 1 | function Receive-FTPFile { 2 | [alias('Get-FTPFile')] 3 | [cmdletBinding(DefaultParameterSetName = 'Text')] 4 | param( 5 | [Parameter(ParameterSetName = 'Text')] 6 | [Parameter(ParameterSetName = 'Native')] 7 | [Parameter(Mandatory)][FluentFTP.FtpClient] $Client, 8 | 9 | [Parameter(ParameterSetName = 'Native')] 10 | [FluentFTP.FtpListItem[]] $RemoteFile, 11 | 12 | [Parameter(ParameterSetName = 'Text')] 13 | [Parameter(ParameterSetName = 'Native')] 14 | [string[]] $RemotePath, 15 | 16 | [Parameter(ParameterSetName = 'Text')] 17 | [Parameter(ParameterSetName = 'Native')] 18 | [string] $LocalPath, 19 | 20 | [Parameter(ParameterSetName = 'Text')] 21 | [Parameter(ParameterSetName = 'Native')] 22 | [FluentFTP.FtpLocalExists] $LocalExists = [FluentFTP.FtpLocalExists]::Skip, 23 | 24 | [Parameter(ParameterSetName = 'Text')] 25 | [Parameter(ParameterSetName = 'Native')] 26 | [FluentFTP.FtpVerify[]] $VerifyOptions = [FluentFTP.FtpVerify]::None, 27 | 28 | [Parameter(ParameterSetName = 'Text')] 29 | [Parameter(ParameterSetName = 'Native')] 30 | [FluentFTP.FtpError] $FtpError = [FluentFTP.FtpError]::Stop, 31 | 32 | [Parameter(ParameterSetName = 'Text')] 33 | [Parameter(ParameterSetName = 'Native')] 34 | [switch] $Suppress 35 | ) 36 | if ($Client -and $Client.IsConnected -and -not $Client.Error) { 37 | $Path = Get-Item -LiteralPath $LocalPath -ErrorAction SilentlyContinue 38 | if ($Path -is [System.IO.DirectoryInfo]) { 39 | Get-PrivateFTPFiles -Client $Client -LocalPath $LocalPath -RemoteFile $RemoteFile -RemotePath $RemotePath -LocalExists $LocalExists -VerifyOptions $VerifyOptions -FtpError $FtpError 40 | } else { 41 | if ($RemoteFile.Count -gt 1 -or $RemotePath.Count -gt 1) { 42 | Write-Warning "Receive-FTPFile - Multiple files detected, but $LocalPath is not a directory or directory doesn't exists. " 43 | if ($RemoteFile) { 44 | $FileToDownload = $RemoteFile.FullName 45 | } else { 46 | $FileToDownload = $RemotePath 47 | } 48 | $Status = [PSCustomObject] @{ 49 | Action = 'DownloadFile' 50 | Status = $false 51 | LocalPath = $LocalPath 52 | RemotePath = $FileToDownload 53 | Message = "Multiple files detected, but $LocalPath is not a directory or directory doesn't exists." 54 | } 55 | } else { 56 | $Splat = @{ 57 | Client = $Client 58 | LocalExists = $LocalExists 59 | VerifyOptions = $VerifyOptions 60 | FtpError = $FtpError 61 | LocalPath = $LocalPath 62 | } 63 | if ($RemoteFile) { 64 | $Splat.RemoteFile = $RemoteFile[0] 65 | } else { 66 | $Splat.RemotePath = $RemotePath[0] 67 | } 68 | Get-PrivateFTPFile @Splat 69 | } 70 | } 71 | } else { 72 | $Status = [PSCustomObject] @{ 73 | Action = 'DownloadFile' 74 | Status = $false 75 | LocalPath = $LocalPath 76 | RemotePath = $FileToDownload 77 | Message = "Not connected." 78 | } 79 | } 80 | if (-not $Suppress) { 81 | $Status 82 | } 83 | } -------------------------------------------------------------------------------- /Public/Connect-SFTP.ps1: -------------------------------------------------------------------------------- 1 | function Connect-SFTP { 2 | [cmdletBinding(DefaultParameterSetName = 'Password')] 3 | param( 4 | [Parameter(ParameterSetName = 'ClearText', Mandatory)] 5 | [Parameter(ParameterSetName = 'Password', Mandatory)] 6 | [Parameter(ParameterSetName = 'PrivateKey', Mandatory)] 7 | [string] $Server, 8 | 9 | [Parameter(ParameterSetName = 'ClearText', Mandatory)] 10 | [Parameter(ParameterSetName = 'PrivateKey', Mandatory)] 11 | [string] $Username, 12 | 13 | [Parameter(ParameterSetName = 'ClearText', Mandatory)] 14 | [string] $Password, 15 | 16 | [Parameter(ParameterSetName = 'Password', Mandatory)] 17 | [pscredential] $Credential, 18 | 19 | [Parameter(Mandatory, ParameterSetName = 'PrivateKey')] 20 | [string] $PrivateKey, 21 | 22 | [Parameter(ParameterSetName = 'ClearText')] 23 | [Parameter(ParameterSetName = 'Password')] 24 | [Parameter(ParameterSetName = 'PrivateKey')] 25 | [int] $Port 26 | ) 27 | try { 28 | if ($Username -and $Password) { 29 | if ($Port) { 30 | $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Port, $Username, $Password) 31 | } else { 32 | $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Username, $Password) 33 | } 34 | } elseif ($Credential) { 35 | if ($Port) { 36 | $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Port, $Credential.Username, $Credential.GetNetworkCredential().Password) 37 | } else { 38 | $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Credential.Username, $Credential.GetNetworkCredential().Password) 39 | } 40 | } elseif ($PrivateKey) { 41 | if (Test-Path -LiteralPath $PrivateKey) { 42 | [string]$PrivateKey = Resolve-Path -LiteralPath $PrivateKey -ErrorAction Stop | Select-Object -ExpandProperty ProviderPath 43 | if ($Port) { 44 | $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Port, $Username, [Renci.SshNet.PrivateKeyFile]$PrivateKey ) 45 | } else { 46 | $SftpClient = [Renci.SshNet.SftpClient]::new($Server, $Username, [Renci.SshNet.PrivateKeyFile]$PrivateKey ) 47 | } 48 | } else { 49 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 50 | throw "PrivateKey $PrivateKey doesn't exists." 51 | return 52 | } else { 53 | Write-Warning "Connect-SFTP - PrivateKey $PrivateKey doesn't exists." 54 | return 55 | } 56 | } 57 | } else { 58 | throw 'Not implemented and unexepected.' 59 | return 60 | } 61 | } catch { 62 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 63 | Write-Error $_ 64 | return 65 | } else { 66 | Write-Warning "Connect-SFTP - Error: $($_.Exception.Message)" 67 | } 68 | } 69 | 70 | try { 71 | $SftpClient.Connect() 72 | $SftpClient | Add-Member -Name 'Error' -Value $null -Force -MemberType NoteProperty 73 | } catch { 74 | $SftpClient | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty 75 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 76 | Write-Error $_ 77 | return 78 | } else { 79 | Write-Warning "Connect-SFTP - Error: $($_.Exception.Message)" 80 | } 81 | } 82 | $SftpClient 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transferetto - PowerShell Module 2 | 3 |

4 | 5 | 6 | 7 | 8 |

9 | 10 |

11 | 12 | 13 | 14 | 15 |

16 | 17 |

18 | 19 | 20 | 21 |

22 | 23 | Transferetto is a PowerShell module that aims to provide FTP, FTPS, and SFTP functionality. 24 | To find out more about it I've created a blog post [Easy](https://evotec.xyz/easy-way-to-connect-to-ftps-and-sftp-using-powershell/) way to connect to FTPS and SFTP using PowerShell](https://evotec.xyz/easy-way-to-connect-to-ftps-and-sftp-using-powershell/). 25 | It uses the following .NET libraries to deliver this functionality: 26 | 27 | - [FluentFTP](https://github.com/robinrodricks/FluentFTP) 28 | - [SSH.NET](https://github.com/sshnet/SSH.NET/) 29 | 30 | Both libraries are MIT licenses. 31 | 32 | ## Features 33 | 34 | - FTPS/SFTP functionality 35 | - Connect to FTP, FTPS, SFTP 36 | - Upload/Download files from FTP/FTPS/SFTP 37 | - Rename SFTP files 38 | - Remove FTP/FTPS files 39 | - And some more 40 | 41 | Please make sure to read blog post or check examples to see how to use it. 42 | 43 | ## To install 44 | 45 | ```powershell 46 | Install-Module -Name Transferetto -AllowClobber -Force 47 | ``` 48 | 49 | Force and AllowClobber aren't necessary, but they do skip errors in case some appear. 50 | 51 | ## And to update 52 | 53 | ```powershell 54 | Update-Module -Name Transferetto 55 | ``` 56 | 57 | That's it. Whenever there's a new version, you run the command, and you can enjoy it. Remember that you may need to close, reopen PowerShell session if you have already used module before updating it. 58 | 59 | **The essential thing** is if something works for you on production, keep using it till you test the new version on a test computer. I do changes that may not be big, but big enough that auto-update may break your code. For example, a small rename to a parameter, and your code stops working! Be responsible! -------------------------------------------------------------------------------- /Sources/Transferetto/OnImportAndRemove.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Management.Automation; 4 | using System.Reflection; 5 | 6 | public class OnModuleImportAndRemove : IModuleAssemblyInitializer, IModuleAssemblyCleanup { 7 | public void OnImport() { 8 | //#if FRAMEWORK 9 | AppDomain.CurrentDomain.AssemblyResolve += MyResolveEventHandler; 10 | //#endif 11 | } 12 | 13 | public void OnRemove(PSModuleInfo module) { 14 | //#if FRAMEWORK 15 | AppDomain.CurrentDomain.AssemblyResolve -= MyResolveEventHandler; 16 | //#endif 17 | } 18 | 19 | private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args) { 20 | // These are known to be problematic in .NET Framework, force it to use our packaged dlls. 21 | if (args.Name.StartsWith("System.Memory,")) { 22 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "System.Memory.dll"); 23 | return Assembly.LoadFile(binPath); 24 | } else if (args.Name.StartsWith("System.Runtime.CompilerServices.Unsafe,")) { 25 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "System.Runtime.CompilerServices.Unsafe.dll"); 26 | return Assembly.LoadFile(binPath); 27 | } else if (args.Name.StartsWith("System.Numerics.Vectors,")) { 28 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "System.Numerics.Vectors.dll"); 29 | return Assembly.LoadFile(binPath); 30 | } else if (args.Name.StartsWith("System.Drawing.Common,")) { 31 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "System.Drawing.Common.dll"); 32 | return Assembly.LoadFile(binPath); 33 | } else if (args.Name.StartsWith("System.Buffers,")) { 34 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "System.Buffers.dll"); 35 | return Assembly.LoadFile(binPath); 36 | } else if (args.Name.StartsWith("System.ValueTuple,")) { 37 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "System.ValueTuple.dll"); 38 | return Assembly.LoadFile(binPath); 39 | } else if (args.Name.StartsWith("System.Text.Encoding.CodePages,")) { 40 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "System.Text.Encoding.CodePages.dll"); 41 | return Assembly.LoadFile(binPath); 42 | } else if (args.Name.StartsWith("BouncyCastle.Cryptography,")) { 43 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "BouncyCastle.Cryptography.dll"); 44 | return Assembly.LoadFile(binPath); 45 | } else if (args.Name.StartsWith("Newtonsoft.Json,")) { 46 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "Newtonsoft.Json.dll"); 47 | return Assembly.LoadFile(binPath); 48 | } else if (args.Name.StartsWith("Microsoft.Identity.Client,")) { 49 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "Microsoft.Identity.Client.dll"); 50 | return Assembly.LoadFile(binPath); 51 | } else if (args.Name.StartsWith("FluentFTP,")) { 52 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "FluentFTP.dll"); 53 | return Assembly.LoadFile(binPath); 54 | } else if (args.Name.StartsWith("Microsoft.Bcl.AsyncInterfaces,")) { 55 | string binPath = Path.Combine(Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location), "Microsoft.Bcl.AsyncInterfaces.dll"); 56 | return Assembly.LoadFile(binPath); 57 | } 58 | return null; 59 | } 60 | } -------------------------------------------------------------------------------- /Public/Request-FTPConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function Request-FTPConfiguration { 2 | <# 3 | .SYNOPSIS 4 | Short description 5 | 6 | .DESCRIPTION 7 | 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. 8 | 9 | Auto connection attempts to find working connection settings in this order of preference: 10 | 11 | Protocol Preference: 12 | 1. None - Let the OS decide which TLS/SSL version to use 13 | 2. Tls12 - TLS 1.2 (TLS 1.3 is not yet stable in .NET Framework) 14 | 3. Tls11 - TLS 1.1 15 | 4. Tls - TLS 1.0 16 | 5. Ssl3 - SSL 3.0 (obsolete, need to use TLS instead) 17 | 6. Ssl2 - SSL 2.0 (obsolete, need to use TLS instead) 18 | 7. Default - Undefined/weird behaviour 19 | 20 | Data Connection Type Preference: 21 | 22 | 1. PASV - We prefer passive as its the most reliable 23 | 2. EPSV - Enhanced passive is not as well supported on servers 24 | 3. PORT - PORT is an older connection type 25 | 4. EPRT - Enhanced PORT is not as well supported on servers 26 | 5. PASVEX 27 | 28 | Encoding Type Preference: 29 | 30 | 1. UTF8 - We prefer Unicode encoding as there will be no issues with file and folder names 31 | 2. ASCII - ASCII/ANSI is a fallback used for older servers 32 | 33 | .PARAMETER Server 34 | Server Name or IP Address to Connect 35 | 36 | .PARAMETER Username 37 | UserName for FTP Connection 38 | 39 | .PARAMETER Password 40 | Password for FTP Connection (cleartext) 41 | 42 | .PARAMETER Credential 43 | UserName and Password in form of Credentials 44 | 45 | .PARAMETER FirstOnly 46 | Returns first working profile 47 | 48 | .EXAMPLE 49 | # Login via UserName/Password 50 | $ProfileFtp1 = Request-FTPConfiguration -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' 51 | $ProfileFtp1 | Format-Table 52 | 53 | .EXAMPLE 54 | # Anonymous login 55 | $ProfileFtp2 = Request-FTPConfiguration -Server 'speedtest.tele2.net' -Verbose 56 | $ProfileFtp2 | Format-Table 57 | 58 | .NOTES 59 | General notes 60 | #> 61 | [cmdletBinding(DefaultParameterSetName = 'Password')] 62 | param( 63 | [Parameter(ParameterSetName = 'ClearText')] 64 | [Parameter(ParameterSetName = 'Password')] 65 | [string] $Server, 66 | 67 | [Parameter(ParameterSetName = 'ClearText')] 68 | [string] $Username, 69 | 70 | [Parameter(ParameterSetName = 'ClearText')] 71 | [string] $Password, 72 | 73 | [Parameter(ParameterSetName = 'Password')] 74 | [pscredential] $Credential, 75 | 76 | [Parameter(ParameterSetName = 'ClearText')] 77 | [Parameter(ParameterSetName = 'Password')] 78 | [int] $Port, 79 | 80 | [Parameter(ParameterSetName = 'ClearText')] 81 | [Parameter(ParameterSetName = 'Password')] 82 | [switch] $FirstOnly 83 | ) 84 | $Client = [FluentFTP.FtpClient]::new($Server) 85 | if ($Port) { 86 | $Client.Port = $Port 87 | } 88 | if ($Username -and $Password) { 89 | $Client.Credentials = [System.Net.NetworkCredential]::new($Username, $Password) 90 | } elseif ($Credential) { 91 | $Client.Credentials = [System.Net.NetworkCredential]::new($Credential.Username, $Credential.Password) 92 | } else { 93 | # anonymous 94 | } 95 | try { 96 | $Client.AutoDetect($FirstOnly.IsPresent) 97 | } catch { 98 | $Client | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty 99 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 100 | Write-Error $_ 101 | return 102 | } else { 103 | Write-Warning "Request-FTPConfiguration - Error: $($_.Exception.Message)" 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Tests/Connect-FTP.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Connect-FTP / Disconnect-FTP' { 2 | It 'Given no login and password it should connect to FTP and list' { 3 | # Anonymous login 4 | $Client = $Null 5 | $Client = Connect-FTP -Server 'ftp.gnu.org' -Verbose 6 | $Client.Host | Should -Be 'ftp.gnu.org' 7 | $Client.IsConnected | Should -Be $True 8 | $List = Get-FTPList -Client $Client 9 | $List.Count | Should -BeGreaterThan 5 10 | $Names = 'Name', 'Type', 'FullName', 'Modified', 'Created' 11 | foreach ($Name in @($Names)) { 12 | $List[0].PSObject.Properties.Name | Should -Contain $Name 13 | } 14 | Disconnect-FTP -Client $Client 15 | $Client.IsConnected | Should -Be $false 16 | } 17 | It 'Given login and password and Encryption Mode it should connect to FTP and list' { 18 | # Anonymous login 19 | $Client = $Null 20 | $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' -EncryptionMode Auto -SslBuffering Auto -SocketKeepAlive 21 | $Client.Host | Should -Be 'test.rebex.net' 22 | $Client.IsConnected | Should -Be $True 23 | $List = Get-FTPList -Client $Client 24 | $List.Count | Should -Be 2 25 | $Names = 'Name', 'Type', 'FullName', 'Modified', 'Created' 26 | foreach ($Name in @($Names)) { 27 | $List[0].PSObject.Properties.Name | Should -Contain $Name 28 | } 29 | Disconnect-FTP -Client $Client 30 | $Client.IsConnected | Should -Be $false 31 | } 32 | It 'Given login and password it should connect to FTP' { 33 | # Login via UserName/Password 34 | $Client = $Null 35 | $Client = Connect-FTP -Server 'ftp.dlptest.com' -Verbose -Username 'dlpuser' -Password 'rNrKYTX9g7z3RgJRmxWuGHbeu' 36 | $Client.Host | Should -Be 'ftp.dlptest.com' 37 | $Client.IsConnected | Should -Be $True 38 | $List = Get-FTPList -Client $Client 39 | $List.Count | Should -BeGreaterThan 1 40 | $Names = 'Name', 'Type', 'FullName', 'Modified', 'Created' 41 | foreach ($Name in @($Names)) { 42 | $List[0].PSObject.Properties.Name | Should -Contain $Name 43 | } 44 | Disconnect-FTP -Client $Client 45 | $Client.IsConnected | Should -Be $false 46 | } 47 | It 'Given login and password, and proxy Socks 5 it should conntect and list' { 48 | # Anonymous login 49 | $Client = $Null 50 | $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' -EncryptionMode Auto -SslBuffering Auto -SocketKeepAlive -ProxyHost '192.252.216.81' -ProxyPort 4145 -ProxyType FtpClientSocks5Proxy 51 | $Client.Host | Should -Be 'test.rebex.net' 52 | $Client.IsConnected | Should -Be $True 53 | $List = Get-FTPList -Client $Client 54 | $List.Count | Should -Be 2 55 | $Names = 'Name', 'Type', 'FullName', 'Modified', 'Created' 56 | foreach ($Name in @($Names)) { 57 | $List[0].PSObject.Properties.Name | Should -Contain $Name 58 | } 59 | Disconnect-FTP -Client $Client 60 | $Client.IsConnected | Should -Be $false 61 | } 62 | 63 | # It 'Given login and password, and proxy Socks 4a it should conntect and list' { 64 | # # Anonymous login 65 | # $Client = $Null 66 | # $Client = Connect-FTP -Server 'test.rebex.net' -Verbose -Username 'demo' -Password 'password' -EncryptionMode Auto -SslBuffering Auto -SocketKeepAlive -ProxyHost '104.37.135.145' -ProxyPort 4145 -ProxyType FtpClientSocks4aProxy 67 | # $Client.Host | Should -Be 'test.rebex.net' 68 | # $Client.IsConnected | Should -Be $True 69 | # $List = Get-FTPList -Client $Client 70 | # $List.Count | Should -Be 2 71 | # $Names = 'Name', 'Type', 'FullName', 'Modified', 'Created' 72 | # foreach ($Name in @($Names)) { 73 | # $List[0].PSObject.Properties.Name | Should -Contain $Name 74 | # } 75 | # Disconnect-FTP -Client $Client 76 | # $Client.IsConnected | Should -Be $false 77 | # } 78 | 79 | } -------------------------------------------------------------------------------- /Transferetto.psm1: -------------------------------------------------------------------------------- 1 | # Get public and private function definition files. 2 | $Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 3 | $Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 4 | $Classes = @( Get-ChildItem -Path $PSScriptRoot\Classes\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 5 | $Enums = @( Get-ChildItem -Path $PSScriptRoot\Enums\*.ps1 -ErrorAction SilentlyContinue -Recurse ) 6 | # Get all assemblies 7 | $AssemblyFolders = Get-ChildItem -Path $PSScriptRoot\Lib -Directory -ErrorAction SilentlyContinue 8 | 9 | # Lets find which libraries we need to load 10 | $Default = $false 11 | $Core = $false 12 | $Standard = $false 13 | foreach ($A in $AssemblyFolders.Name) { 14 | if ($A -eq 'Default') { 15 | $Default = $true 16 | } elseif ($A -eq 'Core') { 17 | $Core = $true 18 | } elseif ($A -eq 'Standard') { 19 | $Standard = $true 20 | } 21 | } 22 | if ($Standard -and $Core -and $Default) { 23 | $FrameworkNet = 'Default' 24 | $Framework = 'Standard' 25 | } elseif ($Standard -and $Core) { 26 | $Framework = 'Standard' 27 | $FrameworkNet = 'Standard' 28 | } elseif ($Core -and $Default) { 29 | $Framework = 'Core' 30 | $FrameworkNet = 'Default' 31 | } elseif ($Standard -and $Default) { 32 | $Framework = 'Standard' 33 | $FrameworkNet = 'Default' 34 | } elseif ($Standard) { 35 | $Framework = 'Standard' 36 | $FrameworkNet = 'Standard' 37 | } elseif ($Core) { 38 | $Framework = 'Core' 39 | $FrameworkNet = '' 40 | } elseif ($Default) { 41 | $Framework = '' 42 | $FrameworkNet = 'Default' 43 | } else { 44 | Write-Error -Message 'No assemblies found' 45 | } 46 | if ($PSEdition -eq 'Core') { 47 | $LibFolder = $Framework 48 | } else { 49 | $LibFolder = $FrameworkNet 50 | } 51 | 52 | $Assembly = @( 53 | if ($Framework -and $PSEdition -eq 'Core') { 54 | Get-ChildItem -Path $PSScriptRoot\Lib\$Framework\*.dll -ErrorAction SilentlyContinue -Recurse 55 | } 56 | if ($FrameworkNet -and $PSEdition -ne 'Core') { 57 | Get-ChildItem -Path $PSScriptRoot\Lib\$FrameworkNet\*.dll -ErrorAction SilentlyContinue -Recurse 58 | } 59 | # if ($AssemblyFolders.BaseName -contains 'Standard') { 60 | # @( Get-ChildItem -Path $PSScriptRoot\Lib\Standard\*.dll -ErrorAction SilentlyContinue -Recurse) 61 | # } 62 | # if ($PSEdition -eq 'Core') { 63 | # @( Get-ChildItem -Path $PSScriptRoot\Lib\Core\*.dll -ErrorAction SilentlyContinue -Recurse ) 64 | # } else { 65 | # @( Get-ChildItem -Path $PSScriptRoot\Lib\Default\*.dll -ErrorAction SilentlyContinue -Recurse ) 66 | # } 67 | ) 68 | 69 | # This is special way of importing DLL if multiple frameworks are in use 70 | $FoundErrors = @( 71 | # We load the DLL that does OnImportRemove if we have special module that requires special treatment for binary modules 72 | 73 | # Get library name, from the PSM1 file name 74 | $LibraryName = $myInvocation.MyCommand.Name.Replace(".psm1", "") 75 | $Library = "$LibraryName.dll" 76 | $Class = "$LibraryName.Initialize" 77 | 78 | # Ignore DLL files that are not NET Libraries 79 | $IgnoreLibraryFiles = @( 80 | 'libgcc_s_seh-1.dll' 81 | 'libgmp-10.dll' 82 | 'libgnutls-30.dll' 83 | 'libhogweed-6.dll' 84 | 'libnettle-8.dll' 85 | 'libwinpthread-1.dll' 86 | #ą 'Microsoft.Bcl.AsyncInterfaces.dll' 87 | ) 88 | 89 | try { 90 | $ImportModule = Get-Command -Name Import-Module -Module Microsoft.PowerShell.Core 91 | 92 | if (-not ($Class -as [type])) { 93 | & $ImportModule ([IO.Path]::Combine($PSScriptRoot, 'Lib', $LibFolder, $Library)) -ErrorAction Stop 94 | } else { 95 | $Type = "$Class" -as [Type] 96 | & $importModule -Force -Assembly ($Type.Assembly) 97 | } 98 | } catch { 99 | Write-Warning -Message "Importing module $Library failed. Fix errors before continuing. Error: $($_.Exception.Message)" 100 | $true 101 | } 102 | 103 | Foreach ($Import in @($Assembly)) { 104 | if ($IgnoreLibraryFiles -contains $Import.Name) { 105 | continue 106 | } 107 | try { 108 | Add-Type -Path $Import.Fullname -ErrorAction Stop 109 | } catch [System.Reflection.ReflectionTypeLoadException] { 110 | Write-Warning "Processing $($Import.Name) Exception: $($_.Exception.Message)" 111 | $LoaderExceptions = $($_.Exception.LoaderExceptions) | Sort-Object -Unique 112 | foreach ($E in $LoaderExceptions) { 113 | Write-Warning "Processing $($Import.Name) LoaderExceptions: $($E.Message)" 114 | } 115 | $true 116 | } catch { 117 | Write-Warning "Processing $($Import.Name) Exception: $($_.Exception.Message)" 118 | $LoaderExceptions = $($_.Exception.LoaderExceptions) | Sort-Object -Unique 119 | foreach ($E in $LoaderExceptions) { 120 | Write-Warning "Processing $($Import.Name) LoaderExceptions: $($E.Message)" 121 | } 122 | $true 123 | } 124 | } 125 | 126 | #Dot source the files 127 | Foreach ($Import in @($Private + $Classes + $Enums + $Public)) { 128 | Try { 129 | . $Import.Fullname 130 | } Catch { 131 | Write-Warning -Message "Failed to import functions from $($import.Fullname).Error: $($_.Exception.Message)" 132 | $true 133 | } 134 | } 135 | ) 136 | 137 | if ($FoundErrors.Count -gt 0) { 138 | $ModuleName = (Get-ChildItem $PSScriptRoot\*.psd1).BaseName 139 | Write-Warning "Importing module $ModuleName failed. Fix errors before continuing." 140 | break 141 | } 142 | 143 | Export-ModuleMember -Function '*' -Alias '*' -Cmdlet '*' -------------------------------------------------------------------------------- /Build/Manage-Module.ps1: -------------------------------------------------------------------------------- 1 | Clear-Host 2 | 3 | Import-Module 'C:\Support\GitHub\PSPublishModule\PSPublishModule.psd1' -Force 4 | 5 | Build-Module -ModuleName 'Transferetto' { 6 | # Usual defaults as per standard module 7 | $Manifest = [ordered] @{ 8 | # Version number of this module. 9 | ModuleVersion = '1.0.0' 10 | # Supported PSEditions 11 | CompatiblePSEditions = @('Desktop', 'Core') 12 | # ID used to uniquely identify this module 13 | GUID = '7d61db15-9efe-41d1-a1c0-81d738975dec' 14 | # Author of this module 15 | Author = 'Przemyslaw Klys' 16 | # Company or vendor of this module 17 | CompanyName = 'Evotec' 18 | # Copyright statement for this module 19 | Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved." 20 | # Description of the functionality provided by this module 21 | Description = 'Module which allows ftp, ftps, sftp file transfers with advanced features. It also allows to transfer files and directorires between servers using fxp protocol. As a side feature it allows to conenct to SSH and executes commands on it. ' 22 | # Minimum version of the Windows PowerShell engine required by this module 23 | PowerShellVersion = '5.1' 24 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 25 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 26 | Tags = @('Windows', 'Linux', 'MacOs', 'ftp', 'sftp', 'ftps', 'scp', 'winscp', 'ssh') 27 | 28 | IconUri = 'https://evotec.xyz/wp-content/uploads/2021/03/Transferetto.png' 29 | 30 | ProjectUri = 'https://github.com/EvotecIT/Transferetto' 31 | 32 | # DotNetFrameworkVersion = '4.7.2' 33 | } 34 | New-ConfigurationManifest @Manifest #-Prerelease 'Alpha1' 35 | # Add external module dependencies, using loop for simplicity 36 | New-ConfigurationModule -Type ExternalModule -Name 'Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Utility' 37 | 38 | # Add approved modules, that can be used as a dependency, but only when specific function from those modules is used 39 | # And on that time only that function and dependant functions will be copied over 40 | # Keep in mind it has it's limits when "copying" functions such as it should not depend on DLLs or other external files 41 | New-ConfigurationModule -Type ApprovedModule -Name 'PSSharedGoods', 'PSWriteColor', 'Connectimo', 'PSUnifi', 'PSWebToolbox', 'PSMyPassword' 42 | 43 | $ConfigurationFormat = [ordered] @{ 44 | RemoveComments = $false 45 | 46 | PlaceOpenBraceEnable = $true 47 | PlaceOpenBraceOnSameLine = $true 48 | PlaceOpenBraceNewLineAfter = $true 49 | PlaceOpenBraceIgnoreOneLineBlock = $false 50 | 51 | PlaceCloseBraceEnable = $true 52 | PlaceCloseBraceNewLineAfter = $true 53 | PlaceCloseBraceIgnoreOneLineBlock = $false 54 | PlaceCloseBraceNoEmptyLineBefore = $true 55 | 56 | UseConsistentIndentationEnable = $true 57 | UseConsistentIndentationKind = 'space' 58 | UseConsistentIndentationPipelineIndentation = 'IncreaseIndentationAfterEveryPipeline' 59 | UseConsistentIndentationIndentationSize = 4 60 | 61 | UseConsistentWhitespaceEnable = $true 62 | UseConsistentWhitespaceCheckInnerBrace = $true 63 | UseConsistentWhitespaceCheckOpenBrace = $true 64 | UseConsistentWhitespaceCheckOpenParen = $true 65 | UseConsistentWhitespaceCheckOperator = $true 66 | UseConsistentWhitespaceCheckPipe = $true 67 | UseConsistentWhitespaceCheckSeparator = $true 68 | 69 | AlignAssignmentStatementEnable = $true 70 | AlignAssignmentStatementCheckHashtable = $true 71 | 72 | UseCorrectCasingEnable = $true 73 | } 74 | # format PSD1 and PSM1 files when merging into a single file 75 | # enable formatting is not required as Configuration is provided 76 | New-ConfigurationFormat -ApplyTo 'OnMergePSM1', 'OnMergePSD1' -Sort None @ConfigurationFormat 77 | # format PSD1 and PSM1 files within the module 78 | # enable formatting is required to make sure that formatting is applied (with default settings) 79 | New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'DefaultPSM1' -EnableFormatting -Sort None 80 | # when creating PSD1 use special style without comments and with only required parameters 81 | New-ConfigurationFormat -ApplyTo 'DefaultPSD1', 'OnMergePSD1' -PSD1Style 'Minimal' 82 | 83 | # configuration for documentation, at the same time it enables documentation processing 84 | New-ConfigurationDocumentation -Enable:$false -StartClean -UpdateWhenNew -PathReadme 'Docs\Readme.md' -Path 'Docs' 85 | 86 | New-ConfigurationImportModule -ImportSelf #-ImportRequiredModules 87 | 88 | $newConfigurationBuildSplat = @{ 89 | Enable = $true 90 | SignModule = $true 91 | MergeModuleOnBuild = $true 92 | MergeFunctionsFromApprovedModules = $true 93 | CertificateThumbprint = '483292C9E317AA13B07BB7A96AE9D1A5ED9E7703' 94 | # require for FluentFTP to work in PS 5.1 in VSCode, works fine outside 95 | ResolveBinaryConflicts = $true 96 | ResolveBinaryConflictsName = 'Transferetto' 97 | NETProjectName = 'Transferetto' 98 | NETConfiguration = 'Release' 99 | NETFramework = 'netstandard2.0' 100 | #NETExcludeMainLibrary = $true 101 | DotSourceLibraries = $true 102 | DotSourceClasses = $true 103 | #SeparateFileLibraries = $true 104 | DeleteTargetModuleBeforeBuild = $true 105 | NETIgnoreLibraryOnLoad = @( 106 | 'libgcc_s_seh-1.dll' 107 | 'libgmp-10.dll' 108 | 'libgnutls-30.dll' 109 | 'libhogweed-6.dll' 110 | 'libnettle-8.dll' 111 | 'libwinpthread-1.dll' 112 | #'Microsoft.Bcl.AsyncInterfaces.dll' 113 | ) 114 | } 115 | 116 | New-ConfigurationBuild @newConfigurationBuildSplat 117 | 118 | $newConfigurationArtefactSplat = @{ 119 | Type = 'Unpacked' 120 | Enable = $true 121 | Path = "$PSScriptRoot\..\Artefacts\Unpacked" 122 | ModulesPath = "$PSScriptRoot\..\Artefacts\Unpacked\Modules" 123 | RequiredModulesPath = "$PSScriptRoot\..\Artefacts\Unpacked\Modules" 124 | AddRequiredModules = $true 125 | CopyFiles = @{ 126 | #"Examples\PublishingExample\Example-ExchangeEssentials.ps1" = "RunMe.ps1" 127 | } 128 | } 129 | New-ConfigurationArtefact @newConfigurationArtefactSplat -CopyFilesRelative 130 | $newConfigurationArtefactSplat = @{ 131 | Type = 'Packed' 132 | Enable = $true 133 | Path = "$PSScriptRoot\..\Artefacts\Packed" 134 | ModulesPath = "$PSScriptRoot\..\Artefacts\Packed\Modules" 135 | RequiredModulesPath = "$PSScriptRoot\..\Artefacts\Packed\Modules" 136 | AddRequiredModules = $true 137 | CopyFiles = @{ 138 | #"Examples\PublishingExample\Example-ExchangeEssentials.ps1" = "RunMe.ps1" 139 | } 140 | ArtefactName = '.v.zip' 141 | } 142 | New-ConfigurationArtefact @newConfigurationArtefactSplat 143 | 144 | #New-ConfigurationTest -TestsPath "$PSScriptRoot\..\Tests" -Enable 145 | 146 | # global options for publishing to github/psgallery 147 | #New-ConfigurationPublish -Type PowerShellGallery -FilePath 'C:\Support\Important\PowerShellGalleryAPI.txt' -Enabled:$true 148 | #New-ConfigurationPublish -Type GitHub -FilePath 'C:\Support\Important\GitHubAPI.txt' -UserName 'EvotecIT' -Enabled:$true 149 | } -ExitCode -------------------------------------------------------------------------------- /Public/Connect-FTP.ps1: -------------------------------------------------------------------------------- 1 | function Connect-FTP { 2 | [cmdletBinding(DefaultParameterSetName = 'Password')] 3 | param( 4 | [Parameter(ParameterSetName = 'FtpProfile')] 5 | [Parameter(ParameterSetName = 'ClearText')] 6 | [Parameter(ParameterSetName = 'Password')] 7 | [string] $ProxyHost, 8 | 9 | [Parameter(ParameterSetName = 'FtpProfile')] 10 | [Parameter(ParameterSetName = 'ClearText')] 11 | [Parameter(ParameterSetName = 'Password')] 12 | [int] $ProxyPort, 13 | 14 | [Parameter(ParameterSetName = 'FtpProfile')] 15 | [Parameter(ParameterSetName = 'ClearText')] 16 | [Parameter(ParameterSetName = 'Password')] 17 | [pscredential] $ProxyCredential, 18 | 19 | [Parameter(ParameterSetName = 'FtpProfile')] 20 | [Parameter(ParameterSetName = 'ClearText')] 21 | [Parameter(ParameterSetName = 'Password')] 22 | [string] $ProxyUserName, 23 | 24 | [Parameter(ParameterSetName = 'FtpProfile')] 25 | [Parameter(ParameterSetName = 'ClearText')] 26 | [Parameter(ParameterSetName = 'Password')] 27 | [string] $ProxyPassword, 28 | 29 | [Parameter(ParameterSetName = 'FtpProfile')] 30 | [Parameter(ParameterSetName = 'ClearText')] 31 | [Parameter(ParameterSetName = 'Password')] 32 | [ValidateSet( 33 | 'FtpClientSocks5Proxy', 34 | 'FtpClientHttp11Proxy', 35 | 'FtpClientSocks4aProxy', 36 | 'FtpClientSocks4Proxy', 37 | 'FtpClientUserAtHostProxy', 38 | 'FtpClientBlueCoatProxy' 39 | )][string] $ProxyType, 40 | 41 | [Parameter(ParameterSetName = 'FtpProfile')] 42 | [FluentFTP.FtpProfile] $FtpProfile, 43 | 44 | [Parameter(ParameterSetName = 'ClearText')] 45 | [Parameter(ParameterSetName = 'Password')] 46 | [string] $Server, 47 | 48 | [Parameter(ParameterSetName = 'ClearText')] 49 | [string] $Username, 50 | 51 | [Parameter(ParameterSetName = 'ClearText')] 52 | [string] $Password, 53 | 54 | [Parameter(ParameterSetName = 'Password')] 55 | [pscredential] $Credential, 56 | 57 | [Parameter(ParameterSetName = 'ClearText')] 58 | [Parameter(ParameterSetName = 'Password')] 59 | [FluentFTP.FtpEncryptionMode[]] $EncryptionMode, 60 | 61 | [Parameter(ParameterSetName = 'ClearText')] 62 | [Parameter(ParameterSetName = 'Password')] 63 | [FluentFTP.FtpDataConnectionType] $DataConnectionType, 64 | 65 | 66 | [Parameter(ParameterSetName = 'ClearText')] 67 | [Parameter(ParameterSetName = 'Password')] 68 | [FluentFTP.FtpsBuffering] $SslBuffering, 69 | 70 | [Parameter(ParameterSetName = 'ClearText')] 71 | [Parameter(ParameterSetName = 'Password')] 72 | [switch] $DisableDataConnectionEncryption, 73 | 74 | [Parameter(ParameterSetName = 'ClearText')] 75 | [Parameter(ParameterSetName = 'Password')] 76 | [switch] $DisableValidateCertificateRevocation, 77 | 78 | [Parameter(ParameterSetName = 'ClearText')] 79 | [Parameter(ParameterSetName = 'Password')] 80 | [switch] $ValidateAnyCertificate, 81 | 82 | [Parameter(ParameterSetName = 'ClearText')] 83 | [Parameter(ParameterSetName = 'Password')] 84 | [int] $Port, 85 | 86 | [Parameter(ParameterSetName = 'ClearText')] 87 | [Parameter(ParameterSetName = 'Password')] 88 | [switch] $SendHost, 89 | 90 | [Parameter(ParameterSetName = 'ClearText')] 91 | [Parameter(ParameterSetName = 'Password')] 92 | [switch] $SocketKeepAlive, 93 | 94 | [Parameter(ParameterSetName = 'ClearText')] 95 | [Parameter(ParameterSetName = 'Password')] 96 | [switch] $AutoConnect 97 | ) 98 | $ClientInternal = $null 99 | if ($PSBoundParameters.ContainsKey('ProxyHost') -or $PSBoundParameters.ContainsKey('ProxyPort') -or $PSBoundParameters.ContainsKey('ProxyType')) { 100 | if ($PSBoundParameters.ContainsKey('ProxyHost') -and $PSBoundParameters.ContainsKey('ProxyType')) { 101 | $ProxyProfile = [FluentFTP.FtpProxyProfile]::new() 102 | $ProxyProfile.ProxyHost = $ProxyHost 103 | $ProxyProfile.ProxyPort = $ProxyPort 104 | 105 | if ($ProxyUsername -and $ProxyPassword) { 106 | $ProxyProfile.ProxyCredentials = [System.Net.NetworkCredential]::new($Username, $Password) 107 | } elseif ($ProxyCredential) { 108 | $ProxyProfile.ProxyCredentials = [System.Net.NetworkCredential]::new($ProxyCredential.Username, $ProxyCredential.Password) 109 | } else { 110 | # anonymous 111 | } 112 | 113 | $ProxyProfile.FtpHost = $Server 114 | if ($Port) { 115 | $ProxyProfile.FtpPort = $Port 116 | } 117 | 118 | if ($Username -and $Password) { 119 | $ProxyProfile.FTPCredentials = [System.Net.NetworkCredential]::new($Username, $Password) 120 | } elseif ($Credential) { 121 | $ProxyProfile.FTPCredentials = [System.Net.NetworkCredential]::new($Credential.Username, $Credential.Password) 122 | } else { 123 | # anonymous 124 | } 125 | 126 | if ($ProxyType -eq 'FtpClientSocks5Proxy') { 127 | $ClientInternal = [FluentFTP.Proxy.SyncProxy.FtpClientSocks5Proxy]::new($ProxyProfile) 128 | } elseif ($ProxyType -eq 'FtpClientHttp11Proxy') { 129 | $ClientInternal = [FluentFTP.Proxy.SyncProxy.FtpClientHttp11Proxy]::new($ProxyProfile) 130 | } elseif ($ProxyType -eq 'FtpClientSocks4aProxy') { 131 | $ClientInternal = [FluentFTP.Proxy.SyncProxy.FtpClientSocks4aProxy]::new($ProxyProfile) 132 | } elseif ($ProxyType -eq 'FtpClientSocks4Proxy') { 133 | $ClientInternal = [FluentFTP.Proxy.SyncProxy.FtpClientSocks4Proxy]::new($ProxyProfile) 134 | } elseif ($ProxyType -eq 'FtpClientUserAtHostProxy') { 135 | $ClientInternal = [FluentFTP.Proxy.SyncProxy.FtpClientUserAtHostProxy]::new($ProxyProfile) 136 | } elseif ($ProxyType -eq 'FtpClientBlueCoatProxy') { 137 | $ClientInternal = [FluentFTP.Proxy.SyncProxy.FtpClientBlueCoatProxy]::new($ProxyProfile) 138 | } 139 | } else { 140 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 141 | Write-Error "ProxyHost, ProxyPort, and ProxyType must be specified together when using Proxy. Only ProxyUserName, ProxyPassword or ProxyCredential are optional." 142 | return 143 | } else { 144 | Write-Warning "Connect-FTP - ProxyHost, ProxyPort, and ProxyType must be specified together when using Proxy. Only ProxyUserName, ProxyPassword or ProxyCredential are optional." 145 | } 146 | } 147 | } 148 | if ($FtpProfile) { 149 | if (-not $ClientInternal) { 150 | $ClientInternal = [FluentFTP.FtpClient]::new() 151 | } 152 | $ClientInternal.LoadProfile($FtpProfile) 153 | } else { 154 | if (-not $ClientInternal) { 155 | $ClientInternal = [FluentFTP.FtpClient]::new($Server) 156 | } 157 | if ($Username -and $Password) { 158 | $ClientInternal.Credentials = [System.Net.NetworkCredential]::new($Username, $Password) 159 | } elseif ($Credential) { 160 | $ClientInternal.Credentials = [System.Net.NetworkCredential]::new($Credential.Username, $Credential.Password) 161 | } else { 162 | # anonymous 163 | } 164 | } 165 | if ($Script:GlobalFTPLogging) { 166 | $ClientInternal.Config.LogHost = $Script:GlobalFTPLogging.LogHost 167 | $ClientInternal.Config.LogUsername = $Script:GlobalFTPLogging.LogUsername 168 | $ClientInternal.Config.LogPassword = $Script:GlobalFTPLogging.LogPassword 169 | $ClientInternal.Config.LogToConsole = $Script:GlobalFTPLogging.LogToConsole 170 | } 171 | 172 | if ($Port) { 173 | $ClientInternal.Port = $Port 174 | } 175 | if ($DataConnectionType) { 176 | $ClientInternal.Config.DataConnectionType = $DataConnectionType 177 | } 178 | if ($DisableDataConnectionEncryption) { 179 | $ClientInternal.Config.DataConnectionEncryption = $false 180 | } 181 | if ($EncryptionMode) { 182 | $ClientInternal.Config.EncryptionMode = $EncryptionMode 183 | } 184 | if ($ValidateAnyCertificate) { 185 | $ClientInternal.Config.ValidateAnyCertificate = $true 186 | } 187 | if ($DisableValidateCertificateRevocation) { 188 | $ClientInternal.Config.ValidateCertificateRevocation = $false 189 | } 190 | if ($SendHost) { 191 | $ClientInternal.Config.SendHost = $true 192 | } 193 | if ($SocketKeepAlive) { 194 | $ClientInternal.Config.SocketKeepAlive = $true 195 | } 196 | if ($FtpsBuffering) { 197 | $ClientInternal.Config.SslBuffering = $SslBuffering 198 | } 199 | try { 200 | if ($AutoConnect) { 201 | $TempFtpProfile = $ClientInternal.AutoConnect() 202 | if ($TempFtpProfile -and $ClientInternal.IsConnected) { 203 | Write-Verbose "Following options where used to autoconnect: " 204 | foreach ($Name in $TempFtpProfile.PSObject.Properties.Name) { 205 | Write-Verbose "[x] $Name -> $($TempFtpProfile.$Name)" 206 | } 207 | } 208 | } else { 209 | $ClientInternal.Connect() 210 | } 211 | $ClientInternal | Add-Member -Name 'Error' -Value $null -Force -MemberType NoteProperty 212 | } catch { 213 | $ClientInternal | Add-Member -Name 'Error' -Value $($_.Exception.Message) -Force -MemberType NoteProperty 214 | if ($PSBoundParameters.ErrorAction -eq 'Stop') { 215 | Write-Error $_ 216 | return 217 | } else { 218 | Write-Warning "Connect-FTP - Error: $($_.Exception.Message)" 219 | } 220 | } 221 | $ClientInternal 222 | } --------------------------------------------------------------------------------