Kill Failed? Let User Try Again

Kill Failed? Let User Try Again

My Access applications regularly need to overwrite existing files.  A common example is exporting data to a text file, such as a .csv.  Sometimes the user chooses to overwrite the existing file, and sometimes the program is hard-coded to save to a specific filename.

Oftentimes, the user wants to immediately re-export the data.  This fails, though, if the target file is open in a program--such as Excel--that locks the file.  The only way to overwrite the file is to close it first.

This situation comes up often enough that I wrote a function to give the user the chance to close an open file instead of raising an error.

Limitations

I should point out that this particular function is of the "quick and dirty" variety.  For most of my use cases, it is more than sufficient.  But it has some real flaws, most notably that it swallows ALL errors, not just the "file is locked" types.  

Also, since it's little more than a wrapper around the Kill statement, it's subject to all the same problems and cautions.  Namely, you need to be careful about accidentally deleting multiple files.  If you (or a user!) includes a wildcard character in the file name (* or ?), you could delete a lot more files than you intended to.  There are also Unicode considerations, which I address at the bottom of the article.

This function as an improvement over calling the Kill statement directly.  It is not the most robust possible solution for deleting files.  Code accordingly.

The Code

#If VBA7 Then
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If


'https://nolongerset.com/the-subtle-dangers-of-dir/
Private Function FileExists(FullPathOfFile As String)
    Dim HasWildcards As Boolean
    HasWildcards = (InStr(FullPathOfFile, "*") > 0) Or (InStr(FullPathOfFile, "?") > 0)
    
    If HasWildcards Then
        FileExists = (Len(Dir(FullPathOfFile)) > 0)
    Else
        Dim oFSO As New Scripting.FileSystemObject
        FileExists = oFSO.FileExists(FullPathOfFile)
    End If
End Function

'---------------------------------------------------------------------------------------
' Procedure : DeleteFile
' Purpose   : Delete a file that may already be open
' Notes     - Returns True if FName does not exist
'           - Returns False if FName exists and could not be deleted
'           - Does not raise or throw errors
'---------------------------------------------------------------------------------------
'
Function DeleteFile(FName As String, _
                    Optional DelayInSeconds As Long = 0, _
                    Optional Silent As Boolean = False) As Boolean
    Dim StartTime As Date, Complete As Boolean
    On Error Resume Next
    StartTime = Now()
    Do Until Complete
        Err.Clear
        Kill FName
        If Not FileExists(FName) Then
            DeleteFile = True
            Complete = True
            Exit Function
        End If
        If Err.Number <> 0 Then
            If (Now() - StartTime) * 86400 > DelayInSeconds Then
                If Not Silent Then
                    If MsgBox("Unable to delete file:" & vbCrLf & vbCrLf & _
                              FName & vbCrLf & vbCrLf & _
                              "Ensure the file is closed and you have the permissions to delete it.", _
                              vbRetryCancel, "File Delete Failed") = vbCancel Then
                        DeleteFile = False
                        Complete = True
                        Exit Function
                    End If
                Else
                    Complete = True
                End If
            Else
                Sleep 1000 ' wait for 1 second before trying again
            End If
        End If
    Loop
End Function

Caveat Scriptor

When I posted my article on Twitter yesterday, @ExcelAnalytics replied to point out that Dir and "other legacy MS-DOS functions (Filelen, filedatetime, Open_As, Attrib, etc) do not recognise Unicode file names, which FSO does."

In light of this, you should be aware that the code above may have problems handling Unicode file names.  I don't know exactly what form those problems may take (does the Kill statement work with Unicode file names? I don't know).

I went searching for a definitive answer. I started going down the Unicode rabbit hole and had to stop before my night spiraled completely out of control.  I intend to revisit this situation at some point in the future.

Defeat of the Black Knight from "Monty Python and the Holy Grail"

All original code samples by Mike Wolfe are licensed under CC BY 4.0