PowerShell: Use Win-SCP to Download Files from SFTP Server

Version 2:

# downloadFilesViaSftp.ps1
# Version 0.0.2
# 
# Description:
# This simple script is to download files from a remote Linux server via SFTP
#
# Features:
# 1. Connect to remote SFTP server and verify contents prior to downloading
# 2. Translate Unix/Linux line-feed (LF) characters to carriage-returns (CRLF) in text files for Windows/ASP.net compatiblity
# 3. Send email notifictions to inform recipients of successful or failed downloads


# Environmental variables
$sftpHost='sftp.kimconnect.com'
$portNumber=22
$sftpUsername='test'
$sftpPassword='password'
$sshHostKey='ssh-rsa 4096 11:22:33:44:55:aa:bb:cc:dd:ee...'
$localDirectory='D:\Downloads'
$remoteDirectory='/home/test/edi'
$winScpAssembly='C:\Program Files (x86)\WinSCP\WinSCPnet.dll'

# Email relay parameters
$emailFrom='devops@kimconnect.com'
$emailTo='edi-admins@kimconnect.com'
$subject='EDI File Downloads'
$smtpRelayServer='relay.kimconnect.com'

# Sanitize inputs
$localDirectory=if($localDirectory[$localDirectory.Length-1] -eq '*'){
    $localDirectory.Substring(0,$localDirectory.Length-1)
}elseif($localDirectory[$localDirectory.Length-1] -ne '\'){
    $localDirectory+'\'
}else{
    $localDirectory
}
$remoteDirectory=if($remoteDirectory[$remoteDirectory.Length-1] -eq '*'){
    $remoteDirectory.Substring(0,$remoteDirectory.Length-1)
}elseif($remoteDirectory[$remoteDirectory.Length-1] -ne '/'){
    $remoteDirectory+'/'
}else{
    $remoteDirectory
}
function downloadFilesViaSftp{
    param(
        [string]$sftpHost='sftp.kimconnect.com',
        [int]$portNumber=22,
        [string]$sftpUsername='test',
        [string]$sftpPassword='password',
        [string]$sshHostKey='ssh-rsa 4096 11:22:33:44:55:aa:bb:cc:dd:ee...',
        [string]$localDirectory='D:\Downloads',
        [string]$remoteDirectory='/home/test/edi',
        [string]$winScpAssembly='C:\ProgramData\chocolatey\bin\WinSCPnet.dll'
    )

    function formatTextFileWindowsCompatible{
        param(
            $file,
            $regexMatch="(?<!\r)\n$",
            $replaceWith="`r`n"
        )
        if([IO.Path]::GetExtension($file) -ne '.txt'){
            break
        }else{
            $content=get-content $file
            $newContent=$content|%{$_.Replace($regexMatch,$replaceWith)}
            [System.IO.File]::WriteAllText($file,$($newContent|out-string))
        }
    }
    try{
        Add-Type -Path $winScpAssembly # Load WinSCP .NET assembly into this session
        $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = [WinSCP.Protocol]::Sftp
            HostName = $sftpHost
            PortNumber = $portNumber
            UserName = $sftpUsername
            Password = $sftpPassword
            SshHostKeyFingerprint = $sshHostKey
        }
        $session = New-Object WinSCP.Session
        $session.Open($sessionOptions)
    }catch{
        write-warning  $_
        return $false
    }
    if($session){
        Try{
            if(!(test-path $localDirectory)) {
                new-item -Path $localDirectory -Type Directory -Force
            }
            # Checking whether there are 0 bytes files that would cause GetFiles method to freeze
            $downloadedFiles=$emptyFiles=$filesToDownload=@()
            $listDirectory = $session.ListDirectory($remoteDirectory)
            foreach ($file in $listDirectory.Files){
                if($file.Length){
                    Write-Host ("{0}{1} {2,9} {3,-12:MMM dd HH:mm:ss yyyy} {4}" -f
                    $file.FileType, $fileInfo.FilePermissions, $file.Length,
                    $file.LastWriteTime, $file.Name)
                    $filesToDownload+=$file.Name
                }elseif($file.Name -ne '..'){
                    Write-Host ("{0}{1} {2,9} {3,-12:MMM dd HH:mm:ss yyyy} {4}" -f
                    $file.FileType, $fileInfo.FilePermissions, $file.Length,
                    $file.LastWriteTime, $file.Name) -ForegroundColor Red
                    $emptyFiles+=$file.Name
                }               
            }
            if($filesToDownload){
                $transferOptions = New-Object WinSCP.TransferOptions
                $transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
                foreach ($fileName in $filesToDownload){
                    $transferResult=$session.GetFiles($RemoteDirectory+$fileName,$localDirectory,$False,$transferOptions)        
                    if($transferResult.Transfers.count -gt 0){
                        Write-Host "Download of $($transferResult.FileName) succeeded" -ForegroundColor Green
                        $downloadedFile=$([IO.Path]::Combine($localDirectory,$fileName))                    
                        $downloadedFiles+=$downloadedFile
                        fixFile $downloadedFile
                    }else{
                        Write-Host "Download of $fileName failed" -ForegroundColor Red
                    }
                }
            }else{
                write-host "There are no files to download."
            }

            # $fileNames=$session.ListDirectory($remoteDirectory).Files.Name|?{$_ -ne '..'}
            # $destinationFiles=$fileNames|%{[IO.Path]::Combine($localDirectory,$_)}
            # $transferOptions = New-Object WinSCP.TransferOptions
            # $transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
            # $transferResult=$session.GetFiles($RemoteDirectory+'*',$localDirectory,$False,$transferOptions)        
            # if($transferResult.Transfers.count -gt 0){
            #     foreach ($transfer in $transferResult.Transfers){
            #         Write-Host "Download of $($transfer.FileName) succeeded"
            #     }            
            # }            
            # $destinationFiles|%{formatTextFileWindowsCompatible $_}
            return [pscustomobject]@{
                downloadedFiles=$downloadedFiles
                emptyFiles=$emptyFiles
            }
        }Catch{
            write-warning $_
            $result=$false
        }finally{
            $session.Dispose()
        }
        return $result
    }
}

