PowerShell FTPS upload fails with "System error."
Asked Answered
I

3

3

The problem:

A client requires that we upload extracted data from our system to their box.com platform, rather than our normal SFTP utility. I have box.com credentials, and am aware they require FTPS not SFTP, and require passive mode. I've cribbed a fragment from ThomasMaurer's Powershell FTP Upload and Download script. Powershell version on my server is 4.0

Code fragment is:

#config 
$Username = "[email protected]"
$Password = "redactedpassword"
$LocalFile = "C:\path\to\my\file.csv"
$RemoteFile = "ftp://ftp.box.com:990/file.csv"
#Create FTPWebRequest
$FTPRequest = [System.Net.FtpWebRequest]::Create($RemoteFile)
$FTPRequest = [System.Net.FtpWebRequest]$FTPRequest
$FTPRequest.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$FTPRequest.Credentials = New-Object System.Net.NetworkCredential($Username, $Password)
$FTPRequest.UseBinary = $true
$FTPRequest.UsePassive = $true
#read file for upload
$FileContent = gc -en byte $LocalFile
$FTPRequest.ContentLength = $FileContent.Length
#get stream request by bytes
$run = $FTPRequest.GetRequestStream()
$run.Write($FileContent,0,$FileContent.Length)
#cleanup
$run.Close()
$run.Dispose()

The error(s):

Exception calling "GetRequestStream" with "0" argument(s): "System error." At C:\path\to\my\powershellscript.ps1:28 char:1
+ $Run = $FTPRequest.GetRequestStream()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: () [], MethodInvocationException
+ FullyQualifiedErrorId: WebException

I also get downstream errors on calling the $FileContent.Length property and $run.close and $run.dispose().

Has anyone successfully automated to box (specifically) or to a passive implicit-ssl using only PowerShell 4.0 commands, and do you have a solid pattern I could reuse? Many thanks

Inarch answered 3/6, 2016 at 5:49 Comment(0)
V
2

I'm uploading files with a derived version of System.Net.WebClient, which supports FTP over TLS. This can easily be achieved by embedding C# code in PowerShell:

$typeDefinition = @"
using System;
using System.Net;
public class FtpClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri address)
    {
        FtpWebRequest ftpWebRequest = base.GetWebRequest(address) as FtpWebRequest;
        ftpWebRequest.EnableSsl = true;
        return ftpWebRequest;
    }
}
"@

Add-Type -TypeDefinition $typeDefinition
$ftpClient = New-Object FtpClient
$ftpClient.UploadFile("ftp://your-ftp-server/yourfile.name", "STOR", "C:\YourLocalFile.name")
Viradis answered 14/4, 2017 at 21:34 Comment(1)
This worked after several other documented fixes did not, thank you for this!Autogiro
U
1

The answer by @h0r41i0 solves the problem by using WebClient. But as the WebClient internally uses (Ftp)WebRequest, it cannot be the solution on its own.

I'll assume that the "System error" occurs because either OP is trying to connect to a secure port (990) with an insecure connection.

Or because the file is too large and the OP code tries to read it whole to memory:

$FileContent = gc -en byte $LocalFile

In either case, there's no reason to give up on FtpWebRequest. Just use a secure connection (FtpWebRequest.EnableSsl). And an efficient way to feed the data from the file to the FTP stream, for example Stream.CopyTo:

$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials = New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile 
$request.EnableSsl = $True

$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()

$fileStream.CopyTo($ftpStream)

$ftpStream.Dispose()
$fileStream.Dispose()

For other options, see Upload files with FTP using PowerShell.

Though note that .NET framework does not support implicit TLS (what is typical use of 990). Only explicit TLS. But support for the explicit TLS is more common ayway. See Does .NET FtpWebRequest Support both Implicit (FTPS) and explicit (FTPES)?

Underquote answered 5/6, 2020 at 11:7 Comment(0)
D
0

Probably too late to be useful to original questioner, but I found this other answer did the trick for me: Cyril Gupta's answer to Upload files with ftp using powershell

Here is my revised edition, including URL encoding (since the box.com usernames are email addresses which include the "at sign"):

## https://mcmap.net/q/240102/-upload-files-with-ftp-using-powershell
## User comment complains can't turn off passive mode, 
## but that is exactly what we want here!
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null

# config 
$Username = "[email protected]"
$Password = "s3cr3tpAssw0rd"
$Servername = "ftp.box.com"

# This is what we need URI it to look like:
#   ftp://foo%40bar.com:[email protected]/
$baseURI = "ftp://$([System.Web.HttpUtility]::UrlEncode($Username)):$([System.Web.HttpUtility]::UrlEncode($Password))@$($Servername)"

$LocalFile = "C:\tmp\to_upload\data.csv"
$RemoteFile = "date.csv"
$ftpURI = "$($baseURI)/$($RemoteFile)"

Write-output "ftp uri: $($ftpURI)";

$webclient = New-Object -TypeName System.Net.WebClient;
$ftpURI = New-Object -TypeName System.Uri -ArgumentList $ftpURI; #"convert" it
$webclient.UploadFile($ftpURI, $LocalFile);

Write-output  "Uploaded $($LocalFile) ... "; # of course since we didn't use try/catch or other error dectection this is a bit presuming.

Also should note this example uses plain FTP, not FTPS or SFTP.

Devalue answered 23/3, 2017 at 19:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.