SFTP automation for Windows PowerShell with WinSCP .NET assembly and COM library

Asankha Perera
6 min readJan 12, 2023

The MFT Gateway allows you the ability to easily send and receive files over the secure AS2 protocol for Internet based file transfer. While the MFT Gateway offers many integration options such as a REST API with webhooks and direct AWS S3 based file integration, many organizations might still choose to integrate existing systems using SFTP.

MFT Gateway for AS2 file transfer with S3, SFTP and REST APIs and Webhooks based integration

While SFTP is natively available on Linux and similar operating systems, automation of SFTP on Windows usually requires purchased software. This article explains how you can use the popular free and open-source software WinSCP as a library and use Windows PowerShell scripting to automate a file exchange.

The MFT Gateway SFTP File Structure

The folder structure exposed for SFTP integration is of the following simplified form, and since a single AS2 message can contain multiple files, or the same file name can be repeated by a partner, the MFT Gateway creates a unique directory to contain files of each AS2 message. At a high level, this results in an inbox folder structure as follows.

../inbox/<unique-directory>/<file-name>

Now an SFTP script must list contents of each unique directory within the inbox and download one or more files that might exist under it.

WinSCP Scripting

WinSCP has very good support for scripting, along with detailed documentation on how to use those capabilities. While using the scripting interface directly is recommended for simple tasks not requiring any control structures, WinSCP recommends the use of the WinSCP .NET assembly and the COM library, for more complex automation needs.

Using WinSCP with Windows PowerShell

To get started, download the WinSCP .NET assembly from the download page, and refer to the installation instructions. It is best to install the distribution to a location without spaces in the path. Note that by default, executing PowerShell scripts will be disabled. Trying to run a PowerShell script without an execution policy will usually result in an error like below.

PS C:\opt\WinSCP-5.21.5-Automation\Demo> .\mftg-sftp-download.ps1
.\mftg-sftp-download.ps1 : File C:\opt\WinSCP-5.21.5-Automation\Demo\mftg-sftp-download.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170.

You can choose to lift this restriction by executing the following cmdlet from a PowerShell administrative console.

Set-ExecutionPolicy Unrestricted

Or you may bypass this by executing scripts as follows.

PS C:\opt\WinSCP-5.21.5-Automation\Demo> powershell -ExecutionPolicy Bypass -File .\mftg-sftp-download.ps1

Loading the Assembly

To load the assembly, use the ‘Add-Type’ cmdlet, along with the full path to the WinSCP DLL.

# Load WinSCP .NET assembly
Add-Type -Path "C:\opt\WinSCP-5.21.5-Automation\WinSCPnet.dll"

Setting up a connection

To setup a connection, create a sessionOptions variable and set the hostname, username and the SSH keypath and fingerprint etc. You would also want to define a variable transferOptions, to select he ‘Binary’ mode of transfer for files.

# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "sftp.mftgateway.com"
UserName = "asankha"
SshPrivateKeyPath = "C:\opt\WinSCP-5.21.5-Automation\Demo\asankha.ppk"
SshHostKeyFingerprint = "ssh-rsa 2048 12:b7:cf:7a:55:9a:77:43:f2:87:d6:43:09:92:88:15"
}

$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary

Iterating directories and getting a file count

Remember our main requirement to be able to handle files within unique subdirectories containing one or more files? So, we will list the root directory and get a count of the total number of files, and also the individual path names.

try {
# Connect to obtain file enumeration
Write-Host "Connecting..."
$session = New-Object WinSCP.Session
$session.Open($sessionOptions)

$remotePath = "/mftg-asankha.com/AS2/files/ACP_PROD/P1/inbox/"
$localPath = "C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\"

Write-Host "Starting files enumeration..."
$directory = $session.ListDirectory($remotePath)
$subdirs = $directory.Files.FullName
}
finally
{
# Disconnect, clean up
$session.Dispose()
}

[System.Collections.ArrayList]$msgs = @()
foreach ($sub in $subdirs)
{
[void]$msgs.Add($sub)
}

$count = $msgs.Count
Write-Host "Total messages found for download: $count"

Downloading individual files

Finally, we are ready to start downloading individual files, and we will create a new session for this task, and we can optionally delete the files after we successfully download them — by uncommenting the ‘$downloadSession.RemoveFiles($file)’ line below.

try {
Write-Host "Starting download ..."
$downloadSession = New-Object WinSCP.Session
$downloadSession.Open($sessionOptions)

while ($True)
{
$count = ($msgs).Count
if ($count -eq 0) {
break
}

$file = ($msgs)[0]
($msgs).RemoveAt(0)

if ($file -match '/..$') {
Write-Host "Skipping parent directory"
break
}

$count = ($msgs).Count

$remote = -join($file, "/");

Write-Host "Downloading $remote to $localPath ..."
$transferResult = $downloadSession.GetFiles($remote, $localPath, $False, $transferOptions)

# Did the download succeeded?
if (!$transferResult.IsSuccess)
{
# Print error (but continue with other files)
Write-Host ("Error downloading file $file " + "$($transferResult.Failures[0].Message)")
} else {
($stats).count++
# Delete remote file and directory after successful download
# $downloadSession.RemoveFiles($file)
}
}

Write-Host "Download completed"
}
finally
{
$downloadSession.Dispose()
}

Sample Execution Output

Execution of this script will yield an output as below. Notice that we have bypassed the PowerShell execution policy for simplicity. Once you have tested your script, you may choose to schedule it for production use.

