How to guarantee a file is copied across a busy network using VB6

Hi Experts

I have a general question which has been asked before but one which is still not answered quite fully IMHO...

I have an application written in VB6 that periodically picks up files from a local share and copies it across to one or more network shares, e.g. U:\ or T:\ etc from where it gets picked up by another VB6 application that I wrote.  The application runs unattended 24x7.

99.99% of the time this works great and file collisions are avoided by the application checking for file date and time stamps that a file must have spent some time in a folder before it gets picked up.  There are no issues here with files being written by one program whilst being opened by another program.

Now, the problet that I am getting at very rare occasions is that when the network gets busy or over the weekends when there are backup jobs happening the file sometimes just does not get transferred across to the shared mapped drives and no errors are trapped, basically the file fails to be transferred but program does not error.  It appears that files disappear into the Twilight Zone...!

Now what I want to know is that can I have a process whereby:

1.  I can retry the copying process 3 times, say
2.  If it fails then send an automated email to the administrator
3.  Copy the failed file locally in some folder for sending later

Alternatively, can I check the share drive's accessability prior to trying to copy the file across and if all OK then copy it, but how can I know that the file did get copied across without corruption or intereference from network, i.e. is there a method or process or mechanism that I can deploy that will tell me for sure that the file did get copied across OK and that all is OK?

Basically it is mission critical for the files to be copied across as pucker files, and if by whatever reason they fail to do so someone should know about it.

Sounds like an old familiar problem but can any one please point me in the right direction?  Any code samples would be great but not required, just tell me a reliable process please.


: - )
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

You will need to implement some type of hashing for example you can create a MD5 hash of a file or even chunks of the buffer and then later check the copied file to see if the hash value is the same as the original. If the values don't match then it's a good indication that there was corruption. You can also use WNetGetConnection() to check if the mapped drive is connected.

Option Explicit

Private Const NO_ERROR = 0

Private Declare Function WNetGetConnectionW Lib "mpr.dll" ( _
  ByVal lpLocalName As Long, _
  ByVal lpRemoteName As Long, _
  ByRef lpnLength As Long) As Long

