none
Truncated cFileName attribute when calling FtpFindFirstFile through VB RRS feed

  • Question

  • Dear Friends

    I am attempting to download data from an FTP server using VB; however the cFileName attribute of the WIN32_FIND_DATA structure returned by calling the FtpFindFirstFile function is returning only the final few characters of the filename. This prevents me from using the FtpGetFile function to download the file.

    The number of characters it returns is anywhere from two to eight. For instance, if the filename is FILENAME.XLSX, the cFileName attribute — after trimming nulls — might give SX or AME.XLSX

    I have already spent quite a few weeks trying to crack this, including an extensive online search and trialling using different computers but to no avail. I am using a 64-bit Windows 10 Pro operating system.

    I have copied the simplest version of the code below, with the connection details obscured for sensitivity. Would anyone be able to help me please?

    Best

    Rich

    Const MAX_PATH = 260
    '
    Public Type FILETIME
        dwLowDateTime As Long
        dwHighDateTime As Long
    End Type
    '
    Private Type WIN32_FIND_DATA
        dwFileAttributes As LongPtr
        ftCreationTime As FILETIME
        ftLastAccessTime As FILETIME
        ftLastWriteTime As FILETIME
        nFileSizeHigh As LongPtr
        nFileSizeLow As LongPtr
        dwReserved0 As LongPtr
        dwReserved1 As LongPtr
        cFileName As String * MAX_PATH
        cAlternateFileName As String * 14
    End Type
    '
    Private Declare PtrSafe Function InternetOpen Lib "WININET.DLL" Alias "InternetOpenA" _
        (ByVal sAgent As String, _
        ByVal lAccessType As LongPtr, _
        ByVal sProxyName As String, _
        ByVal sProxyBypass As String, _
        ByVal lFlags As LongPtr) As LongPtr
    '
    Private Declare PtrSafe Function InternetConnect Lib "WININET.DLL" Alias "InternetConnectA" _
        (ByVal hInternetSession As LongPtr, _
        ByVal sServerName As String, _
        ByVal nServerPort As Integer, _
        ByVal sUsername As String, _
        ByVal sPassword As String, _
        ByVal lService As LongPtr, _
        ByVal lFlags As LongPtr, _
        ByVal lContext As LongPtr) As LongPtr
    '
    Private Declare PtrSafe Function FtpSetCurrentDirectory Lib "WININET.DLL" Alias "FtpSetCurrentDirectoryA" _
        (ByVal hFtpSession As LongPtr, _
        ByVal lpszDirectory As String) As Boolean
    '
    Private Declare PtrSafe Function FtpFindFirstFile Lib "WININET.DLL" Alias "FtpFindFirstFileA" _
        (ByVal hFtpSession As LongPtr, _
        ByVal lpszSearchFile As String, _
        lpFindFileData As WIN32_FIND_DATA, _
        ByVal dwFlags As LongPtr, _
        ByVal dwContent As LongPtr) As LongPtr
    '
    Private Declare PtrSafe Function InternetCloseHandle Lib "WININET.DLL" _
        (ByVal hInet As LongPtr) As Integer
    '
    '
    Sub Test()
    '
    hostName = "ftpaddress"
    port = 21
    username = "username"
    password = "password"
    remoteDirectory = "directory/"
    remoteMatchFiles = "*.xls*"
    '
    Dim fileFind As WIN32_FIND_DATA
    '
    hOpen = InternetOpen("ftp VBA", 1, vbNullString, vbNullString, 0)
    Debug.Print hOpen
    '
    If hOpen > 0 Then hConn = InternetConnect(hOpen, hostName, port, username, password, 1, 0, 0)
    Debug.Print hConn
    '
    If hConn > 0 Then hFind = FtpFindFirstFile(hConn, remoteDirectory & remoteMatchFiles, fileFind, 0, 0)
    Debug.Print hFind
    Debug.Print fileFind.cFileName
    '
    InternetCloseHandle hConn
    InternetCloseHandle hOpen
    '
    End Sub


    Monday, August 26, 2019 3:21 AM

