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 

4 thoughts on “PowerShell: Use Win-SCP to Download Files from SFTP Server”

  1. The script is not working and giving error – WARNING: The value supplied is not valid, or the property is read-only. Change the value, and then try again.

    1. Hi Subodh, this script has only been tested in a controlled environment (a ‘jump box’). Without knowing or having access to your environment, it’s difficult to know which line of script is throwing the error. Please play around with the variables and let me know if you would like to provide more info. Also, if you’ve fixed an error in script, I’d be appreciative of your sharing.

    1. Hi Nicola, could it be that this script must be ran in the context of a System Administrator? Please run PowerShell as System Administrator and paste script (after updating the static variables at the top of script). Let me know if we’re still experience issues. Also, paste some error messages so that I can see which line is causing errors. Thanks

Leave a Reply

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