locked
FtpWebRequest - Access to IBM AS/400 FTP Server - 501 Character (/) not allowed in object name.

    Question

  • Hello everybody

    I'm trying to communicate with an AS/400 FTP Server. As a first try I just execute the WebRequestMethods.Ftp.ListDirectory method, which translates to the NLST FTP command. Doing the same accessing a Microsoft FTP Server or a UNIX FTP Server works fin.

    This is the code to get the listing:



    Dim Uri As System.Uri = New System.Uri(listUrl)
    Dim listRequest As FtpWebRequest = CType(WebRequest.Create(Uri), FtpWebRequest)
    listRequest.Credentials = New System.Net.NetworkCredential(User, Password)listRequest.Method = WebRequestMethods.Ftp.ListDirectory
    listRequest.KeepAlive =
    False
    listRequest.UseBinary =
    False
    listRequest.UsePassive =
    False
    Dim listResponse As FtpWebResponse = CType(listRequest.GetResponse(), FtpWebResponse)
    Reader = New StreamReader(listResponse.GetResponseStream())
    Return reader.ReadToEnd()

     


    This is a short trace where the error is occurring (see highlighted section):

    System.Net Information: 0 : [5116] FtpWebRequest#8707876::.ctor(ftp://192.168.133.2/mak)
    System.Net Information: 0 : [5116] FtpWebRequest#8707876::GetResponse(Method=NLST.)
    System.Net Information: 0 : [5116] Associating FtpWebRequest#8707876 with FtpControlStream#50927418
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Received response [220-QTCP at 192.168.133.2. 220 Connection will close if idle more than 15 minutes.]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Sending command [USER TESTUSER]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Received response [331 Enter password.]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Sending command [PASS ********]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Received response [230 TESTUSER logged on.]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Sending command [OPTS utf8 on]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Received response [501 OPTS unsuccessful; specified subcommand not recognized.]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Sending command [PWD]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Received response [257 "QGPL" is current library.]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Sending command [CWD QGPL/]
    System.Net Information: 0 : [5116] FtpControlStream#50927418 - Received response [501 Character (/) not allowed in object name.]
    System.Net Information: 0 : [5116] FtpWebRequest#8707876::(Releasing FTP connection#50927418.)
    System.Net Error: 0 : [5116] Exception in the FtpWebRequest#8707876::GetResponse - The remote server returned an error: (501) Syntax error in parameters or arguments.
    System.Net Error: 0 : [5116] at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
    at System.Net.FtpWebRequest.RequestCallback(Object obj)
    at System.Net.CommandStream.Abort(Exception e)
    at System.Net.FtpWebRequest.FinishRequestStage(RequestStage stage)
    at System.Net.FtpWebRequest.GetResponse()

    The AS/400 FTP Server dose not allow a "/" for changing directories. The server would accept an alternative filenameformat (quote site namefmt 1) which would accept slashes.
     
    Is there a write method that I could override to filter the CWD command and change it?
    Or is there a way to insert the "site namefmt 1" command?

    I hope someone can help me with this problem!
    Tuesday, November 29, 2005 5:06 PM

Answers

  • Currently FtoWebRequest does not support quote and I cannot think of a way you'll be able to overide the method without exposing our code 
    Wednesday, November 30, 2005 7:49 AM
    Moderator