Public Function NetGetConnection(ByVal drive As String) As Boolean
  Dim Buff(4096)  As Byte
  Dim cchBuff     As Long
  Dim dwResult    As Long
  Dim fOk         As Boolean

  fOk = False
  cchBuff = 4096
  dwResult = WNetGetConnectionW(StrPtr(drive), _
    VarPtr(Buff(0)), _
  If dwResult = NO_ERROR Then
     fOk = True
  End If
  NetGetConnection = fOk
End Function

Private Sub Form_Load()

  If NetGetConnection("y:") Then
    Debug.Print "Connection OK"
  End If
End Sub

Open in new window


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
KarenAnalyst programmerCommented:
Well... the obvious and easy thing to do is to check if the file is there once you have copied it across. You could also check its size to see it matches what you expect. Of course this does not check that it copied without any corruption... for that you would need to do something like the hashing that egl1044 has suggested.

Dim fso As New Scripting.FileSystemObject
Dim f As Scripting.File

' This will raise an error if the file does not exist.
Set f = fso.GetFile(whatever)
If f.Size = whatever Then
    ' All good.
End If

infinetpointAuthor Commented:
Hi Experts

Both of your suggestions are very good and thank you for that!

Can I just probe a little deeper and ask if there is anything like MSMQ equivalent in VB6 that we can implement, i.e. if network is down or busy then we will know from WNetGetConnection() call that we should try later so we wait on a timer to periodically check the network connection and as soon as the network share is up again we can then do what snowberry suggested, i.e. copy the file and check if the file got transferred OK by using some sort of a checksum routine.


The Ultimate Tool Kit for Technolgy Solution Provi

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more!

It appears there is some problems using WNetGetConnection() contrary to what MSDN says... I wrote an alternative example that you could use for retry attempts.
That type of implementation in VB6 would require alot of time and resources from what I can see in the documentation. I don't think it's worth the effort for something this simple if you already using shared network drives.
Query the mapped drive see if you have access and continue to copy the files... You should be aware depending on which type of mapped resource you use that if the computer goes to sleep you may not have access to the drive which can cause problems. This may not be the case if your using dhcp server for network share.
How exactly do you perform the copy using built in VB functions, script, or Windows API?

Option Explicit

Private Const INVALID_HANDLE_VALUE = (-1)
Private Const CREATE_ALWAYS = 2
Private Const FILE_FLAG_DELETE_ON_CLOSE = &H4000000

Private Declare Function CreateFileW Lib "kernel32" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

Public Function IsReady(ByVal path As String) As Boolean
  Dim hFile As Long
  path = path & "\net.tmp"
  hFile = CreateFileW(StrPtr("\\?\" & path), 0, 0, 0, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, 0)
    IsReady = False
    IsReady = True
  End If
  If hFile <> 0 Then CloseHandle hFile
End Function

Private Sub Form_Load()

  If IsReady("Y:") Then
    MsgBox "Ready"
    MsgBox "Not ready try again later."
  End If

End Sub

Open in new window

infinetpointAuthor Commented:
I use the following code to copy:

Public Function CopyFile(strSource As String, strDestination As String) As Boolean
1       Const cintBufferSize    As Integer = 4096
        Dim strErrorDescription As String
        Dim lngErrorNumber      As Long
        Dim lngErrorLine        As Long
        Dim strBuffer           As String * cintBufferSize
        Dim strTempBuffer       As String
        Dim intSourceFile       As Integer
        Dim intDestinationFile  As Integer
        Dim lngCounter          As Long
        Dim dtFileDate          As Date
        On Error GoTo CopyFile_Err
        'Push on to the error stack
2       Push App.Title & ".MVBFileDisk.CopyFile"

        ' Comments  : Copies a file
        ' Parameters: strSource - Source file
        '             strDestination - Destination file
        ' Returns   : True if successful, False otherwise
        ' Source    : infinetpoint
3       dtFileDate = MAPIFileAndPaths.GetFileTime(strSource, ftt_Modified, True)
4       intSourceFile = FreeFile

5       Open strSource For Binary Access Read As #intSourceFile
6       intDestinationFile = FreeFile

7       Open strDestination For Binary As #intDestinationFile
        For lngCounter = 1 To LOF(intSourceFile) \ cintBufferSize
8           Get #intSourceFile, , strBuffer
9           Put #intDestinationFile, , strBuffer
10      Next lngCounter

11      lngCounter = LOF(intSourceFile) Mod cintBufferSize
12      If lngCounter > 0 Then
13          Get #intSourceFile, , strBuffer
14          strTempBuffer = Left$(strBuffer, lngCounter)
15          Put #intDestinationFile, , strTempBuffer
        End If

16      Close #intSourceFile
17      Close #intDestinationFile

18      MAPIFileAndPaths.SetFileTime strDestination, dtFileDate, ftt_Modified

19      CopyFile = True
20      Pop
        Exit Function

21      Close #intSourceFile
22      Close #intDestinationFile

23      lngErrorNumber = Err.Number
24      lngErrorLine = Erl
25      strErrorDescription = Err.Description

        Select Case lngErrorNumber
        Case Else
26          LogError lngErrorNumber, Left$(strErrorDescription, 255), App.Title & ".MVBFileDisk.CopyFile", lngErrorLine, gintErrorLog
        End Select
        'Pop off the error stack
27      Pop
    End Function

Open in new window

You could use this one instead... This takes advantage of the cache manager for better performance over networks and less smb's. This would also give you detailed information about what might have occured when you experience this behavior by using the debug information... Let me know if this increases the copy performance.

Option Explicit

' The buffer size to use. (default: 64kb)
Private Const BUF_SIZE = 65536

Private Const HEAP_ZERO_MEMORY = &H8&
Private Const INVALID_HANDLE_VALUE As Long = (-1)
Private Const GENERIC_READ As Long = &H80000000
Private Const GENERIC_WRITE As Long = &H40000000
Private Const OPEN_EXISTING As Long = &H3&
Private Const CREATE_ALWAYS As Long = &H2&

Private Declare Function GetProcessHeap Lib "kernel32.dll" () As Long
Private Declare Function HeapAlloc Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function HeapFree Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long
Private Declare Function CreateFileW Lib "kernel32" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, ByVal lpBuffer As Long, ByVal nNumberOfBytesToWrite As Long, ByRef lpNumberOfBytesWritten As Long, ByVal lpOverlapped As Long) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, ByVal lpBuffer As Long, ByVal nNumberOfBytesToRead As Long, ByRef lpNumberOfBytesRead As Long, ByVal lpOverlapped As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

Public Sub NetworkCopyFile(ByVal szSource As String, ByVal szDestination As String)
  Dim Buffer        As Long 'raw buffer
  Dim hFileSrc      As Long
  Dim hFileDst      As Long
  Dim dwRead        As Long
  Dim dwWritten     As Long
  ' Tip: Specify GENERIC_READ Or GENERIC_WRITE helps the cache manager use less smb's
  '       over a network.
  hFileSrc = CreateFileW(StrPtr("\\?\" & szSource), GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0)
    Debug.Print "CreateFile:" & Err.LastDllError
    Exit Sub '_leave
  End If
  hFileDst = CreateFileW(StrPtr("\\?\" & szDestination), GENERIC_READ Or GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0)
    Debug.Print "CreateFile:" & Err.LastDllError
    CloseHandle hFileSrc
    Exit Sub '_leave
  End If
  ' allocate buffer
  Buffer = HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, BUF_SIZE)
    If ReadFile(hFileSrc, Buffer, BUF_SIZE, dwRead, 0) Then
      If dwRead = 0 Then
        Exit Do
      End If
      If WriteFile(hFileDst, Buffer, dwRead, dwWritten, 0) = 0 Then
        Debug.Print "WriteFile:" & Err.LastDllError
      End If
      Debug.Print "ReadFile:" & Err.LastDllError
      Exit Do
    End If
  Loop Until dwRead = 0
  '// cleanup
  CloseHandle hFileSrc
  CloseHandle hFileDst
  '// free memory
  If Buffer <> 0 Then
    HeapFree GetProcessHeap, 0, Buffer
  End If
End Sub
Private Sub Command1_Click()

  NetworkCopyFile "c:\windows\system32\calc.exe", "y:\calc.exe"
End Sub

Open in new window

infinetpointAuthor Commented:
I'll try that and let you know.

Thanks for following it up and providing comprehensive and detailed replies.  This will surely help in solving my problem and I really appreciate it!

;- )

It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic Classic

From novice to tech pro — start learning today.