0

I am struggling to try and get a simple piece of code working as a very novice programmer, here's what I want:

Recursively search a directory(for example D:\Site_Data), find all files with a given extension(for example .xrl) and delete them.

The reason I want to use the Win32API's is because I'm dealing with files buried in directorys over 260 char's deep and have searched high and low and cannot find a solution. VB.NET is what I've been learning so that's what I want to stick to.

I've managed to copy some code and adjust it to delete a "single" file using the Kernel32.dll DeleteFile function, but that only does 1 file, it can't accept wildcard's, here's what I got:

Imports System
Imports System.Runtime.InteropServices
Imports System.IO


Public Class Form1

Public Property delFile As String


<DllImport("kernel32.dll", CharSet:=CharSet.Unicode, ExactSpelling:=False, SetLastError:=True)> _
Public Shared Function DeleteFile(ByVal path As String) As Boolean
End Function

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

    delFile = "D:\Site_Data\Test.JPG"

    Try
        Dim boolResult As Boolean
        Dim delName As String = "\\?\" + delFile
        boolResult = DeleteFile(delName)
        Debug.WriteLine("Result: " & boolResult.ToString)

    Catch ex As Exception
        Throw ex
    End Try
End Sub
End Class

I understand what I'm looking for is the "FindFirstFile" function but have no idea how to write the code to implement it in VB.NET.

If anyone can help it would be greatly appreciated.

UPDATE 2, WORKING CODE:

Imports System.IO
Imports System.Runtime.InteropServices

Public Class Form1

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim longFolderName As String = "\\?\c:\users\user1\desktop\pics\"
    Dim extensionsToDelete As String = ".jpg"

    Dim filenames As List(Of String) = VeryLongFilenameHandler.GetFilenames(longFolderName)

    For Each filename As String In filenames
        If filename.ToLower.EndsWith(extensionsToDelete) Then
            VeryLongFilenameHandler.DeleteFile(longFolderName + filename)

            Debug.WriteLine("Result: " & longFolderName, filename)

        End If
    Next
End Sub
End Class
Class VeryLongFilenameHandler

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Structure WIN32_FIND_DATA
    Public dwFileAttributes As UInteger
    Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public nFileSizeHigh As UInteger
    Public nFileSizeLow As UInteger
    Public dwReserved0 As UInteger
    Public dwReserved1 As UInteger
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> Public cFileName As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)> Public cAlternateFileName As String
End Structure

<DllImport("kernel32", CharSet:=CharSet.Unicode)>
Public Shared Function FindFirstFile(lpFileName As String, ByRef lpFindFileData As WIN32_FIND_DATA) As IntPtr
End Function

<DllImport("kernel32", CharSet:=CharSet.Unicode)>
Public Shared Function FindNextFile(hFindFile As IntPtr, ByRef lpFindFileData As WIN32_FIND_DATA) As Boolean
End Function

<DllImport("kernel32.dll")>
Public Shared Function FindClose(ByVal hFindFile As IntPtr) As Boolean
End Function

<DllImport("kernel32.dll", CharSet:=CharSet.Unicode, ExactSpelling:=False, SetLastError:=True)>
Public Shared Function DeleteFile(ByVal path As String) As Boolean
End Function


Public Shared Function GetFilenames(folderName As String) As List(Of String)
    Dim filenames As New List(Of String)
    Dim findData As New WIN32_FIND_DATA
    Dim findHandle As New IntPtr
    Dim INVALID_HANDLE_VALUE As New IntPtr(-1)

    ' Add wildcard to foldername to get all files
    folderName += "*"

    findHandle = FindFirstFile(folderName, findData)
    If findHandle <> INVALID_HANDLE_VALUE Then
        Do
            If findData.cFileName <> "." AndAlso findData.cFileName <> ".." AndAlso (findData.dwFileAttributes And FileAttributes.Directory) <> FileAttributes.Directory Then
                filenames.Add(findData.cFileName)
            End If
        Loop While (FindNextFile(findHandle, findData))
        FindClose(findHandle)
    End If

    Return filenames