All replies

  • I will look into this.
    Is this server reacheable over the internet?

    Tuesday, November 29, 2005 5:07 PM
    Moderator
  • Sorry. The server is not reachable over the internet.

    Tuesday, November 29, 2005 5:12 PM
  • OK we will look into this
    Tuesday, November 29, 2005 5:24 PM
    Moderator
  • Currently FtoWebRequest does not support quote and I cannot think of a way you'll be able to overide the method without exposing our code 
    Wednesday, November 30, 2005 7:49 AM
    Moderator
  • Has anything changed regarding this behavior since the last post?  (11-30-2005)

    Has Microsoft come up with any workarounds, so that we can connect to AS/400, VMS & MVS FTP servers using .Net?

    Friday, September 01, 2006 7:46 PM
  • Did anyone ever answer this?  I am trying to do the same thing!!!!
    Thursday, September 28, 2006 5:44 PM
  • Katie,

    I came to a complete deadend with Microsoft on this.  Simply not supported and no workarounds.

    I'm currently converting my code over to using WeOnlyDo - wodFtpDLX.NET component.

    It's relatively inexpensive and was the only 3rd party component that I could find to handle the SSH protocol in addition to all the variations of SSL.

    I am currently working with the developers of this software, so that their component correctly parses directory stuctures for AS/400, MVS & VMS. 

    Since I already had the parsing logic in my program, I kept it in as a precautionary fail-over.  The main thing I was after anyway, was for the component to handle making the connections via the correct protocol.

    Here's a link if you want to check into this software to see if it meets your needs.

    http://www.weonlydo.com/index.asp?showform=FtpDLX.NET

    Hopefully this will save you some time and frustration.

    Good Luck, Dave

    Thursday, September 28, 2006 6:08 PM
  • Unfortunately your scenario is still not supported for FtpWebRequest. We will work on getting this fixed in a future release

    Mariya

    Friday, September 29, 2006 12:13 AM
    Moderator
  • As you can see from Mariya’s message (28.09.2006) FtpWebRequest dose still not support the special directory and file structure of AS/400.

    As a workaround I start FTP.EXE in a separate process. It is not the nicest way to do the work but it works. Of course it dose not support any secure transport but as I work over VPN it is not necessary.

    This is an example how to retrieve files from the FTP-Server:


    Public Class FTPExample

     

        'These variables hold the result from Output and Error steams

        'from the process

        Private Shared FTPOutput As System.Text.StringBuilder = Nothing

        Private Shared FTPError As System.Text.StringBuilder = Nothing

     

        Public Shared Function FTPGet(ByVal RemotePath As String, _

               ByVal LocalPath As String, _

               ByVal FileNames() As String) As Boolean

     

            'FTP Process initalization

            Dim FTPProcess As New Process()

            FTPProcess.StartInfo.FileName = "ftp.exe"

     

            'UseShellExecute to false to enable redirection

            'of INPUT/OUTUT/ERROR

            FTPProcess.StartInfo.UseShellExecute = False

     

            'Hide window

            FTPProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

     

            'Redirection of OUTPUT- and ERROR-Streams

            FTPProcess.StartInfo.StandardOutputEncoding = _

                                 System.Text.Encoding.GetEncoding(850)

            FTPProcess.StartInfo.StandardErrorEncoding =  _

                                 System.Text.Encoding.GetEncoding(850)

            FTPProcess.StartInfo.RedirectStandardOutput = True

            FTPProcess.StartInfo.RedirectStandardError = True

     

            'Creation of OUTPUT- and ERROR-StringBuilder

            FTPOutput = New System.Text.StringBuilder()

            FTPError = New System.Text.StringBuilder()

     

            'Eventhandler for OUTPUT- and ERROR-Streams

            AddHandler FTPProcess.OutputDataReceived, _

                       AddressOf FTPOutputHandler

            AddHandler FTPProcess.ErrorDataReceived, _

                       AddressOf FTPErrorHandler

     

            'Redirection of INPUT

            FTPProcess.StartInfo.RedirectStandardInput = True

     

            'This process argument forces the FTP.EXE to execute first

            'the file "FTPLogin".

            'Be aware of the current directory when you use a

            'relative file path or use absolute file path.

            'in our case it contains in the first line an open statement

            'in the second the username and in the third the password

            'example:

            'open 192.168.1.1

            'joe

            '12345

            FTPProcess.StartInfo.Arguments = "-s:FTPLogin"

     

            'Starting the process

            FTPProcess.Start()

     

            'Creating reference to Input-Writer

            Dim FTPStreamWriter As System.IO.StreamWriter = _

                                   FTPProcess.StandardInput

     

            'Start reading asynchronously

            FTPProcess.BeginOutputReadLine()

            FTPProcess.BeginErrorReadLine()

     

            'Sending commands to FTP-Process

            FTPStreamWriter.WriteLine("DEBUG")

            FTPStreamWriter.WriteLine("PROMPT")

            FTPStreamWriter.WriteLine("QUOTE SITE LISTFMT 0")

            FTPStreamWriter.WriteLine("QUOTE SITE NAMEFMT 0")

            FTPStreamWriter.WriteLine("LCD " + LocalPath)

            FTPStreamWriter.WriteLine("CD " + RemotePath)

            FTPStreamWriter.WriteLine("ASCII")

            If FileNames Is Nothing Then

                'retrieve all files from the remote path if nothing is defined

                FTPStreamWriter.WriteLine("MGET *.*")

            Else

                'retrieve the selected files from the remote path

                For Each FileName As String In FileNames

                    FTPStreamWriter.WriteLine("GET " + _

                       System.IO.Path.GetFileName(FileName))

                Next

            End If

            FTPStreamWriter.WriteLine("QUIT")

     

            'Waiting for process termination

            FTPProcess.WaitForExit()

     

            'Close Input-Writer

            FTPStreamWriter.Close()

     

            'process OUTPUT/ERROR results

            If FTPError.Length = 0 Then

                Debug.WriteLine("FTPProcess Error: <empty>")

            Else

                Debug.WriteLine("FTPProcess Error: " + FTPError.ToString)

            End If

            If FTPOutput.Length = 0 Then

                Debug.WriteLine("FTPProcess Output: <empty>")

            Else

                Debug.WriteLine("FTPProcess Output: " + FTPOutput.ToString)

            End If

     

            'Removing Eventhandlers for Output and Error-streams

            RemoveHandler FTPProcess.OutputDataReceived, _

                          AddressOf FTPOutputHandler

            RemoveHandler FTPProcess.ErrorDataReceived, _

                          AddressOf FTPErrorHandler

     

            'Closing process object

            FTPProcess.Close()

            FTPProcess.Dispose()

            FTPProcess = Nothing

     

            'doing some error handling

            Return (FTPError.Length = 0)

        End Function

     

        Private Shared Sub FTPOutputHandler(ByVal sendingProcess As Object, _

                           ByVal outLine As DataReceivedEventArgs)

            'Collecting Output

            If Not String.IsNullOrEmpty(outLine.Data) Then

                FTPOutput.Append(outLine.Data + Environment.NewLine)

            End If

        End Sub

     

        Private Shared Sub FTPErrorHandler(ByVal sendingProcess As Object, _

                           ByVal outLine As DataReceivedEventArgs)

            'Collecting Error

            If Not String.IsNullOrEmpty(outLine.Data) Then

                FTPError.Append(outLine.Data + Environment.NewLine)

            End If

        End Sub

    End Class


     

    I hope this helps you a little bit.

     

    Friday, September 29, 2006 8:47 AM
  • Thanks for the update... I've been trying to do the same thing and running into this error.  Thanks for the helpful information. 

    FYI, we've use the Xceed .NET FTP component to access the AS400 programmatically.  Its fully featured, but its not cheap.

    http://www.xceedsoft.com/products/FtpNet/index.aspx

     

     

    Monday, October 02, 2006 9:26 PM
  • I have the same problem with accessing MVS.  Took me forever to try and fail.  In the end I found this post and lo-and-behold!  It doesn't work!!! 
    Monday, October 23, 2006 9:16 PM
  • Forgive me if this is a stupid question, but is wininet no longer an option after VB6?
    Tuesday, October 31, 2006 4:02 AM
  • Great Work-Around, Thanks!
    Wednesday, March 07, 2007 4:18 PM
  • I am dumbfounded that Microsoft let this slide! Entire classes of FTP servers can't be accessed all because of a path separator!! Microsoft, an easy fix to this would be to expose a path separator property on the FtpWebRequest class, that way people whose server doesn't support a / as the path separator can specify what it is. That, or don't insist on appending a slash to the end of the path.

    When a simple program like ftp that's been built into the OS for years can do something that complex .NET can't do it's a shame, especially when the only problem is a slash character... The quality of Microsoft products is going down, erm, getting even worse.

    Tuesday, March 20, 2007 8:02 PM
  • This is really helpful. However, I noticed that it does not truely work in interactive mode. The system does not process my commands as I write them via the stream writer (even if I set AutoFlush). It does nothing until the WaitForExit. And then I get my response back all at once.

    Is there any way to make this code more responsive?

    Wednesday, March 21, 2007 12:06 AM
  • Another option in terms of FTP, and the option I ultimately went with, is Indy. This is a huge free library, around 2.5 MB, of networking protocols. It can handle FTP, HTTP, IMAP, etc. This is an example of the code I used to do my FTP.

     

    Code Snippet

    Indy.Sockets.FTP ftp = new Indy.Sockets.FTP();

    try {

    ftp.Host = "eagle";

    ftp.Username = someusername;

    ftp.Password = somepassword;

    ftp.Connect();

    if(!String.IsNullOrEmpty(directory))

    ftp.ChangeDir(directory);

    ftp.Put(decryptedPathname, filename, false);

    }

    catch(Exception x) {

    this.MessageBox(String.Format(Messages.TransferError, someusername, x.Message));

    okay = false;

    }

    finally {

    ftp.Quit();

    }

     

    Tuesday, March 27, 2007 1:49 PM
  • Hello,

     

    Try configuring the iSeries FTP server to use *PATH in name format and *HOMEDIR as default directoy.

    You will need SECOF rights to access FTP configuration:

       CHGFTPA NAMEFMT(*PATH) CURDIR(*HOMEDIR)

     

    Hope this helps you a little bit.

     

     

    Friday, October 26, 2007 2:19 PM
  • Unfortunately, I cannot ask my customers to change their FTP parameters.  But thanks for the workaround.  I hope it helps the others here.  And I hope MS addresses the issue.

    Friday, October 26, 2007 6:32 PM
  • Hi,

    I've the same problem as posted here, but the strange thing that the code succedded in putting file via ftp to the AS400 from the local machine (Windows XP, IIS 5), and it failed when we put the site on the production web server (Windows Server 2003, IIS 6).

    I've tried the AS400 configuration on the AS400 test machine, and it worked successfully , but the problem that we can't change the ftp directory of the AS400 production machine as this will lead to changing all the running applications

    My code is as shown below:
        public static void FTPPutFile(string strFileName, string strFTPPath)
        {
            try
            {
                string strFilePath = ConfigurationManager.AppSettings["XMLPathOUT"] + strFileName;
                string URI = strFTPPath + strFileName;
                FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(URI);
      ftp.Credentials = new NetworkCredential(ConfigurationManager.AppSettings["AS400UserName"],   ConfigurationManager.AppSettings["AS400Password"]);
                ftp.KeepAlive = false;
                ftp.UseBinary = true;
                ftp.Proxy = null;

                //Write
                ftp.Method = WebRequestMethods.Ftp.UploadFile;
                Stream stream = ftp.GetRequestStream();
                FileStream fs = new FileStream(strFilePath, FileMode.Open);
                byte[] buffer = new byte[(int)fs.Length];
                fs.Read(buffer, 0, buffer.Length);
                stream.Write(buffer, 0, buffer.Length);

                fs.Close();
                stream.Close();
            }
            catch (Exception ex)
            {
                clsHelperMethods.HandleError(ex);
                throw (ex);
            }

        }
    Tuesday, December 25, 2007 2:05 PM
  • I was having the same problem trying to connect to an IBM MVS server...after reading these posts I thought it was hopeless...but I finally got something to work for both uploading and downloading files:

    ftp://myFTPsite/%2F%27path.filename%27 - I hope this works for others.

    Thursday, January 10, 2008 11:02 PM
  •  

    Thanks FTPOdyssey, you really saved my day yesterday, I also had to connect to a MVS server via FTPWebRequest and after almost giving up, I finally found your post here and your solution worked! Thanks!!

    Tuesday, April 15, 2008 8:57 AM
  • Hello FTPOdyssey,

     

    I am getting this error 501 and wanted to know what you have tried out. The link that you have given doesnt seem to work.

     

    thanks

    VJ

     

    Tuesday, May 06, 2008 5:19 PM
  • Here is how I used that statement:

     

    Dim request As FtpWebRequest = WebRequest.Create("ftp://" & strFTPsite & "/%2F'" & strHostFile & "'"

     

    Replace the strFTPsite with the FTP site you are trying to connect to, and the strHostFile with the path and filename like this: mypath.myfile.

     

     

    • Proposed as answer by sfibich Wednesday, July 15, 2009 3:15 PM
    Tuesday, May 06, 2008 6:17 PM
  • Thanks for the reply. Unfortunately didnt work in my case. I used a batch file instead. Thanks once again

    VJ

     

     

    Saturday, May 17, 2008 11:50 AM
  • We have the same problem. We solve it using an open source library called edtFTPnet. You can show it in http://www.enterprisedt.com/products/edtftpnet/overview.html

    I write you a little part of our source code. It works great with AS/400 FTP.

    string ftpUserID = "anyUser";  
    string ftpPassword = "xxxxxxxxxxxx";  
    string ftpServerIP = "as400.host";  
     
            private void Form1_Load(object sender, EventArgs e)  
            {  
                int i;  
                FTPClient ftp = new FTPClient();  
                ftp.RemoteHost = ftpServerIP;  
                ftp.Connect();  
                ftp.User(ftpUserID);  
                ftp.Password(ftpPassword);  
                ftp.ChDir("PERSONAL_DIR");  
                string[] files=ftp.Dir();  
                for(i=0;i<=files.Length-1;i++)  
                {  
                    MessageBox.Show(files[i]);  
     
                }  

    The traces are:
    --------------------- AS400 .NET STANDARD TRACE -----------------------
    220-QTCP at COMPANY.
    220 Connection will close if idle more than 5 minutes.
    USER anyuser
    331 Enter password.
    PASS xxxxxxxxxx
    230 ANYUSER logged on.
    OPTS utf8 on
    501 OPTS unsuccessful; specified subcommand not recognized.
    PWD
    257 "QGPL" is current library.
    CWD QGPL/
    501 Character (/) not allowed in object name.
    221 QUIT subcommand received.

    --------------------- AS400 edtFTPnet TRACE ----------------------
    220-QTCP at COMPANY.
    220 Connection will close if idle more than 5 minutes.
    USER anyuser
    331 Enter password.
    PASS xxxxxxxxxx
    230 ANYUSER logged on.
    PWD
    257 "QGPL" is current library.
    CWD PERSONAL_DIR
    250 "PERSONAL_DIR" is current library.
    PASV
    227 Entering Passive Mode (171,18,1,8,282,33).
    NLST
    125 List started.
    250 List completed.

    Bye ;).
    Thursday, June 12, 2008 5:13 PM
  • Thank you FTPOdyssey, works like a charm, no open source garbage necessary.
    • Edited by Chris H E Thursday, July 10, 2008 3:26 PM moded
    Thursday, July 10, 2008 3:25 PM
  • Limason showed a correct solution for this using the native FTP client via the Process class.  All legal FTP commands are supported. 
    There is one other solution which no body showed, that's writing a native socket FTP client class especially for the AS/400 and or Mainframe.  It wouldn't take that long to do and could be a tad more robust as well... 

    I also believe on the AS/400 that the system admin's can configure FTP to be in Namefmt 1 as the default as well.
    Javaman
    Monday, September 08, 2008 10:11 PM
  • Has anyone ever found a solution for sending QUOTE or SITE commands through the FTPWebRequest object?

    Wednesday, October 29, 2008 9:33 PM
  • DaveWendt, did you ever get WeOnlyDo to work with AS/400?
    Thursday, November 20, 2008 4:37 PM
  • This solution works for me, if you set the URL to ftp://serverName/%2F it will drop you into the Root, if you are not allow to access the root you must know what exact static path you are able to access.  (At least in my case thats how it is working)

    Thanks for the tip!
    Wednesday, July 15, 2009 3:27 PM
  • Thanks Marcos,

    Your solution solved this problem for me! My code is actually simpler with edtFTPnet than it was with FTPWebRequest
    Thursday, August 20, 2009 6:00 AM
  • Hi,

     

    I am having the same problem with AS400. 

    When I am opening the above link it showing some error , can you please  post the code one more time ASAP.

     

    please its urgent...

     

    thanks in advance

     

     

     

    Saturday, July 17, 2010 8:58 AM
  • I was having the same problem connecting to an AS/400.  I tried different ftp web requests, and they all raised a WebException.  A few things that helped me out - I followed sfibich's suggestion and set my uri to ftp://10.1.1.1/%2f, but that still raised a WebException.  In the exception handler I did this:

        Catch exWeb As WebException
          clsShared.PostMsg("WebException error in " & "FtpPrintWorkingDirectory: " & exWeb.ToString, 1)
          bReturn = False
    
          Try
            Dim ftpResp As FtpWebResponse
            ftpResp = DirectCast(exWeb.Response, FtpWebResponse)
            clsShared.PostMsg("WebException error in " & "FtpPrintWorkingDirectory with StatusCode [" & ftpResp.StatusCode & "], StatusDescription [" & ftpResp.StatusDescription & "]", 1)
    
          Catch ex2 As Exception
            ' do nothing
          End Try
    
    
    

    The ftpWebResponse object in the exception allowed me to see the error returned from the ftp server.  In my case the StatusDescription was "501 Character (/) not allowed in object name.<CR><LF>".  Now I was finally getting the same error that started this thread.  So I started playing with the URI class, and using the URIBuilder.  I ended up with this method for building the uri in my ftpPut function:

          ub = New UriBuilder
          ub.Scheme = Uri.UriSchemeFtp
          ub.Host = 10.1.1.1/%2f
          ub.UserName = UserName
          ub.Password = Password
          ub.Path = FtpPathName & "/" & RemoteFile
    
          clsShared.PostMsg("Uri is [" & ub.Uri.ToString & "]", 9)
    
    
    

    I was trying to avoid using forward slashes as much as possible.  There was a lot of trial and error here.  I kept printing out the uri string so I could see what worked and what didn't.  The correct uri for the UploadFile ftp method was ftp://USER:PASS@10.1.1.1///folder/FileName.tmp.  I would have never guessed that three forwad slashes was the solution.

    I am also setting the Credentials property of the ftpRequest with the same user and pass.  I know this is redundant, but it's working so I'm not going to mess with it.

     

    Friday, October 22, 2010 3:20 PM
  • Fantastic - thanks
    Wednesday, April 27, 2011 5:06 PM
  • Hi All,
    I've been searching and trying quite a lot to get ftpwebrequest working with AS/400...
    I found that you just need to specify the right path (syntax) to get access to both, IFS and library system:
     
    IFS:
    ftp://10.5.1.20// [folder/file.ext]
     
    Library System:
    ftp://10.5.1.20//qsys.lib [/somelib.lib/somefile.file[/somemember.mbr]]
     
    Just try the path in your browser and you'll see...
     
    Uri ur = new Uri("ftp://10.5.1.20//<<folder>>");
    // ---  or --- Uri ur = new Uri("ftp://10.5.1.20//qsys.lib/<<library>>.lib");
    FtpWebRequest wr = (FtpWebRequest)FtpWebRequest.Create(ur);
    wr.Credentials = new NetworkCredential("<<user>>","<<password>>");
    wr.UsePassive = true;
    wr.UseBinary = true;
    wr.KeepAlive = true;
    
    try
    {
    	  wr.Method = WebRequestMethods.Ftp.ListDirectory;
    	  WebResponse rs = wr.GetResponse();
    	  Stream st = rs.GetResponseStream();
    	  StreamReader sr = new StreamReader(st);
    	  textBox1.Text = sr.ReadToEnd();
    	  sr.Close();
    	  st.Close();
    	  rs.Close();
    }
    catch (Exception se)
    {
    	MessageBox.Show(se.Message);
    }
    
    Monday, August 08, 2011 1:15 PM
  • Like Robert, I found the double-forward-slash is needed if your path contains a bunch of directories.

    For me, I needed to FTP a file to the IFS directory - way in there, so my path was something like:

    //usr//local//wu-ftpd//ftp//XAMA//reportingreq/test.xml

    My code looks something like this, given that:

    mServer = ftp://server
    mUser and mPass are strings passed into the method
    local = text.xml     --- the local file
    remote = //usr//local//wu-ftpd//ftp//XAMA//reportingreq/test.xml
    (remote contains the full path including the filename)

                try
                {
                    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(mServer + remote);  //uri
                    request.UseBinary = mUseBinary; //true
                    request.EnableSsl = mUseSsl;  //false
                    request.UsePassive = true;
                    request.KeepAlive = true;
                    request.Credentials = new NetworkCredential(mUser, mPass);
                    request.Method = WebRequestMethods.Ftp.UploadFile;

                    //local is the file on my local directory, the one I am "putting" - it's a string.
                    StreamReader sourceStream = new StreamReader(local, Encoding.Default);
                    byte[] fileContents = Encoding.Default.GetBytes(sourceStream.ReadToEnd());
                    sourceStream.Close();
                    request.ContentLength = fileContents.Length;

                    Stream requestStream = request.GetRequestStream();         //Connect...
                    requestStream.Write(fileContents, 0, fileContents.Length); //Here is where we send the data/file
                    requestStream.Close();

                    FtpWebResponse response = (FtpWebResponse)request.GetResponse();

                    _log.InfoFormat("Upload File Complete, status {0}", response.StatusDescription);

                    response.Close();

                }
                catch (Exception e)
                {
                    _log.Error(e.Message);
                    bRet = false;
                }


    • Edited by JasHolt Thursday, September 15, 2011 10:41 PM Made sure paths were identical in example
    Thursday, September 15, 2011 10:38 PM
  • Thanks man! This helped. To reciprocate, here is my object oriented class to try encapsulating the basic functionality. I'm sure I will tweak it more, but this exposes get, put, change directory, and list directory.

    Public Class MyFTP
        Const TIMEOUT_SECONDS As Integer = 15
        Private mDomain As String
        Private mWorkingFolder As String
        Private mUserID As String
        Private mPassword As String
        Private ReadOnly Property tempFileName() As String
            Get
                Return mWorkingFolder & "temp.txt"
            End Get
        End Property
        Private ReadOnly Property loginFileName() As String
            Get
                Return mWorkingFolder & "login.txt"
            End Get
        End Property
        Public Sub New(ByVal Domain As String, ByVal WorkingFolder As String, _
                       ByVal UserID As String, ByVal Password As String)
            mUserID = UserID
            mWorkingFolder = WorkingFolder
            If Not mWorkingFolder.EndsWith("\") Then
                mWorkingFolder = mWorkingFolder & "\"
            End If
            mPassword = Password
            mDomain = Domain
            Call DeleteTempFiles()
            Call InitializeConnection()
        End Sub
        Private Sub DeleteTempFiles()
            If IO.File.Exists(tempFileName) Then
                Call IO.File.Delete(tempFileName)
            End If
            If IO.File.Exists(loginFileName) Then
                Call IO.File.Delete(loginFileName)
            End If
        End Sub
        'These variables hold the result from Output and Error steams
        'from the process
        Private Shared mFTPOutput As System.Text.StringBuilder = Nothing
        Public ReadOnly Property FTPOutput() As String
            Get
                Return mFTPOutput.ToString
            End Get
        End Property
        Private Shared mFTPError As System.Text.StringBuilder = Nothing
        Public ReadOnly Property FTPError() As String
            Get
                Return mFTPError.ToString
            End Get
        End Property
        Private mFtpStreamWriter As System.IO.StreamWriter
        Private mConnection As Process = Nothing
        Private Sub SendWithWait(ByVal input As String)
            Dim outputLength As Integer = mFTPOutput.Length
            mFtpStreamWriter.WriteLine(input)
            mFtpStreamWriter.Flush()
            'uses the confirmation message (sent by having DEBUG on) to know that the transfer is complete
            Dim waitStart As Date = Now
            Do
                Call Threading.Thread.Sleep(100)
            Loop Until mFTPOutput.Length > outputLength _
            Or (Now.Subtract(waitStart)).TotalSeconds >= TIMEOUT_SECONDS
            If (Now.Subtract(waitStart)).TotalSeconds >= TIMEOUT_SECONDS Then
                Throw New Exception("Command timed out:" & vbCrLf & input)
            End If
        End Sub
        Private Sub Send(ByVal input As String)
            SendWithWait(input)
        End Sub
        Private Shared Sub FTPOutputHandler(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
            'Collecting Output
            If Not String.IsNullOrEmpty(outLine.Data) Then
                mFTPOutput.Append(outLine.Data + Environment.NewLine)
            End If
        End Sub
        Private Shared Sub FTPErrorHandler(ByVal sendingProcess As Object, _
                           ByVal outLine As DataReceivedEventArgs)
            'Collecting Error
            If Not String.IsNullOrEmpty(outLine.Data) Then
                mFTPError.Append(outLine.Data + Environment.NewLine)
            End If
        End Sub

        Private Sub InitializeConnection()
            Dim thisFtpProcess As New Process()
            thisFtpProcess.StartInfo.FileName = "ftp.exe"

            'UseShellExecute to false to enable redirection
            'of INPUT/OUTUT/ERROR
            thisFtpProcess.StartInfo.UseShellExecute = False

            'Hide window
            thisFtpProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

            'Redirection of OUTPUT- and ERROR-Streams
            thisFtpProcess.StartInfo.StandardOutputEncoding = System.Text.Encoding.GetEncoding(850)
            thisFtpProcess.StartInfo.StandardErrorEncoding = System.Text.Encoding.GetEncoding(850)
            thisFtpProcess.StartInfo.RedirectStandardOutput = True
            thisFtpProcess.StartInfo.RedirectStandardError = True

            'Creation of OUTPUT- and ERROR-StringBuilder
            mFTPOutput = New System.Text.StringBuilder()
            mFTPError = New System.Text.StringBuilder()

            'Eventhandler for OUTPUT- and ERROR-Streams
            AddHandler thisFtpProcess.OutputDataReceived, AddressOf FTPOutputHandler
            AddHandler thisFtpProcess.ErrorDataReceived, AddressOf FTPErrorHandler

            'Redirection of INPUT
            thisFtpProcess.StartInfo.RedirectStandardInput = True

            'create temporary login file
            Dim loginText As New System.Text.StringBuilder
            Call loginText.AppendLine("open " & mDomain)
            Call loginText.AppendLine(mUserID)
            Call loginText.AppendLine(mPassword)
            Call loginText.AppendLine("CD ..")
            Call loginText.AppendLine("DEBUG")
            Call IO.File.WriteAllText(loginFileName, loginText.ToString)
            thisFtpProcess.StartInfo.Arguments = "-s:" & loginFileName

            'Starting the process
            Call thisFtpProcess.Start()

            'Creating reference to Input-Writer
            mFtpStreamWriter = thisFtpProcess.StandardInput()

            'Start reading asynchronously
            Call thisFtpProcess.BeginOutputReadLine()
            Call thisFtpProcess.BeginErrorReadLine()

            mConnection = thisFtpProcess

            'start connection to server
            'Send("OPEN " & mDomain)
            'send user & password
            'Send(mUserID)
            'Send(mPassword)
            Send("ASCII")
            'set local path to the "working folder"
            Send("LCD " + mWorkingFolder)
        End Sub
        Public Sub CloseConnection()
            If mConnection Is Nothing Then
                Throw New Exception("FTP connection not open")
            End If
            Send("QUIT")

            'Waiting for process termination
            mConnection.WaitForExit()

            'Close Input-Writer
            mFtpStreamWriter.Close()

            'process OUTPUT/ERROR results
            If FTPError.Length = 0 Then
                Debug.WriteLine("FTPProcess Error: <empty>")
            Else
                Debug.WriteLine("FTPProcess Error: " + FTPError.ToString)
            End If
            If FTPOutput.Length = 0 Then
                Debug.WriteLine("FTPProcess Output: <empty>")
            Else
                Debug.WriteLine("FTPProcess Output: " + FTPOutput.ToString)
            End If

            'Removing Eventhandlers for Output and Error-streams
            RemoveHandler mConnection.OutputDataReceived, AddressOf FTPOutputHandler
            RemoveHandler mConnection.ErrorDataReceived, AddressOf FTPErrorHandler

            'Closing process object
            mConnection.Close()
            mConnection.Dispose()
            mConnection = Nothing

            Call DeleteTempFiles()

            'final error handling
            If FTPError.Length <> 0 Then
                Throw New Exception(FTPError.ToString)
            End If
        End Sub
        Public Sub SetRemotePath(ByVal remotePath As String)
            Send("CD " + remotePath)
        End Sub

        Public Function GetFile(ByVal remoteFileName As String) As String
            'send FTP command to get the file
            Call SendWithWait("GET """ & remoteFileName & """ """ & tempFileName & """")

            'read the file text into memory
            Dim contents As String = IO.File.ReadAllText(tempFileName)

            'clean up - delete the temporary file that was just created
            Call IO.File.Delete(tempFileName)

            Return contents
        End Function
        Public Sub PutFile(ByVal contents As String, ByVal remoteFileName As String)
            'write the file text from memory
            Call IO.File.WriteAllText(tempFileName, contents)

            'send FTP command to put the file
            Call SendWithWait("PUT """ & tempFileName & """ """ & remoteFileName & """")

            'clean up - delete the temporary file that was just created
            Call IO.File.Delete(tempFileName)
        End Sub
        Public Sub ChangeDirectory(ByVal newRemoteDirectory As String)
            'send FTP command to get the file
            Call SendWithWait("CD " & newRemoteDirectory)
        End Sub
        Private Enum DirectoryStructure
            PDS = 1
            DataSet = 2
        End Enum
        ''' <summary> Note that this does not always work with complex filters (more than one asterisk) </summary>
        Public Function GetDirectoryContents(Optional ByVal fileFilter As String = "") As String()
            Dim outputLength As Integer = mFTPOutput.Length
            'send FTP command to get the directory list
            Call SendWithWait("DIR " & fileFilter)
            Call SendWithWait("NOOP")

            Dim waitStart As Date = Now
            Dim newOutput As String
            Dim outputLines As String()
            Do
                newOutput = mFTPOutput.ToString.Substring(outputLength)
                outputLines = newOutput.Replace(vbCrLf, vbLf).Split(CChar(vbLf))
                'NOOP gets back the "Invalid command." response
                '(and the last line is always empty string, hence the -2)
                If (Now.Subtract(waitStart)).TotalSeconds >= TIMEOUT_SECONDS Then
                    Throw New Exception("Unable to interpret output of directory listing:" & vbCrLf & newOutput)
                End If
            Loop Until outputLines(outputLines.Length - 2) = "Invalid command."

            Dim headerLine As String
            Dim listingStructure As DirectoryStructure
            headerLine = outputLines(2)
            If headerLine.StartsWith(" Name") Then
                'this is a PDS. Output will look something like this:
                '---> PORT 10,5,16,140,16,122
                '---> LIST
                ' Name     VV.MM   Created       Changed      Size  Init   Mod   Id
                '#CONCLRA  01.00 2001/03/19 2001/03/19 11:36     1     1     0 #CONCLR
                '#KC99A01  01.01 2009/05/14 2009/05/14 09:09  3523  3835     0 KRCRAN 
                'PAMA103                                                              
                'PAMA106                                                              
                'PAMA604   01.01 2011/06/12 2011/06/12 10:52   232   178     0 RXSREE 
                'PAPA99A   01.85 2006/11/03 2011/09/26 09:28  1554     0     0 RXSREE 
                'PAPA99AA  01.50 2001/03/26 2004/01/15 07:58  1334  1221     0 MWCOPE 
                'PAPA99C   01.38 2001/04/09 2006/02/08 13:51   647   581     0 PARITT 
                '(last line blank)
                listingStructure = DirectoryStructure.PDS
            ElseIf headerLine.StartsWith("Volume") Then
                'this is a list of datasets. Output will look something like this:
                '---> PORT 10,5,16,140,16,122
                '---> LIST
                'Volume Unit    Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname
                'TSOPK1 3390   2011/07/29  1    8  FB      80  4560  PO  BMCCAT.JCL
                'TSOPK1 3390   2011/09/29  2    4  VB    4092  4096  PS  BMCCAT.SQL
                'TSOPK1 3390   2011/09/30 16  240  FB      80  4560  PO  JCL
                'TSOPK1 3390   2011/05/12  1   15  FB      80  4560  PO  JCLE
                'TSOPK1 3390   2011/09/30  1    1  FB      80 27920  PS  LOGON.CLIST
                '(last line blank)
                listingStructure = DirectoryStructure.DataSet
            ElseIf headerLine = "No members found." Then
                Dim emptyStringArray As String()
                ReDim emptyStringArray(-1)
                Return emptyStringArray
            Else
                Throw New Exception("Unable to interpret output of directory listing:" & vbCrLf & newOutput)
            End If

            Const headerRows As Integer = 3
            Const trailerRows As Integer = 2
            Dim fileNames As String()
            ReDim fileNames(outputLines.Length - headerRows - trailerRows - 1)

            For i As Integer = 0 To fileNames.Length - 1
                Dim thisLine As String = outputLines(i + headerRows)
                If listingStructure = DirectoryStructure.PDS Then
                    thisLine = thisLine.Substring(0, 8).Trim
                ElseIf listingStructure = DirectoryStructure.DataSet Then
                    thisLine = thisLine.Substring(56).Trim
                End If
                fileNames(i) = thisLine
            Next

            Return fileNames
        End Function
    End Class

    Friday, September 30, 2011 7:26 PM
  • Since this thread has helped us to resolve our problems uploading files with the FTPWebRequest to AS/400, here is our solution:

    We first changed our url to the following format:

    ftp://[ip]/%2F/QSYS.LIB[/some other.LIBs]/test.xml

    %2F sends you to the root as explained by other posts here. From there we navigate through some libraries to the destination. This worked well until we got to the uploading part of the FTP as you can see in this trace log file

    System.Net Information: 0 : [3664] FtpWebRequest#18796293::(Releasing FTP connection#34948909.)
    System.Net Verbose: 0 : [3664] WebRequest::Create(ftp://10.1.1.10///QSYS.LIB/TESTLIB.LIB/test.xml)
    System.Net Information: 0 : [3664] FtpWebRequest#46104728::.ctor(ftp://10.1.1.10///QSYS.LIB/TESTLIB.LIB/test.xml)
    System.Net Verbose: 0 : [3664] Exiting WebRequest::Create() -> FtpWebRequest#46104728
    System.Net Verbose: 0 : [3664] FtpWebRequest#46104728::GetRequestStream()
    System.Net Information: 0 : [3664] FtpWebRequest#46104728::GetRequestStream(Method=STOR.)
    System.Net Information: 0 : [3664] Associating FtpWebRequest#46104728 with FtpControlStream#12289376
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [220-QTCP at AS400.
    220 Connection will close if idle more than 5 minutes.]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [USER TESTUSER]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [331 Enter password.]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [PASS ********]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [230 TESTUSER logged on.]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [OPTS utf8 on]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [501 OPTS unsuccessful; specified subcommand not recognized.]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [PWD]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [257 "QGPL" is current library.]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [CWD //QSYS.LIB/TESTLIB.LIB/]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [250-NAMEFMT set to 1.
    250 "/QSYS.LIB/TESTLIB.LIB" is current library.]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [TYPE I]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [200 Representation type is binary IMAGE.]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [PASV]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [227 Entering Passive Mode (10,1,1,10,102,233).]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Sending command [STOR test.xml]
    System.Net Information: 0 : [3664] FtpControlStream#12289376 - Received response [501 Unknown extension in database file name.]

    System.Net Information: 0 : [3664] FtpWebRequest#46104728::(Releasing FTP connection#12289376.)
    System.Net Error: 0 : [3664] Exception in the FtpWebRequest#46104728::GetRequestStream - The remote server returned an error: (501) Syntax error in parameters or arguments.

    So we changed test.xml to test.FILE instead and it worked! Instead of the indicated error message we got

    System.Net Information: 0 : [3664] FtpControlStream#55915408 - Sending command [STOR test.FILE]
    System.Net Information: 0 : [3664] FtpControlStream#55915408 - Received response [150 Sending file to member TEST in file TEST in library TESTLIB.]

    To keep the extension .xml, our final URI now looks like this:

    ftp://10.1.1.10/%2F/QSYS.LIB/TESTLIB.LIB/test.xml.FILE

    where test.xml is the filename and extension of your choice. Trace file result:

    System.Net Information: 0 : [3664] FtpControlStream#32854180 - Sending command [STOR test.xml.FILE]
    System.Net Information: 0 : [3664] FtpControlStream#32854180 - Received response [150 Sending file to member TEST.XML in file TEST.XML in library TESTLIB.]

    Note: It seems this syntax with .FILE is only needed if you are uploading to libraries. when uploading to an IFS folder the .FILE should (must?) be left out. That leaves The following URI:

    ftp://10.1.1.10/%2F/some/long/path/test.xml

    Thanks everyone for your valuable input! Hope this helps someone else.


    Friday, October 21, 2011 4:03 PM