This is an old revision of the document!

Documentation » Using WinSCP » Guides » Scripting/Automation »

SFTP file transfers in .NET

This guide describes how to implement SFTP transfer in .NET application using WinSCP.

WinSCP is SFTP client with scripting interface that you can use to automate many operations that it supports, including file transfers, synchronization and other operations. So WinSCP is not a library (e.g. .NET assembly) that you can call directly. Though this guides shows you how to use it seamlessly from the .NET code.

Advertisement

Before starting you should:

Using WinSCP from .NET Code

To run winscp.com use System.Diagnostics.Process. This class allows running any executable, possibly redirecting its standard input and output to a stream accessible from .NET code. Code below expects that winscp.com (ProcessStartInfo.FileName) can be found in current working directory or in search path. You need to provide full path otherwise.

You can use standard input redirection (ProcessStartInfo.RedirectStandardInput) to feed scripting commands, sparing necessity to assemble temporary script file.1

Redirection of standard output is less useful, as output of WinSCP does not have any predefined form (cannot be parsed). Though it can be useful to capture it, in case you want to show it to a user in your GUI or for diagnostic purposes.

To capture results of script, you can use XML logging. For this you need to instruct WinSCP to store log file using /log command-line parameter (ProcessStartInfo.Arguments).

const string logname = "log.xml";
 
Process winscp = new Process();
winscp.StartInfo.FileName = "winscp.com";
winscp.StartInfo.Arguments = "/log=" + logname;
winscp.StartInfo.UseShellExecute = false;
winscp.StartInfo.RedirectStandardInput = true;
winscp.StartInfo.CreateNoWindow = true;
winscp.Start();

Advertisement

To feed commands to standard input use Process.StandardInput stream:

winscp.StandardInput.WriteLine("option batch abort");
winscp.StandardInput.WriteLine("option confirm off");
winscp.StandardInput.WriteLine("open mysession");
winscp.StandardInput.WriteLine("ls");
winscp.StandardInput.WriteLine("put d:\\examplefile.txt");

Now you need to wait for WinSCP to finish before you can safely start reading the log file:

winscp.StandardInput.Close();
winscp.WaitForExit();

If you want to collect the output, redirect the standard output before starting WinSCP (ProcessStartInfo.RedirectStandardOutput) and read from output stream (Process.StandardOutput). You need to collect the output before calling Process.WaitForExit. The output stream has limited capacity. Once it gets filled, WinSCP hangs waiting for free space, never finishing.

winscp.StartInfo.RedirectStandardOutput = true;
...
string output = winscp.StandardOutput.ReadToEnd();

Once WinSCP script finishes, you should check the results. First check exit code (Process.ExitCode) of the process:

if (winscp.ExitCode != 0)
{
    /// Error processing
}
else
{
    /// Success processing
}

To analyse results further, you may parse and interpret the XML log. First learn about XML logging to understand basic concepts.

The .NET library offers several classes for handling XML documents. The following example uses System.Xml.XPath.XPathDocument class.

XPathDocument log = new XPathDocument(logname);
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("w", "http://winscp.net/schema/session/1.0");
XPathNavigator nav = log.CreateNavigator();

In case of error you may check for message elements to capture any associated error messages:

foreach (XPathNavigator message in nav.Select("//w:message", ns))
{
    Console.WriteLine(message.Value);
}

Advertisement

In case of success, you can extract directory listing generated by ls command inside ls element:

XPathNodeIterator files = nav.Select("//w:file", ns);
Console.WriteLine(string.Format("There are {0} files and subdirectories:", files.Count));
foreach (XPathNavigator file in files)
{
    Console.WriteLine(file.SelectSingleNode("w:filename/@value", ns).Value);
}

Full C# Example

Individual parts of this example are explained in the previous chapter.

using System;
using System.Diagnostics;
using System.Xml;
using System.Xml.XPath;
 
...
 