End Function
End Class

So the above works for deleting all files inside the folder with extension .JPG, but does not do it recursively.

UPDATE 3:

Again thanks for the help theduck, greatly appreciated, I think I'm almost there, I've managed to separate the 2 functions into 2 buttons, 1 button can list all files with .jpg, the other button can list all directories, now it's a case of combining them, so I managed to do the below with the second button but the Debug output isn't showing the files it is "suppose" to delete, this is what I've got so far, getting a little confused as to how it's supposed to work:

Private Sub Button2_Click(sender As Object, e As EventArgs) Handles     Button2.Click
    Dim longFolderName As String = "\\?\c:\users\Administrator\Desktop\TestDir\"

    Dim foldernames As List(Of String) = VeryLongFilenameHandler.GetFoldernames(longFolderName)

    For Each foldername As String In foldernames
        Dim longFolderName1 As String = (longFolderName + foldername)
        Dim extensionsToDelete As String = ".jpg"

        Dim filenames As List(Of String) = VeryLongFilenameHandler.GetFilenames(longFolderName1)
        MsgBox(longFolderName1)

        For Each filename As String In filenames
            If filename.ToLower.EndsWith(extensionsToDelete) Then
                'VeryLongFilenameHandler.DeleteFile(longFolderName + filename)

                Debug.WriteLine("Deleted: " & longFolderName1 + filename)

            End If
        Next
    Next

End Sub 

The MsgBox pops up with both directories as it should, the "VeryLongFilenameHandler.GetFoldernames" is the function adjustment you provided below for getting back just the directories. I basically copied the Public Shared Function and made the necessary adjustments.

UPDATE: Working Ok so it work's now, thanks to theduck, that last edit you made on the sample code is perfect, to link it to a button, I simply moved the code from inside the Module, into a button. Of course I'm not blind to the obvious, you basically wrote the entire code block for me, to which I am very thankful, I haven't seen help like that in a long time. As I'm only a novice programmer the way I learn quicker is by finding working code, and breaking down how that works, I grasp that much quicker than reading MSDN articles.

So once again thank you, here's my final code(consists of one Form and one Button) Imports System.IO Imports System.Runtime.InteropServices Public Class Form1

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles         Button1.Click
    Dim longFolderName As String = "\\?\c:\users\administrator\Desktop\TestDir\"
    Dim extensionToDelete As String = ".jpg"

    RecursiveDelete(longFolderName, extensionToDelete)

End Sub

Sub RecursiveDelete(path As String, extensionToDelete As String)

    If Not path.EndsWith("\") Then path += "\"

    ' Handle all folders below this one
    Dim folders As List(Of String) = VeryLongFilenameHandler.GetFolders(path)
    For Each folder As String In folders
        RecursiveDelete(path + folder, extensionToDelete)
    Next

    ' Delete any applicable files
    Dim filenames As List(Of String) = VeryLongFilenameHandler.GetFilenames(path)

    For Each filename As String In filenames
        If filename.ToLower.EndsWith(extensionToDelete) Then
            VeryLongFilenameHandler.DeleteFile(path + filename)
        End If
    Next

End Sub



End Class

Class VeryLongFilenameHandler

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Structure WIN32_FIND_DATA
    Public dwFileAttributes As UInteger
    Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public nFileSizeHigh As UInteger
    Public nFileSizeLow As UInteger
    Public dwReserved0 As UInteger
    Public dwReserved1 As UInteger
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> Public cFileName As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)> Public cAlternateFileName As String
End Structure

<DllImport("kernel32", CharSet:=CharSet.Unicode)>
Public Shared Function FindFirstFile(lpFileName As String, ByRef lpFindFileData As WIN32_FIND_DATA) As IntPtr
End Function

<DllImport("kernel32", CharSet:=CharSet.Unicode)>
Public Shared Function FindNextFile(hFindFile As IntPtr, ByRef lpFindFileData As WIN32_FIND_DATA) As Boolean
End Function