All replies

  • According to documentation for Windows programming, the size of FILETIME is 8 bytes and the size of WIN32_FIND_DATA is 592 bytes (in case of Unicode). You can check the sizes using LenB function.

    Try this fix:

    Private Type WIN32_FIND_DATA
        dwFileAttributes As Long
        ftCreationTime As FILETIME
        ftLastAccessTime As FILETIME
        ftLastWriteTime As FILETIME
        nFileSizeHigh As Long
        nFileSizeLow As Long
        dwReserved0 As Long
        dwReserved1 As Long
        cFileName As String * MAX_PATH
        cAlternateFileName As String * 14
    End Type

    And use FtpFindFirstFileW instead of FtpFindFirstFileA.

    Monday, August 26, 2019 6:53 AM
  • A test with ftp.intel.com on Windows 10, VS 2015

    I get the correct listing :

    readme.txt
    images <DIR>
    Pub <DIR>
    TransferringFilesWithIntel.htm

    Option Strict On
    Imports System.Runtime.InteropServices
    
    Public Class Form1
        <DllImport("Wininet.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function InternetOpen(lpszAgent As String, dwAccessType As Integer, lpszProxy As String, lpszProxyBypass As String, dwFlags As Integer) As IntPtr
        End Function
    
        Public Const INTERNET_OPEN_TYPE_PRECONFIG = 0 ' use registry configuration
        Public Const INTERNET_OPEN_TYPE_DIRECT = 1 ' direct To net
        Public Const INTERNET_OPEN_TYPE_PROXY = 3 ' via named proxy
        Public Const INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4 ' prevent Using java/script/INS
    
        Public Const PRE_CONFIG_INTERNET_ACCESS = INTERNET_OPEN_TYPE_PRECONFIG
        Public Const LOCAL_INTERNET_ACCESS = INTERNET_OPEN_TYPE_DIRECT
        Public Const CERN_PROXY_INTERNET_ACCESS = INTERNET_OPEN_TYPE_PROXY
    
    
        <DllImport("Wininet.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function InternetConnect(hInternet As IntPtr, lpszServerName As String, nServerPort As Short, lpszUserName As String, lpszPassword As String, dwServices As Integer,
                                               dwFlagss As Integer, dwContext As IntPtr) As IntPtr
        End Function
    
        Public Const INTERNET_SERVICE_FTP = 1
        Public Const INTERNET_SERVICE_GOPHER = 2
        Public Const INTERNET_SERVICE_HTTP = 3
    
        Public Const INTERNET_INVALID_PORT_NUMBER = 0           ' use the protocol-specific default
    
        Public Const INTERNET_DEFAULT_FTP_PORT = 21         ' default for FTP servers
        Public Const INTERNET_DEFAULT_GOPHER_PORT = 70         '    "     "  gopher "
        Public Const INTERNET_DEFAULT_HTTP_PORT = 80         '    "     "  HTTP   "
        Public Const INTERNET_DEFAULT_HTTPS_PORT = 443        '   "     "  HTTPS  "
        Public Const INTERNET_DEFAULT_SOCKS_PORT = 1080       ' default for SOCKS firewall servers.
    
        Public Const INTERNET_FLAG_ASYNC = &H10000000  ' this request Is asynchronous (where supported)
        Public Const INTERNET_FLAG_PASSIVE = &H8000000  ' used For FTP connections
    
        <DllImport("Wininet.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function FtpFindFirstFile(hConnect As IntPtr, lpszSearchFile As String, ByRef lpFindFileData As WIN32_FIND_DATA, dwFlags As Integer, dwContext As IntPtr) As IntPtr
        End Function
    
        Public Const ERROR_NO_MORE_FILES = 18L
    
        Public Const FILE_ATTRIBUTE_DIRECTORY = &H10
    
        <DllImport("Wininet.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function InternetFindNextFile(hFind As IntPtr, ByRef lpvFindData As WIN32_FIND_DATA) As Boolean
        End Function
    
        <DllImport("Wininet.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function InternetCloseHandle(hInternet As IntPtr) As Boolean
        End Function
    
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
        Public Structure WIN32_FIND_DATA
            Public dwFileAttributes As Integer
            Public ftCreationTime As ComTypes.FILETIME
            Public ftLastAccessTime As ComTypes.FILETIME
            Public ftLastWriteTime As ComTypes.FILETIME
            Public nFileSizeHigh As Integer
            Public nFileSizeLow As Integer
            Public dwReserved0 As Integer
            Public dwReserved1 As Integer
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
            Public cFileName As String
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)>
            Public cAlternateFileName As String
        End Structure
    
    
    ' Add a Button for the click
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim sHost As String = "ftp.intel.com"
            Dim hOpen As IntPtr = InternetOpen("FTP Test", LOCAL_INTERNET_ACCESS, Nothing, Nothing, 0)
            Dim nError As Integer = 0
            If (hOpen <> IntPtr.Zero) Then
                Dim hConnect As IntPtr = InternetConnect(hOpen, sHost, INTERNET_INVALID_PORT_NUMBER, Nothing, Nothing, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, IntPtr.Zero)
                If (hConnect <> IntPtr.Zero) Then
                    Dim wfd As WIN32_FIND_DATA = New WIN32_FIND_DATA()
                    Dim hFind As IntPtr = FtpFindFirstFile(hConnect, "*.*", wfd, 0, IntPtr.Zero)
                    If (hFind <> IntPtr.Zero) Then
                        If (wfd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY) Then
                            Console.WriteLine("{0} <DIR>", wfd.cFileName)
                        Else
                            Console.WriteLine("{0}", wfd.cFileName)
                        End If
                        Do
                            If (Not InternetFindNextFile(hFind, wfd)) Then
                                nError = Marshal.GetLastWin32Error()
                                If (nError = ERROR_NO_MORE_FILES) Then
                                    Exit Do
                                Else
                                    Console.WriteLine("InternetFindNextFile Error : {0}", nError)
                                    Return
                                End If
                            Else
                                If (wfd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY) Then
                                    Console.WriteLine("{0} <DIR>", wfd.cFileName)
                                Else
                                    Console.WriteLine("{0}", wfd.cFileName)
                                End If
                            End If
                        Loop While (True)
                        InternetCloseHandle(hFind)
                    End If
                    InternetCloseHandle(hConnect)
                Else
                    nError = Marshal.GetLastWin32Error()
                    Console.WriteLine("InternetConnect Error : {0}", nError)
                End If
                InternetCloseHandle(hOpen)
            End If
        End Sub
    End Class

    Monday, August 26, 2019 10:31 AM
  • Thanks for your suggestion — an issue with the attribute sizes does sound plausible. However I have:

    1. Replaced the LongPtr declarations with Long
    2. Used FtpFindFirstFileW instead of FtpFindFirstFileA

    And the function still returns a bogus value in the attribute cFileName. Indeed when implementing FtpFindFirstFileW the return value for the function is zero suggesting that the function is not even finding the first file let alone returning a WIN32_FIND_DATA structure.

    Wednesday, August 28, 2019 5:52 AM
  • Thanks for your response. Could I ask what you mean by correct listing? I note that you have included Visual Studio script yet I am implementing my script in Visual Basic for Applications
    Wednesday, August 28, 2019 5:53 AM
  • If possible, perform an experiment with FindFirstFile:

     

    Private Type WIN32_FIND_DATA

        dwFileAttributes As Long

        ftCreationTime As FILETIME

        ftLastAccessTime As FILETIME

        ftLastWriteTime As FILETIME

        nFileSizeHigh As Long

        nFileSizeLow As Long

        dwReserved0 As Long

        dwReserved1 As Long

        cFileName As String * MAX_PATH

        cAlternateFileName As String * 14

        Unused1 As Long

        Unused2 As Long

        Unused3 As Integer

    End Type

     

    Declare PtrSafe Function FindFirstFile Lib "kernel32" Alias "FindFirstFileA" (ByVal lpFileName As String, lpFindFileData As WIN32_FIND_DATA) As LongPtr

     

    . . .

    Dim r As LongPtr

    Dim fd As WIN32_FIND_DATA

       

    r = FindFirstFile("C:\MyFiles\*.xls?", fd)

       

    MsgBox r

    MsgBox fd.cFileName

     

    Specify an accessible local folder that contains some Excel files.

    If this work, then try the next definition of FtpFindFirstFile:

     

    Private Declare PtrSafe Function FtpFindFirstFile Lib "WININET.DLL" Alias "FtpFindFirstFileA" _

        (ByVal hFtpSession As LongPtr, _

        ByVal lpszSearchFile As String, _

        lpFindFileData As WIN32_FIND_DATA, _

        ByVal dwFlags As Long, _

        ByVal dwContent As LongPtr) As LongPtr

     

    Wednesday, August 28, 2019 6:47 AM
  • Thanks for your response. Could I ask what you mean by correct listing? I note that you have included Visual Studio script yet I am implementing my script in Visual Basic for Applications
    If I use a FTP client, I get the same list of files/directories as with the VB.NET code

    • Edited by Castorix31 Wednesday, August 28, 2019 6:48 AM
    Wednesday, August 28, 2019 6:48 AM
  • Thanks for your suggestion to test FindFirstFile first.

    I tried this and the return value is nonzero indicating that a file has been found; however the cFileName attribute comprises a string of 260 null characters.

    Wednesday, August 28, 2019 7:51 AM
  • Thanks for your suggestion. I have yet to try Visual Basic .NET but I am trying to steer clear of scripting outside of the Excel environment as we are intending to deliver a product that does not require our client to install any additional software, for instance Visual Studio.
    Wednesday, August 28, 2019 7:58 AM