Copy Several Files Into One

by Karl E. Peterson

  Q. Concatenate Files  
I need to imitate the DOS copy command's ability to copy multiple source files into a single destination file such as this, where text3.txt is the combination of text1.txt and text2.txt:

Ask the VB Pro provides you with free advice on programming obstacles, techniques, and ideas. Read more answers from our crack VB pros on the Web at
. You can submit your questions, tips, or ideas on the site, or access a comprehensive database of previously answered questions.
C:\>copy text1.txt+text2.txt text3.txt

However, I get an error 76 when I try to use the FileCopy command with this sort of argument for the source. Is there a way to do this?

A. Sure there is. Here are the steps to copy a single file manually:

1. Verify that the source is a valid file.
2. Delete the destination file if it already exists.
3. Open both source and destination files for binary access.
4. Read the source into a buffer.
5. Write the buffer to the destination file.
6. Repeat steps 4 and 5, as needed.
7. Close both files.

However, the devil is in the details, and file operations involve plenty of details. Errors can occur at nearly every stage of this process, so you must be prepared to either deal with them or accept them gracefully as signs of defeat. I've prepared a class, CFileCopy, that offers methods for both single-file and concatenation copies. The class also exposes a number of useful utility routines such as IsFile, IsDirectory, ExtractName, ExtractPath, and KillFile.

The ConcatFiles method of CFileCopy accepts a destination file's name and a parameter array of source files (see Listing 1). ParamArrays are always optional, so ConcatFiles must first check how many source files were passed to it. If zero, the function returns immediately with a success code of False. If one, the copy activity is delegated to CFileCopy's CopyFile method. If more than one source file is passed, ConcatFiles gets busy.

If the destination is a directory, ConcatFiles appends the name of the first source file to form the fully qualified destination. ConcatFiles calls KillFile, a simple error-wrapper around VB's Kill statement, on the destination file if it exists. CFileCopy notifies its client of progress, so module-level variables must be initialized. ConcatFiles opens the destination file for binary write access, and enters a loop that opens each source file in turn, copies its bits to the destination, and closes the file. Finally, ConcatFiles closes the destination and reports success.

CFileCopy's public copy methods rely on the private CopyBits routine for all bit-slinging. ConcatFiles passes CopyBits the file handles for both (current) source and destination. CopyBits creates a 32K buffer for intermediate storage. Sizing the intermediate buffer is critical, because this affects final performance more than any other factor. You want the intermediate buffer to be an even multiple of disk cluster size, large enough to reduce the number of read/write operations, but small enough so as not to overly burden available RAM or inhibit responsiveness in the client app.

CopyBits loops through as many full buffers as it can before the remaining portion of the source file is smaller than the buffer, at which point the buffer is reduced so that CopyBits reads only the remaining bytes on the final iteration. As CopyBits processes each buffer, CFileCopy raises an event to advise its client on the percent complete and allow the client to cancel the operation.

Q. Check for CD-ROM Presence
How can I check to see if the user has a CD-ROM in the CD drive?

A. The failure of any number of API calls could indicate a missing disk. For example, Get DriveType tells you an empty drive is a CD-ROM, and GetDiskFreeSpace(Ex) fails on a disk that isn't present, returning error 15 ("The system cannot find the drive specified.") in Err.LastDllError.

You also can check for missing media using only two native VB functions:

Public Function MediaPresent(ByVal _
   Drive As String) As Boolean
   Dim Buffer As String
   ' Rely on errors to indicate
   ' missing disk.
   On Error Resume Next
   ' Check for current directory.
   ' Failure means drive not present.
   Buffer = CurDir(Drive)
   If Err.Number = 0 Then
      ' Try directory in root.
      ' Failure means media not 
      ' present.
      Buffer = Dir(Left$(Drive, 1) & _
      MediaPresent = (Err.Number = 0)
   End If
End Function

The CurDir function fails on any drive that isn't present, either locally or mapped through a network. However, CurDir doesn't fail on a removable drive without media present. You can use Dir to differentiate between drives with or without media. Call Dir on the drive's root directory to determine whether a present drive contains media; this call fails if the media is missing.

It's easy to turn immediately to the API to solve a given problem, but these last two examples show that native VB is often much quicker to code and provides just as robust a solution.

Q. Check File Existence on FTP Server
I want to use FTP to send a file from a client to a server. Before moving the file, I want to test for whether the file already exists. I know I have to look in only one directory and I know the name of that directory. Is there a function I can feed the server IP and the path to check to see if the file exists?

A. No, there's no single function, but you can use a handful of WinInet calls for this purpose. You first need to establish an Internet session by calling InternetOpen. Pass the handle returned from InternetOpen to InternetConnect, along with the server's host name (or its IP address in dotted-decimal format) and username/password—if needed—to establish a direct connection with the server. Finally, call FtpFindFirstFile with the fully qualified path to the file you're looking for (see Listing 2).

Pay particular attention to storing and later releasing all handles returned by WinInet functions. Notice that after each call, you test for success by looking for a valid handle. After using that handle within the If block that follows the call that obtains the handle, you call InternetCloseHandle to assure nothing's left open. If your application is doing a lot of work on the Internet, you might want to cache some of these handles, instead of making new connections for each of multiple requests. If you do, be prepared to reinitiate a connection closed by your server.

Also note that you can control whether to rely on the system's Internet cache, or to request a fresh reload as your needs dictate. In this case, checking for the exist- ence of a file, it's definitely smart to use the INTERNET_FLAG_RELOAD flag in the FtpFindFirstFile call.

Karl E. Peterson is a GIS analyst with a regional transportation planning agency and serves as a member of the Visual Basic Programmer's Journal Technical Review and Editorial Advisory Boards. Online, he's a Microsoft MVP and a section leader on several VBPJ forums. Find more of Karl's VB samples at

Original Code
  Get the code for this article here.
Updated Code