<DllImport("kernel32.dll")>
Public Shared Function FindClose(ByVal hFindFile As IntPtr) As Boolean
End Function

<DllImport("kernel32.dll", CharSet:=CharSet.Unicode, ExactSpelling:=False, SetLastError:=True)>
Public Shared Function DeleteFile(ByVal path As String) As Boolean
End Function

Public Shared Function GetFilenames(folderName As String) As List(Of String)
    Return GetNames(folderName, False)
End Function

Public Shared Function GetFolders(folderName As String) As List(Of String)
    Return GetNames(folderName, True)
End Function

Private Shared Function GetNames(folderName As String, getDirectories As Boolean) As List(Of String)
    Dim names As New List(Of String)
    Dim findData As New WIN32_FIND_DATA
    Dim findHandle As New IntPtr
    Dim INVALID_HANDLE_VALUE As New IntPtr(-1)

    If Not folderName.EndsWith("\") Then folderName += "\"
    ' Add wildcard to foldername to get all files
    folderName += "*"

    findHandle = FindFirstFile(folderName, findData)
    If findHandle <> INVALID_HANDLE_VALUE Then
        Do

            If findData.cFileName <> "." AndAlso findData.cFileName <> ".." Then
                Dim isDirectory As Boolean = (findData.dwFileAttributes And FileAttributes.Directory) = FileAttributes.Directory
                If (getDirectories AndAlso isDirectory) Or (Not getDirectories AndAlso Not isDirectory) Then
                    names.Add(findData.cFileName)
                End If
            End If

        Loop While (FindNextFile(findHandle, findData))
        FindClose(findHandle)
    End If

    Return names
End Function

End Class

Kishan Kaplan
  • 13
  • 1
  • 7
  • Make sure your foldername has a '\' at the end of it. – theduck Feb 05 '15 at 11:33
  • Thank you for that, got it to work now, it deletes the files inside the folder, but it doesn't do it recursively ?, I've pasted my complete updated code above – Kishan Kaplan Feb 05 '15 at 11:58
  • It would be relatively easy to add recursion. The Win32 FindFirstFile and FindNextFile actually return both files and directories. If you created a new function in the helper class which looked exactly like the current GetFilenames but changed the If statement to add only directories and not files (effectively the opposite of its current function) then you could get a list of directories. Then write a function to get all folders in a directory and then delete the files and have it recursively called for each folder that it finds. – theduck Feb 05 '15 at 15:16
  • Hmm, doesn't sound easy, I don't know how to change the If statement to apply to Directories only, have tried a few variations of the code but it's not getting me anywhere. – Kishan Kaplan Feb 05 '15 at 17:06
  • If findData.cFileName <> "." AndAlso findData.cFileName <> ".." AndAlso (findData.dwFileAttributes And FileAttributes.Directory) = FileAttributes.Directory Then ... this will only bring you back directories. – theduck Feb 05 '15 at 17:10
  • OK - getting close! What you need to do now is create a sub that takes a folder path as a parameter and that then 1) deletes all the files (with the correct extension) from that folder and 2) *calls itself* with the path of all the folders in the target directory. This will then recurse down the directory structure deleting all the applicable files. Your Button2_Click sub will then just need to call your new sub with the top level path. – theduck Feb 05 '15 at 20:11
  • http://stackoverflow.com/questions/8176993/recursive-function-examples-in-vb-net - this might be useful to explain how recursive functions work and one of the examples is similar to what you want to achieve (except not using Win32 calls) – theduck Feb 05 '15 at 20:16
  • Thanks for that, have taken a look at the example and can't seem to apply what is being done there to what I am doing, I can create a sub but can't get it to take a folder path as a parameter, the folder path I would need to pass to it would need to be the output of the "filenames" list which is generated in Button 2. Also I can't seem to understand why what I have already isn't working, in effect, the "filenames" list which is generated has the directories in it, I am hen simply running a "For Each" on that list which isn't running, completely don't understand that ? – Kishan Kaplan Feb 05 '15 at 20:57
  • Have updated answer below with some example code. – theduck Feb 05 '15 at 21:45