PS C:\opt\WinSCP-5.21.5-Automation\Demo> powershell -ExecutionPolicy Bypass -File .\mftg-sftp-download.ps1
Connecting...
Starting files enumeration...
Total messages found for download: 5
Starting download ...
Downloading /mftg-asankha.com/AS2/files/ACP_PROD/P1/inbox/956246844612951/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
Downloading /mftg-asankha.com/AS2/files/ACP_PROD/P1/inbox/956251031647737/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
Downloading /mftg-asankha.com/AS2/files/ACP_PROD/P1/inbox/956251096571533/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
Downloading /mftg-asankha.com/AS2/files/ACP_PROD/P1/inbox/956251102345887/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
Skipping parent directory
Download completed
Took 00:00:19.7675095
Downloaded 4 files

Complete working script

Here is the complete working script for my Windows 11 laptop where this was developed.

# Sample WinSCP script to download MFT Gateway received files using SFTP
#
# @author Asankha - Aayu Technologies LLC - 11 Jan 2022
#
# Download WinSCP .Net assembly from https://winscp.net/eng/downloads.php#additional and extract to "C:\opt\WinSCP-5.21.5-Automation" as an example
#
# Execution example
# PS C:\opt\WinSCP-5.21.5-Automation\Demo> powershell -ExecutionPolicy Bypass -File .\mftg-sftp-download.ps1
# Connecting...
# Starting files enumeration...
# Total messages found for download: 5
# Starting download ...
# Downloading /mftg-asankha.com/AS2/files/ACP_PROD/AdvanceAutoParts/inbox/956246844612951/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
# Downloading /mftg-asankha.com/AS2/files/ACP_PROD/AdvanceAutoParts/inbox/956251031647737/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
# Downloading /mftg-asankha.com/AS2/files/ACP_PROD/AdvanceAutoParts/inbox/956251096571533/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
# Downloading /mftg-asankha.com/AS2/files/ACP_PROD/AdvanceAutoParts/inbox/956251102345887/ to C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\ ...
# Skipping parent directory
# Download completed
# Took 00:00:19.7675095
# Downloaded 4 files

try {
[Console]::TreatControlCAsInput = $True
Start-Sleep -Seconds 1
$Host.UI.RawUI.FlushInputBuffer()

# Load WinSCP .NET assembly
Add-Type -Path "C:\opt\WinSCP-5.21.5-Automation\WinSCPnet.dll"

# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "sftp.mftgateway.com"
UserName = "asankha"
SshPrivateKeyPath = "C:\opt\WinSCP-5.21.5-Automation\Demo\asankha.ppk"
SshHostKeyFingerprint = "ssh-rsa 2048 12:b7:cf:7a:55:9a:77:43:f2:87:d6:43:09:92:88:15"
}

$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary

try {
# Connect to obtain file enumeration
Write-Host "Connecting..."
$session = New-Object WinSCP.Session
$session.Open($sessionOptions)

$remotePath = "/mftg-asankha.com/AS2/files/ACP_PROD/AdvanceAutoParts/inbox/"
$localPath = "C:\opt\WinSCP-5.21.5-Automation\Demo\inbox\"

Write-Host "Starting files enumeration..."
$directory = $session.ListDirectory($remotePath)
$subdirs = $directory.Files.FullName
}
finally
{
# Disconnect, clean up
$session.Dispose()
}

[System.Collections.ArrayList]$msgs = @()
foreach ($sub in $subdirs)
{
[void]$msgs.Add($sub)
}

$count = $msgs.Count

Write-Host "Total messages found for download: $count"

$started = Get-Date
$stats = @{
count = 0
bytes = [long]0
}

try {
Write-Host "Starting download ..."
$downloadSession = New-Object WinSCP.Session
$downloadSession.Open($sessionOptions)

while ($True)
{
$count = ($msgs).Count
if ($count -eq 0) {
break
}

$file = ($msgs)[0]
($msgs).RemoveAt(0)

if ($file -match '/..$') {
Write-Host "Skipping parent directory"
break
}

$count = ($msgs).Count

$remote = -join($file, "/");

Write-Host "Downloading $remote to $localPath ..."
$transferResult = $downloadSession.GetFiles($remote, $localPath, $False, $transferOptions)

# Did the download succeeded?
if (!$transferResult.IsSuccess)
{
# Print error (but continue with other files)
Write-Host ("Error downloading file $file " + "$($transferResult.Failures[0].Message)")
} else {
($stats).count++
# Delete remote file and directory after successful download
# $downloadSession.RemoveFiles($file)
}
}

Write-Host "Download completed"
}
finally
{
$downloadSession.Dispose()
}

$ended = Get-Date
Write-Host "Took $(New-TimeSpan -Start $started -End $ended)"
Write-Host ("Downloaded $($stats.count) files")
exit 0
}
catch
{
Write-Host "Error: $($_.Exception.Message)"
exit 1
}

MFT Gateway for AS2 File Transfer

MFT Gateway is a hosted Software as a Service (SaaS) product by Aayu Technologies LLC, which allows companies to easily support AS2 file transfers, without installing and maintaining software, or allocating resources and personnel.

Its pricing is based on the total number of files exchanged per month and starts from $9/month and scales to support millions of files even on a single day — due to its native serverless architecture and the capabilities of the AWS cloud infrastructure used underneath. Since each file is stored on AWS S3, the accumulation of files over time or the size of files does not impact its performance at all, as shown by real AS2 performance testing.

--

--

Asankha Perera

Founder and CEO Aayu Technologies LLC & AdroitLogic. Disrupting B2B AS2/EDI with modern Serverless technology