Search code examples
powershellftpwinscpwinscp-net

Moving (not copying) remote files after download with WinSCP .NET assembly


I have this script that downloads all .txt and .log files. But I need to move them to another directory on the server after the download.

So far I just keep getting errors like "cannot move "file" to "/file".

try
{
    # Load WinSCP .NET assembly
    Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"

    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.Protocol = [WinSCP.Protocol]::ftp
    $sessionOptions.HostName = "host"
    $sessionOptions.PortNumber = "port"
    $sessionOptions.UserName = "user"
    $sessionOptions.Password = "pass"

    $session = New-Object WinSCP.Session

    try
    {
        # Connect
        $session.DisableVersionCheck = "true"
        $session.Open($sessionOptions)

        $localPath = "C:\users\user\desktop\file"
        $remotePath = "/"
        $fileName = "*.txt"
        $fileNamee = "*.log"
        $remotePath2 = "/completed"
        $directoryInfo = $session.ListDirectory($remotePath)
        $directoryInfo = $session.ListDirectory($remotePath2)

        # Download the file
        $session.GetFiles(($remotePath + $fileName), $localPath).Check()
        $session.GetFiles(($remotePath + $fileNamee), $localPath).Check()

        $session.MoveFile(($remotePath + $fileName, $remotePath2)).Check()
        $session.MoveFile(($remotePath + $fileNamee, $remotePath2)).Check() 

    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }

    exit 0
}
catch [Exception]
{
    Write-Host $_.Exception.Message
    exit 1
}

Solution

  • You have many problems in your code:


    The targetPath argument of the Session.MoveFile method is a path to move/rename the file to.

    So, if you use the target path /complete, you are trying to move the file to a root folder and rename it to the complete. While you probably want to move the file to folder the /complete, and keep its name. For that use the target path /complete/ (or the /complete/* to make it more obvious).

    Your current code fails, because you are renaming the file to a name of an already existing folder.


    You actually have the same bug in the .GetFiles. You are downloading all files (both *.txt and *.log) to the folder C:\users\user\desktop and save them all to the same name file, overwriting one another.


    You have brackets incorrectly around both arguments, instead of around the first argument only. While I'm no PowerShell expert, I'd actually say you are omitting the second argument of the method completely this way.


    Further, note that the MoveFile method does not return anything (contrary to the GetFiles). So there's no object to call the .Check() method on.


    The MoveFile (note the singular, comparing to the GetFiles), moves only a single file. So you should not use a file mask. Actually the present implementation allows a use of the file mask, but this use is undocumented and may be deprecated in future versions.

    Anyway, the best solution is to iterate the list of actually downloaded files, as returned by the GetFiles and move the files one by one.

    This way you avoid race condition, where you download set of files, new files are added (which you didn't download) and you incorrectly move them to the "completed" folder.


    The code should look like (for the first set of files only, i.e. the *.txt):

    $remotePath2 = "/completed/"
    
    ...
    
    $transferResult = $session.GetFiles(($remotePath + $fileName), $localPath)
    $transferResult.Check()
    
    foreach ($transfer in $transferResult.Transfers)
    {
        $session.MoveFile($transfer.FileName, $remotePath2) 
    }
    

    Note that this does not include a fix for the $localPath, as I'm not sure, what the path C:\users\user\desktop\file actually mean.


    There's actually a very similar sample code available:
    Moving local files to different location after successful upload