const string logname = "log.xml";
 
/// Run hidden WinSCP process
Process winscp = new Process();
winscp.StartInfo.FileName = "winscp.com";
winscp.StartInfo.Arguments = "/log=" + logname;
winscp.StartInfo.UseShellExecute = false;
winscp.StartInfo.RedirectStandardInput = true;
winscp.StartInfo.RedirectStandardOutput = true;
winscp.StartInfo.CreateNoWindow = true;
winscp.Start();
 
/// Feed in the scripting commands
winscp.StandardInput.WriteLine("option batch abort");
winscp.StandardInput.WriteLine("option confirm off");
winscp.StandardInput.WriteLine("open mysession");
winscp.StandardInput.WriteLine("ls");
winscp.StandardInput.WriteLine("put d:\\examplefile.txt");
winscp.StandardInput.Close();
 
/// Collect all output (not used in this example)
string output = winscp.StandardOutput.ReadToEnd();
 
/// Wait until WinSCP finishes
winscp.WaitForExit();
 
/// Parse and interpret the XML log
/// (Note that in case of fatal failure the log file may not exist at all)
XPathDocument log = new XPathDocument(logname);
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("w", "http://winscp.net/schema/session/1.0");
XPathNavigator nav = log.CreateNavigator();
 
/// Success (0) or error?
if (winscp.ExitCode != 0)
{
    Console.WriteLine("Error occured");
 
    /// See if there are any messages associated with the error
    foreach (XPathNavigator message in nav.Select("//w:message", ns))
    {
        Console.WriteLine(message.Value);
    }
}
else
{
    /// It can be worth looking for directory listing even in case of
    /// error as possibly only upload may fail
    
    XPathNodeIterator files = nav.Select("//w:file", ns);
    Console.WriteLine(string.Format("There are {0} files and subdirectories:", files.Count));
    foreach (XPathNavigator file in files)
    {
        Console.WriteLine(file.SelectSingleNode("w:filename/@value", ns).Value);
    }
}

Advertisement

Full VB.NET Example

Individual parts of this example are explained in the first chapter. Note that the VB.NET example was not tested. It is based on C# example above though, which was. Feel free to fix it.

Imports System
Imports System.Diagnostics
Imports System.Xml
Imports System.Xml.XPath
 
...
 
Const logname As String = "log.xml"
 
' Run hidden WinSCP process
Dim winscp As Process = New Process()
winscp.StartInfo.FileName = "winscp.com"
winscp.StartInfo.Arguments = "/log=" + logname
winscp.StartInfo.UseShellExecute = False
winscp.StartInfo.RedirectStandardInput = True
winscp.StartInfo.RedirectStandardOutput = True
winscp.StartInfo.CreateNoWindow = True
winscp.Start()
 
' Feed in the scripting commands
winscp.StandardInput.WriteLine("option batch abort")
winscp.StandardInput.WriteLine("option confirm off")
winscp.StandardInput.WriteLine("open mysession")
winscp.StandardInput.WriteLine("ls")
winscp.StandardInput.WriteLine("put d:\examplefile.txt")
winscp.StandardInput.Close()
 
' Collect all output (not used in this example)
Dim output As String = winscp.StandardOutput.ReadToEnd()
 
' Wait until WinSCP finishes
winscp.WaitForExit()
 
' Parse and interpret the XML log
' (Note that in case of fatal failure the log file may not exist at all)
Dim log As XPathDocument = New XPathDocument(logname)
Dim ns As XmlNamespaceManager = New XmlNamespaceManager(New NameTable())
ns.AddNamespace("w", "http://winscp.net/schema/session/1.0")
Dim nav As XPathNavigator = log.CreateNavigator()
 
' Success (0) or error?
If winscp.ExitCode <> 0 Then
 
    Console.WriteLine("Error occured")
 
    ' See if there are any messages associated with the error
    For Each message As XPathNavigator In nav.Select("//w:message", ns)
        Console.WriteLine(message.Value)
    Next
 