2 Answers2

0

if you use a loop you can make the program continue until a certain condition is met (e.g. all files with .example extension are deleted) research into while loops they should be able to do what you want the program to do.

Loop Structures: https://msdn.microsoft.com/en-GB/library/ezk76t25.aspx

0

Something along these lines may help:

Imports System.IO
Imports System.Runtime.InteropServices

Module Module1

Sub Main()
    Dim longFolderName As String = "\\?\c:\temp\"
    Dim extensionToDelete As String = ".xrl"

    RecursiveDelete(longFolderName, extensionToDelete)
End Sub

Sub RecursiveDelete(path As String, extensionToDelete As String)

    If Not path.EndsWith("\") Then path += "\"

    ' Handle all folders below this one
    Dim folders As List(Of String) = VeryLongFilenameHandler.GetFolders(path)
    For Each folder As String In folders
        RecursiveDelete(path + folder, extensionToDelete)
    Next

    ' Delete any applicable files
    Dim filenames As List(Of String) = VeryLongFilenameHandler.GetFilenames(path)

    For Each filename As String In filenames
        If filename.ToLower.EndsWith(extensionToDelete) Then
            VeryLongFilenameHandler.DeleteFile(path + filename)
        End If
    Next

End Sub
End Module

Class VeryLongFilenameHandler

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Structure WIN32_FIND_DATA
    Public dwFileAttributes As UInteger
    Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME
    Public nFileSizeHigh As UInteger
    Public nFileSizeLow As UInteger
    Public dwReserved0 As UInteger
    Public dwReserved1 As UInteger
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> Public cFileName As String
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)> Public cAlternateFileName As String
End Structure

<DllImport("kernel32", CharSet:=CharSet.Unicode)>
Public Shared Function FindFirstFile(lpFileName As String, ByRef lpFindFileData As WIN32_FIND_DATA) As IntPtr
End Function

<DllImport("kernel32", CharSet:=CharSet.Unicode)>
Public Shared Function FindNextFile(hFindFile As IntPtr, ByRef lpFindFileData As WIN32_FIND_DATA) As Boolean
End Function

<DllImport("kernel32.dll")>
Public Shared Function FindClose(ByVal hFindFile As IntPtr) As Boolean
End Function

<DllImport("kernel32.dll", CharSet:=CharSet.Unicode, ExactSpelling:=False, SetLastError:=True)>
Public Shared Function DeleteFile(ByVal path As String) As Boolean
End Function

Public Shared Function GetFilenames(folderName As String) As List(Of String)
    Return GetNames(folderName, False)
End Function

Public Shared Function GetFolders(folderName As String) As List(Of String)
    Return GetNames(folderName, True)
End Function

Private Shared Function GetNames(folderName As String, getDirectories As Boolean) As List(Of String)
    Dim names As New List(Of String)
    Dim findData As New WIN32_FIND_DATA
    Dim findHandle As New IntPtr
    Dim INVALID_HANDLE_VALUE As New IntPtr(-1)

    If Not folderName.EndsWith("\") Then folderName += "\"
    ' Add wildcard to foldername to get all files
    folderName += "*"

    findHandle = FindFirstFile(folderName, findData)
    If findHandle <> INVALID_HANDLE_VALUE Then
        Do

            If findData.cFileName <> "." AndAlso findData.cFileName <> ".." Then
                Dim isDirectory As Boolean = (findData.dwFileAttributes And FileAttributes.Directory) = FileAttributes.Directory
                If (getDirectories AndAlso isDirectory) Or (Not getDirectories AndAlso Not isDirectory) Then
                    names.Add(findData.cFileName)
                End If
            End If

        Loop While (FindNextFile(findHandle, findData))
        FindClose(findHandle)
    End If

    Return names
End Function

End Class

The class VeryLongFilenameHandler has a function GetFilenames that will return you a list of files in the folder specified using Win32 APIs. You can then iterate over those filenames, check the extensions and delete if appropriate (again using Win32 calls).

theduck
  • 2,589
  • 13
  • 17
  • 23