$result=downloadFilesViaSftp -sftpHost $sftpHost `
    -portNumber $portNumber `
    -sftpUsername $sftpUsername `
    -sftpPassword $sftpPassword  `
    -sshHostKey $sshHostKey `
    -localDirectory $localDirectory `
    -remoteDirectory $remoteDirectory `
    -winScpAssembly $winScpAssembly 

if($result){
    $emailContent="Good day $emailTo,<br><br>As of $((get-date|out-string).trim())<br><br>"
    $emailContent+=if($result.downloadedFiles){
            "These new files have successfully downloaded:<br>$(($result.downloadedFiles|out-string).trim())<br>"
        }else{
            "No files have been downloaded.<br>"
        }
    $emailContent+=if($result.emptyFiles){
            "These files have no contents and are not downloaded:<br>$(($result.emptyFiles|out-string).trim())<br><br>Please double check with the source."
        }else{''}
    Send-MailMessage -From $emailFrom `
    -To $emailTo `
    -Subject $subject `
    -Body $emailContent `
    -BodyAsHtml `
    -SmtpServer $smtpRelayServer  
}

Version 1:

# downloadFilesViaSftp.ps1
# Version 0.0.1
# This simple script is to download files from a remote Linux server via SFTP
# There's a special sequence to handle .txt files
# by replacing Linux line-feed 'lf' chars with their Windows carriage-return line-feed 'crlf' equivalence

# Environmental variables
$sftpHost='sftp.kimconnect.com'
$portNumber=22
$sftpUsername='test'
$sftpPassword='password'
$sshHostKey='ssh-rsa 4096 11:22:33:44:55:aa:bb:cc:dd:ee...'
$localDirectory='D:\Downloads'
$remoteDirectory='/home/test/edi'
$winScpAssembly='C:\Program Files (x86)\WinSCP\WinSCPnet.dll'

function downloadFilesViaSftp{
    param(
        $sftpHost='sftp.kimconnect.com',
        $portNumber=22,
        $sftpUsername='test',
        $sftpPassword='password',
        $sshHostKey='ssh-rsa 4096 11:22:33:44:55:aa:bb:cc:dd:ee...',
        $localDirectory='D:\Downloads',
        $remoteDirectory='/home/test/edi',
        $winScpAssembly='C:\ProgramData\chocolatey\bin\WinSCPnet.dll'
    )

    function formatTextFileWindowsCompatible{
        param(
            $file,
            $regexMatch="(?<!\r)\n$",
            $replaceWith="`r`n"
        )
        if([IO.Path]::GetExtension($file) -ne '.txt'){
            break
        }else{
            $content=get-content $file
            $newContent=$content|%{$_.Replace($regexMatch,$replaceWith)}
            [System.IO.File]::WriteAllText($file,$($newContent|out-string))
        }
    }
    try{
        Add-Type -Path $winScpAssembly # Load WinSCP .NET assembly into this session
        $sessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = [WinSCP.Protocol]::Sftp
            HostName = $sftpHost
            PortNumber = $portNumber
            UserName = $sftpUsername
            Password = $sftpPassword
            SshHostKeyFingerprint = $sshHostKey
        }
        $session = New-Object WinSCP.Session
        $session.Open($sessionOptions)
    }catch{
        write-warning  $_
        return $false
    }
    if($session){
        Try{
            if(!(test-path $localDirectory)) {
                new-item -Path $localDirectory -Type Directory -Force
            }
            $fileNames = $session.ListDirectory($remoteDirectory).Files.Name
            $destinationFiles=$fileNames|%{[IO.Path]::Combine($localDirectory,$_)}
            $transferOptions = New-Object WinSCP.TransferOptions
            $transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
            $session.GetFiles($remoteDirectory,$localDirectory,$False,$transferOptions)
            $destinationFiles|%{formatTextFileWindowsCompatible $_}
            $result=$true
        }Catch{
            write-warning $_
            $result=$false
        }finally{
            $session.Dispose()
        }
        return $result
    }
}

downloadFilesViaSftp -sftpHost $sftpHost `
    -portNumber $portNumber `
    -sftpUsername $sftpUsername `
    -sftpPassword $sftpPassword  `
    -sshHostKey $sshHostKey `
    -localDirectory $localDirectory `
    -remoteDirectory $remoteDirectory `
    -winScpAssembly $winScpAssembly 

Leave a Reply

Your email address will not be published. Required fields are marked *