Else
 
    ' It can be worth looking for directory listing even in case of
    ' error as possibly only upload may fail
    
    Dim files As XPathNodeIterator = nav.Select("//w:file", ns)
    Console.WriteLine(string.Format("There are {0} files and subdirectories:", files.Count))
    For Each file As XPathNavigator In files
        Console.WriteLine(file.SelectSingleNode("w:filename/@value", ns).Value)
    Next
 
End If

Advertisement

Modified the above example to be a more robust vb.net function implementation:

Imports System
Imports System.IO
Imports System.Diagnostics
Imports System.Xml
Imports System.Xml.XPath
Imports System.Configuration.ConfigurationManager

Public Class SFTP
    ' SFTP support, built on WinSCP
    ' AF Goldberg, July 2010
    Public Shared Function PutSFTP(ByRef filename As String, ByRef remotehost As String, ByRef username As String, ByRef password As String, _
                           Optional ByVal outfilename As String = Nothing, Optional ByVal output As String = Nothing,
                           Optional ByRef errmsg As String = Nothing) As Boolean

        ' Run hidden WinSCP process
        Dim winscp As Process = New Process()
        Dim logname As String = Path.ChangeExtension(Path.GetTempFileName, "xml")
        With winscp.StartInfo
            ' SFTPExecutable needs to be defined in app.config to point to winscp.com
            Try
                .FileName = AppSettings("SFTPExecutable")
                If .FileName Is Nothing OrElse .FileName.Length = 0 Then Throw (New Exception("from PutSFTP: SFTPExecutable not set in config file."))
            Catch ex As Exception
                errmsg = ex.Message
                Return False
            End Try
            .Arguments = "/log=" + logname
            .UseShellExecute = False
            .RedirectStandardInput = True
            .RedirectStandardOutput = True
            .CreateNoWindow = True
        End With
        Try
            winscp.Start()
        Catch ex As Exception
            errmsg = "from PutSFTP:  Could not run the WinSCP executable " & winscp.StartInfo.FileName & Environment.NewLine & ex.Message
            Return False
        End Try

        ' Feed in the scripting commands
        With winscp.StandardInput
            .WriteLine("option batch abort")
            .WriteLine("option confirm off")
            .WriteLine("open sftp://" & username & ":" & password & "@" & remotehost)
            If outfilename Is Nothing Then .WriteLine("put " & filename) Else .WriteLine("put " & filename & " """ & outfilename & """")
            .Close()
        End With
        If Not output Is Nothing Then output = winscp.StandardOutput.ReadToEnd()

        ' Wait until WinSCP finishes
        winscp.WaitForExit()

        ' Parse and interpret the XML log
        ' (Note that in case of fatal failure the log file may not exist at all)
        If Not File.Exists(logname) Then
            errmsg = "from PutSFTP:  The WinSCP executable appears to have crashed."
            Return False
        End If

        Dim log As XPathDocument = New XPathDocument(logname)
        Dim ns As XmlNamespaceManager = New XmlNamespaceManager(New NameTable())
        ns.AddNamespace("w", "http://winscp.net/schema/session/1.0")
        Dim nav As XPathNavigator = log.CreateNavigator()

        ' Success (0) or error?
        Dim status As Boolean = (winscp.ExitCode = 0)
        If Not status Then
            errmsg = "from PutSFTP:  There was an error transferring " & filename & "."
            ' See if there are any messages associated with the error
            For Each message As XPathNavigator In nav.Select("//w:message", ns)
                errmsg &= Environment.NewLine & message.Value
            Next message
        End If

        Try
            My.Computer.FileSystem.DeleteFile(logname)
        Catch ex As Exception
            ' at least we tried to clean up
        End Try

        Return status
    End Function
End Class

Advertisement

  1. Of course unless what you plan to do is actually execution of existing script file.Back