Deleting all files except few - file-io

So I have to write a script to delete all files except n newest (most recently created). I'm running my script with two arguments: folder directory and number of files that shouldn't be removed.
Here is my script, but it is actually removing random files. How can I make it to leave n newest files?
How I run script: delete C:\users\Adam\Desktop\Test 3
My script:
Dim address
Dim n
Set fso = CreateObject("Scripting.FileSystemObject")
If (Wscript.Arguments.Count <> 2) Then
MsgBox("Wrong number of paramets")
Else
address = Wscript.Arguments(0)
n = Wscript.Arguments(1)
MsgBox( address & " " & n)
Set objFolder = fso.GetFolder(address)
For Each objFile in objFolder.files
If n <> 0 Then
n = n - 1
else
objFile.Delete True
End If
Next
End if
Code with sort:
Function SortFiles(files)
ReDim sorted(files.Count - 1)
Dim file, i, j
i = 0
For Each file in files
Set sorted(i) = file
i = i + 1
Next
For i = 0 to files.Count - 2
For j = i + 1 to files.Count - 1
If sorted(i).DateLastModified < sorted(j).DateLastModified Then
Dim tmp
Set tmp = sorted(i)
Set sorted(i) = sorted(j)
Set sorted(j) = tmp
End If
Next
Next
SortFiles = sorted
End Function
If (Wscript.Arguments.Count <> 2) Then
MsgBox("Wrong number of paramets")
else
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")
Dim files
address = Wscript.Arguments(0)
n = Wscript.Arguments(1)
Set files = fso.GetFolder(address).Files
Dim file
For Each file in SortFiles(files)
If n <> 0 Then
n = n - 1
else
file.Delete True
End If
Next
end if

The following should do the job:
Dim address
Dim n, no_of_files
Set fso = CreateObject("Scripting.FileSystemObject")
If (Wscript.Arguments.Count <> 2) Then
MsgBox("Wrong number of paramets")
Else
address = Wscript.Arguments(0)
n = Wscript.Arguments(1)
MsgBox( address & " " & n)
Set objFol = fso.GetFolder(path)
no_of_files = n
While no_of_files>0
latestDate = 0
Set objFiles = objFol.Files
'Loop to get the creation date of the newest File
For Each file In objFiles
tempDate = file.DateCreated
If CDate(tempDate)>CDate(latestDate) Then
latestDate = tempDate
End If
Next
'Loop to delete the newest file using the date fetched in the last loop
For Each file In objFiles
If file.DateCreated = latestDate Then
file.Delete True
End If
Next
no_of_files = no_of_files-1
Wend
End if
Set fso = Nothing

Related

Code to include longer file name where the file name is a number

I am trying to edit code that someone else wrote. I have done NO VBA and very little coding in general.
The original code is written for a 5 digit number and we now have files that are six digits. I have tried to copy the code but change it to 6 digit numbers below the current code above Next objFile at the end. This has not worked.
The main issue here is I didn't write the original code and I don't understand the logic. I have tried just changing all of the 5's to 6's and the 99999 to 999999. I have tried copying from Folder = "" down, changing them to 6 digits and pasting below Next objFile. This didn't work either.
Sub CopyPics()
Dim objFSO As Object
Dim objFolder As Object
Dim objFile As Object
Dim varDirectory As Variant
Dim objSubFolder As Object
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(Application.ActiveWorkbook.Path)
Dim Dest As String
Dest = "R:\Field Assurance\FA PHOTOS AND INFORMATION\"
'Loop through each file in this folder
For Each objFile In objFolder.Files
Folder = "" 'Empty old folder name
MainFolder = "" 'Empty old folder name
For i = 1 To Len(objFile.Name)
Test = Mid(objFile.Name, i, 5)
If Test >= 10000 And Test <= 99999 Then 'For files: Find any five numbers in a row and assume it to be the file number.
Folder = "NC-" & Mid(objFile.Name, i, 5) 'If found, create new folder.
i = Len(objFile.Name) 'In other words, take the first 5 numbers, then get out.
End If
Next
For Each objSubFolder In objFolder.subfolders 'Find the main folder.
If Right(Folder, 5) >= Mid(objSubFolder.Name, 4, 5) And Right(Folder, 5) <= Mid(objSubFolder.Name, 18, 5) Then 'If my file number is within the main folder bounds...
MainFolder = objSubFolder.Name & "\" 'Use that folder.
End If
Next objSubFolder
If Len(Folder) = 8 And Len(MainFolder) = 23 Then 'If real folders are identified...
On Error Resume Next
If Dir(Dest & MainFolder & Folder) = "" Then 'Check to see if the directory/folder does not exist...
objFSO.CreateFolder (Dest & MainFolder & Folder) 'If not, make one.
End If
'Rename that file's directory to be the new one - aka cut and paste file into new folder.
Name Application.ActiveWorkbook.Path & "\" & objFile.Name As Dest & MainFolder & Folder & "\" & objFile.Name
End If
Next objFile
ActiveWorkbook.Close
End Sub
This is a bit more complex than your original code but I think it's more robust...
Lightly tested.
Option Explicit
Sub CopyPics()
'use constants for fixed values
Const DEST As String = "R:\Field Assurance\FA PHOTOS AND INFORMATION\"
Dim objFSO As Object, srcFolder As Object, objFile As Object
Dim objSubFolder As Object, destFolder As Object, fNum, folderName, picFolderName
Dim FileWasMoved As Boolean, sMsg
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set srcFolder = objFSO.GetFolder(Application.ActiveWorkbook.Path) 'ThisWorkbook.Path ?
Set destFolder = objFSO.GetFolder(DEST) 'parent destination folder
'Loop through each file in this folder
For Each objFile In srcFolder.Files
FileWasMoved = False 'reset "moved" flag
fNum = ExtractNumber(objFile.Name) 'get the file number
If Len(fNum) > 0 Then 'any number found?
folderName = "NC-" & fNum
For Each objSubFolder In destFolder.subfolders 'Find the subfolder.
If IsTheCorrectFolder(objSubFolder.Name, fNum) Then
picFolderName = objSubFolder.Path & "\" & folderName
If Not objFSO.folderexists(picFolderName) Then
objFSO.CreateFolder picFolderName
End If
'move the file
Name objFile.Path As picFolderName & "\" & objFile.Name
FileWasMoved = True 'flag file as moved
Exit For
End If
Next objSubFolder
End If 'filename contains a number
'if file was not moved then add it to the list....
If Not FileWasMoved Then sMsg = sMsg & vbLf & objFile.Name
Next objFile
'warn user if some files were not moved
If Len(sMsg) > 0 Then
MsgBox "Some files were not moved:" & vbLf & sMsg, vbExclamation
End If
End Sub
'Return true/false depending on whether this is the correct
' folder to hold the specified filenumber
Function IsTheCorrectFolder(folderName, fileNumber) As Boolean
Dim arr, num1, num2, rv As Boolean
rv = False 'default return value
arr = Split(folderName, "thru") 'split folder name on "thru"
If UBound(arr) = 1 Then 'should have two parts
'get the numbers from each part and compare against the file number
num1 = ExtractNumber(arr(0))
num2 = ExtractNumber(arr(1))
If Len(num1) > 0 And Len(num2) > 0 Then
fileNumber = CLng(fileNumber) 'convenrt to Long for comparison
rv = (fileNumber >= CLng(num1) And fileNumber <= CLng(num2))
End If
End If
IsTheCorrectFolder = rv
End Function
'Extract the first 5- or 6-digit number from a string
' Match is "greedy" so if there are six digits it will match 6 and
' not just the first 5...
Function ExtractNumber(txt)
Dim re As Object, allMatches, rv
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "(\d{5,6})"
re.ignorecase = True
re.Global = True
Set allMatches = re.Execute(txt)
If allMatches.Count > 0 Then rv = allMatches(0) 'if there's a match then return the first one
ExtractNumber = rv
End Function
You need to change the lower limit in IF condition also. Like
If Test >= 10000 And Test <= 99999 Then
becomes
If Test >= 100000 And Test <= 999999 Then
Currently the loop could be exiting when it finds the first five digit number.

VBA - Get the Second-Last modified file (FSO) without built-in functions

I'm trying to get the one Before the Last modified File in a Folder Using Excel VBA, I have managed to Get the Last Modified File, But I couldn't get the second one.
Herein Below the code I used to get the Last Modified File, without using system Functions or built-in function.
Sub LastFileModified()
Dim fso As New Scripting.FileSystemObject
Dim fill As Scripting.File
Dim i As Integer
Dim ForStep As Integer
Dim Arr() As Variant
ReDim Arr(fso.GetFolder("C:\Users\Shahim\Desktop\xxxx").Files.Count - 1, 1) As Variant
i = 0
For Each fill In fso.GetFolder("C:\Users\Shahim\Desktop\xxxx").Files
Arr(i, 0) = fill.Name
Arr(i, 1) = CDbl(fill.DateLastModified)
i = i + 1
Next fill
Dim filename As String
Dim Initializer As Double
Initializer = Arr(0, 1)
For ForStep = LBound(Arr) To UBound(Arr)
If Arr(ForStep, 1) > Initializer Then
Initializer = Arr(ForStep, 1)
filename = Arr(ForStep, 0)
End If
Next ForStep
Debug.Print filename
Erase Arr
End Sub
Sub SecodLastModified()
Const FLDR_PATH As String = "C:\Test"
Dim i As Long, j As Long, fileArr() As String, maxFiles As Long
Dim fso As Variant, fldr As Variant, f As Variant, l1 As String, l2 As String
Set fso = CreateObject("Scripting.FileSystemObject")
Set fldr = fso.GetFolder(FLDR_PATH)
maxFiles = fldr.Files.Count
ReDim fileArr(1 To maxFiles, 1 To 2)
i = 1
For Each f In fldr.Files
fileArr(i, 1) = f.Name
fileArr(i, 2) = f.DateLastModified
i = i + 1
Next
For i = 1 To maxFiles
For j = i + 1 To maxFiles
If fileArr(j, 2) > fileArr(i, 2) Then
l1 = fileArr(i, 2)
l2 = fileArr(i, 1)
fileArr(i, 2) = fileArr(j, 2)
fileArr(i, 1) = fileArr(j, 1)
fileArr(j, 2) = l1
fileArr(j, 1) = l2
End If
Next
Next
MsgBox fileArr(2, 1)
End Sub
The original answer did not work for me for two reasons.
fileArr(i,2) was not declared a date, and occassionally Excel could not decipher what was the greater date. When I tried to dim this as a date, it said I could not dim an Array.
If temporary files were included, it did not skip over those files.
Here is what worked for me.
Function SecodLastModified(Directory)
Dim FileSys As FileSystemObject
Dim objFile As File, objFile1 As File
Dim myFolder
Dim strFilename As String, strFolder As String, myDir As String
Dim strFilenameFirst As String, strFilenameSecond As String, strFilenameSecond1 As String
Dim dteFile As Date, dteFileSecond1 As Date, dteFileFirst As Date, dteFileSecond As Date
Dim openLastFile
'set up filesys objects
Set FileSys = New FileSystemObject
Set myFolder = FileSys.GetFolder(Directory)
dteFileSecond1 = DateSerial(1900, 1, 1)
dteFile = DateSerial(1900, 1, 1)
'loop through each file and get date last modified. If largest date then store Filename
For Each objFile In myFolder.Files
For Each objFile1 In myFolder.Files
' To prevent opening temporary files
If objFile1.Name Like "*.xlsx" And Left(objFile1.Name, 2) <> "~$" Then
If objFile1.DateLastModified > objFile.DateLastModified Then
dteFileSecond = objFile.DateLastModified
strFilenameSecond = objFile.Name
dteFileFirst = objFile1.DateLastModified
strFilenameFirst = objFile1.Name
' If second file date is greater than current second file, store away as the second file
If dteFileSecond > dteFileSecond1 Then
dteFileSecond1 = objFile.DateLastModified
strFilenameSecond1 = objFile.Name
End If
End If
End If
Next
Next objFile
Set SecodLastModified = Workbooks.Open(Directory & "\" & strFilenameSecond1)
Set FileSys = Nothing
Set myFolder = Nothing
End Function

Read a CSV file and list of folders and then compare in VB

What I am trying to achieve is I currently have a (main) folder filled with many Sub-folders and these sometimes get drag & dropped into another Sub-folder by accident.
I have an CSV file containing all the names of the current (main) folder list as it should stand and I want to check this against the current version of Sub-folders found in the (main) folder and output a message box with the results of matching files and missing files.
This is the code I have got so far although I am unsure how to check the list of folders against the CSV file.
Read data from an CSV file.
'Holds Data from CSV file
Dim arrValue As String()
'create a new TextFieldParser and opens the file
Using MyReader As New Microsoft.VisualBasic.FileIO.TextFieldParser("C:\Users\USERNAME\Dropbox (Personal)\IT\jobs.csv")
'Define the TextField type and delimiter
MyReader.TextFieldType = FileIO.FieldType.Delimited
MyReader.SetDelimiters(",")
While Not MyReader.EndOfData
Dim arrCurrentRow As String() = MyReader.ReadFields()
If arrValue Is Nothing Then
ReDim Preserve arrValue(0)
arrValue(0) = arrCurrentRow(0)
Else
ReDim Preserve arrValue(arrValue.Length)
arrValue((arrValue.Length - 1)) = arrCurrentRow(0)
End If
End While
Read list of folders
'check against the Clients folder
Set w = WScript.CreateObject("WScript.Shell")
w.Popup ShowFolders("C:\Users\USERNAME\Dropbox (Innovation PS)\Clients")
Function ShowFolders(folderName)
'Setting Variables
Dim fs, f, f1, fc, s
'holds folder name
s = ""
'Obtain folder Object
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFolder(folderName)
'Obtain SubFolders collection within folder
Set fc = f.SubFolders
'Examine each item in the collection
For Each f1 in fc
s = s & f1.name
s = s & (Chr(13) & Chr(10)) ' Chr(13) & Chr(10) = Carriage return–linefeed combination
Next
ShowFolders = s
End Function
'See if it matches the .CSV file
Thank you in advance. (Also if you could include comments it would be appreciated)
Run this script to get a base line, it wil create a spreadsheet of the folders files and properties, Then runt it again copy the sheet in to the baseline work bbok and do a vlookup. You could also use this as a base line to create a csv and compare the it that way. Not exactly waht you are looking for but it is a workable solution
Const ForReading = 1, ForWriting = 2, Forappending = 8
'Option Explicit
'DIM Objects
'Dim variabbles
Dim folderspec
'Dim
DIM arrBlk(3)
DIM arrFLN(3)
DIM arrInfo(3)
Set objXL = Wscript.CreateObject("Excel.Application")
Set ofso = CreateObject("Scripting.FileSystemObject")
folderspec = InputBox("Please enter the path", "FileList", " ")
If folderspec = "" Then
' if cancel is selected quit the program
wscript.quit
ElseIf folderspec = " " Then
' if nothing is entered give a warning message ang quit the program
msgbox "No Directory has been seleted " & vbCrLf
wscript.quit
End If
intRow = 2
buildsheet() 'Build the XLS spreadsheet
'folderspec ="C:\_epas_5.0\Web_Server\ASP"
'folderspec ="C:\_epas_5.0\Web_Server\COM+ Source"
strFldrCmp = folderspec
Set root = ofso.GetFolder(folderspec)
ShowFileList(root)
For Each oFolder in root.subfolders
walkfolder oFolder
Next
Sub walkfolder(f)
ShowFileList(f)
For Each sf in f.subfolders
walkfolder sf
Next
End Sub
Function ShowFileList(folderspec)
Dim oFolder
Dim oFiles
Dim oFile
Set oFolder = ofso.GetFolder(folderspec)
' Wscript.echo oFolder.name
Set oFiles = oFolder.Files
' If IsEmpty(oFiles) Then Wscript.echo oFolder.name
'i = 0
For Each oFile in oFiles
i = 1 + i
'If i < 1 Then
'Wscript.echo oFolder.name,i
'End If
Next
If i < 1 Then
Wscript.echo oFolder.name & " Null"
ReDim arrB(3)
'strPath = Replace(oFolder.path,strFldrCmp,"", 1 ,1 ,vbTextCompare)
arrB(0) = "\" & Trim(oFolder.Name) 'oFolder.path
arrB(1) = ""
arrB(2) = ""
arrB(3) = ""
AddLineToXLS(arrB)
End If
For Each oFile in oFiles
ReDim arrB(3)
srtfldr = oFolder.path
' MsgBox srtfldr& " " & strFldrCmp
'strPath = Replace(srtfldr,strFldrCmp,"", 1 ,1 ,vbTextCompare)
strPath = Replace(oFolder.path,strFldrCmp,"", 1 ,1 ,vbTextCompare)
'strPath = Replace("C:\_5Test\Web_Server\ASP\app\admin","C:\_5Test\Web_Server\ASP","",,,vbTestCompare)
arrB(0) = Trim(strPath) 'oFolder.path
arrB(1) = Trim(oFile.name)
arrB(2) = Trim(oFile.Size)
arrB(3) = Trim(oFile.DateLastModified)
If LCase(ofso.GetExtensionName(oFile)) <> "scc" Then 'skip VSS .scc files
AddLineToXLS(arrB)
End If
Next
End Function
Function buildsheet
intRow = 1
objXL.Visible = True
objXL.WorkBooks.Add
'** Set Row Height
objXL.Rows(1).RowHeight = 17
'** Set Column widths
objXL.Columns(1).ColumnWidth = 40.14
objXL.Columns(2).ColumnWidth = 33.14
objXL.Columns(3).ColumnWidth = 15
objXL.Columns(4).ColumnWidth = 23
objXL.Columns(5).ColumnWidth = 23
objXL.Columns(6).ColumnWidth = 23
'** Set Cell Format for Column Titles ***
objXL.Range("A1:F1").Select
objXL.Selection.Font.Bold = True
' objXL.Selection.Font.Size = 8
objXL.Selection.Interior.ColorIndex = 15
objXL.Selection.Interior.Pattern = 1 'xlSolid
objXL.Selection.Font.ColorIndex = 1
objXL.Selection.WrapText = True
objXL.Columns("A:T").Select
objXL.Columns.Font.Size = 8
objXL.Selection.HorizontalAlignment = 1 'xlCenter
objXL.Columns("C:C").Select
objXL.Selection.NumberFormat = "#,###0"
objXL.Columns("D:D").Select
objXL.Selection.NumberFormat = "m/d/yy h:mm AM/PM"
'*** Set Column Titles ***
Dim arrA(3)
arrA(0)= "File Path"
arrA(1) = "File Name"
arrA(2) = "Size(bytes)"
arrA(3) = "Modified Date/Time"
AddLineToXLS(arrA)
End Function
Function AddLineToXLS(r)' Writes a line to the spreadsheet recieves an array as input
objXL.Cells(intRow, 1).Value = r(0)
objXL.Cells(intRow, 2).Value = r(1)
objXL.Cells(intRow, 3).Value = r(2)
objXL.Cells(intRow, 4).Value = r(3)
' MsgBox r(3)
'objXL.Cells(intRow, 5).Value = r(4)
'objXL.Cells(intRow, 6).Value = r(5)
' objXL.Cells(intRow, 4).Value = r(3)
intRow = intRow + 1
objXL.Cells(1, 1).Select
End Function

Searching a folder to match each file to a table

I added a For loop (see k part) and it really slows down my entire program. Is it possible make this more efficient?
I am searching a specific folder and trying to match each file to a table in my spreadsheet. I am trying to make Quarters(1,j) in the For k loop same as Quarters(i,j) from the lower part of the code but not sure how to do that since I have already used integer i.
For j = 1 To 2
For k = 1 To 39
If k <= 29 Then
'Looks at all the files in the folder for the given Quarter
SourceFolderName = FolderPath & "\" & Quarters(1, j)
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(SourceFolderName)
End If
If k > 29 Then
SourceFolderName = FolderPath & "\" & Quarters(k, j)
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(SourceFolderName)
End If
For Each objFile In objFolder.Files
i = 1
NotAssigned = True
'Keep going until we match the file
While NotAssigned = True
'If the beginning of the file name matches for a given state,
'assign the file name to that state for this quarter
If Left(objFile.Name, 9) = StateAbbr(i, 1) & Quarters(i, j) & "FA" Then
WBName(i, j) = objFile.Name
'Stop trying to match the file
NotAssigned = False
End If
If i = 39 Then NotAssigned = False
i = i + 1
Wend
Next objFile
Set objFile = Nothing
Set objFolder = Nothing
Set objFSO = Nothing
Next k
Next j
I managed to change my entire code to use DIR instead of looping each cell in spreadsheet and looping each file in my folder. My run time cut down from 40 min to 2 second!!!!!!! I am so amazed by this now. Here is the solution if you are interested.
Dim StrFile As String
For j = 1 To 2
For i = 1 To 39
StrFile = Dir(FolderPath & "\" & Quarters(i, j) & "\*FA*")
Do While Len(StrFile) > 0
If Left(StrFile, 9) = StateAbbr(i, 1) & Quarters(i, j) & "FA" Then
WBName(i, j) = StrFile
End If
StrFile = Dir
Loop
Next i
Next j

Get list of sub-directories in VBA

I want to get a list of all sub-directories within a directory.
If that works I want to expand it to a recursive function.
However my initial approach to get the subdirs fails. It simply shows everything including files:
sDir = Dir(sPath, vbDirectory)
Do Until LenB(sDir) = 0
Debug.Print sDir
sDir = Dir
Loop
The list starts with '..' and several folders and ends with '.txt' files.
EDIT:
I should add that this must run in Word, not Excel (many functions are not available in Word) and it is Office 2010.
EDIT 2:
One can determine the type of the result using
iAtt = GetAttr(sPath & sDir)
If CBool(iAtt And vbDirectory) Then
...
End If
But that gave me new problems, so that I am now using a code based on Scripting.FileSystemObject.
Updated July 2014: Added PowerShell option and cut back the second code to list folders only
The methods below that run a full recursive process in place of FileSearch which was deprecated in Office 2007. (The later two codes use Excel for output only - this output can be removed for running in Word)
Shell PowerShell
Using FSO with Dir for filtering file type. Sourced from this EE answer which sits behind the EE paywall. This is longer than what you asked for (a list of folders) but i think it is useful as it gives you an array of results to work further with
Using Dir. This example comes from my answer I supplied on another site
1. Using PowerShell to dump all folders below C:\temp into a csv file
Sub Comesfast()
X2 = Shell("powershell.exe Get-ChildItem c:\temp -Recurse | ?{ $_.PSIsContainer } | export-csv C:\temp\filename.csv", 1)
End Sub
2. Using FileScriptingObject to dump all folders below C:\temp into Excel
Public Arr() As String
Public Counter As Long
Sub LoopThroughFilePaths()
Dim myArr
Dim strPath As String
strPath = "c:\temp\"
myArr = GetSubFolders(strPath)
[A1].Resize(UBound(myArr, 1), 1) = Application.Transpose(myArr)
End Sub
Function GetSubFolders(RootPath As String)
Dim fso As Object
Dim fld As Object
Dim sf As Object
Dim myArr
Set fso = CreateObject("Scripting.FileSystemObject")
Set fld = fso.GetFolder(RootPath)
For Each sf In fld.SUBFOLDERS
ReDim Preserve Arr(Counter)
Arr(Counter) = sf.Path
Counter = Counter + 1
myArr = GetSubFolders(sf.Path)
Next
GetSubFolders = Arr
Set sf = Nothing
Set fld = Nothing
Set fso = Nothing
End Function
3 Using Dir
Option Explicit
Public StrArray()
Public lngCnt As Long
Public b_OS_XP As Boolean
Public Enum MP3Tags
' See http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=160880&page=1 for OS specific attribute lists
XP_Artist = 16
XP_AlbumTitle = 17
XP_SongTitle = 10
XP_TrackNumber = 19
XP_RecordingYear = 18
XP_Genre = 20
XP_Duration = 21
XP_BitRate = 22
Vista_W7_Artist = 13
Vista_W7_AlbumTitle = 14
Vista_W7_SongTitle = 21
Vista_W7_TrackNumber = 26
Vista_W7_RecordingYear = 15
Vista_W7_Genre = 16
Vista_W7_Duration = 17
Vista_W7_BitRate = 28
End Enum
Public Sub Main()
Dim objws
Dim objWMIService
Dim colOperatingSystems
Dim objOperatingSystem
Dim objFSO
Dim objFolder
Dim Wb As Workbook
Dim ws As Worksheet
Dim strobjFolderPath As String
Dim strOS As String
Dim strMyDoc As String
Dim strComputer As String
'Setup Application for the user
With Application
.ScreenUpdating = False
.DisplayAlerts = False
End With
'reset public variables
lngCnt = 0
ReDim StrArray(1 To 10, 1 To 1000)
' Use wscript to automatically locate the My Documents directory
Set objws = CreateObject("wscript.shell")
strMyDoc = objws.SpecialFolders("MyDocuments")
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem")
For Each objOperatingSystem In colOperatingSystems
strOS = objOperatingSystem.Caption
Next
Set objFSO = CreateObject("Scripting.FileSystemObject")
If InStr(strOS, "XP") Then
b_OS_XP = True
Else
b_OS_XP = False
End If
' Format output sheet
Set Wb = Workbooks.Add(1)
Set ws = Wb.Worksheets(1)
ws.[a1] = Now()
ws.[a2] = strOS
ws.[a3] = strMyDoc
ws.[a1:a3].HorizontalAlignment = xlLeft
ws.[A4:J4].Value = Array("Folder", "File", "Artist", "Album Title", "Song Title", "Track Number", "Recording Year", "Genre", "Duration", "Bit Rate")
ws.Range([a1], [j4]).Font.Bold = True
ws.Rows(5).Select
ActiveWindow.FreezePanes = True
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(strMyDoc)
' Start the code to gather the files
ShowSubFolders objFolder, True
ShowSubFolders objFolder, False
If lngCnt > 0 Then
' Finalise output
With ws.Range(ws.[a5], ws.Cells(5 + lngCnt - 1, 10))
.Value2 = Application.Transpose(StrArray)
.Offset(-1, 0).Resize(Rows.Count - 3, 10).AutoFilter
.Offset(-4, 0).Resize(Rows.Count, 10).Columns.AutoFit
End With
ws.[a1].Activate
Else
MsgBox "No files found!", vbCritical
Wb.Close False
End If
' tidy up
Set objFSO = Nothing
Set objws = Nothing
With Application
.ScreenUpdating = True
.DisplayAlerts = True
.StatusBar = vbNullString
End With
End Sub
Sub ShowSubFolders(ByVal objFolder, bRootFolder As Boolean)
Dim objShell
Dim objShellFolder
Dim objShellFolderItem
Dim colFolders
Dim objSubfolder
'strName must be a variant, as ParseName does not work with a string argument
Dim strFname
Set objShell = CreateObject("Shell.Application")
Set colFolders = objFolder.SubFolders
Application.StatusBar = "Processing " & objFolder.Path
If bRootFolder Then
Set objSubfolder = objFolder
GoTo OneTimeRoot
End If
For Each objSubfolder In colFolders
'check to see if root directory files are to be processed
OneTimeRoot:
strFname = Dir(objSubfolder.Path & "\*.mp3")
Set objShellFolder = objShell.Namespace(objSubfolder.Path)
Do While Len(strFname) > 0
lngCnt = lngCnt + 1
If lngCnt Mod 1000 = 0 Then ReDim Preserve StrArray(1 To 10, 1 To (lngCnt + 1000))
Set objShellFolderItem = objShellFolder.ParseName(strFname)
StrArray(1, lngCnt) = objSubfolder
StrArray(2, lngCnt) = strFname
If b_OS_XP Then
StrArray(3, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_Artist)
StrArray(4, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_AlbumTitle)
StrArray(5, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_SongTitle)
StrArray(6, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_TrackNumber)
StrArray(7, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_RecordingYear)
StrArray(8, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_Genre)
StrArray(9, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_Duration)
StrArray(10, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.XP_BitRate)
Else
StrArray(3, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_Artist)
StrArray(4, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_AlbumTitle)
StrArray(5, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_SongTitle)
StrArray(6, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_TrackNumber)
StrArray(7, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_RecordingYear)
StrArray(8, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_Genre)
StrArray(9, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_Duration)
StrArray(10, lngCnt) = objShellFolder.GetDetailsOf(objShellFolderItem, MP3Tags.Vista_W7_BitRate)
End If
strFname = Dir
Loop
If bRootFolder Then
bRootFolder = False
Exit Sub
End If
ShowSubFolders objSubfolder, False
Next
End Sub
You would be better off with the FileSystemObject. I reckon.
To call this you just need, say:
listfolders "c:\data"
Sub listfolders(startfolder)
''Reference Windows Script Host Object Model
''If you prefer, just Dim everything as Object
''and use CreateObject("Scripting.FileSystemObject")
Dim fs As New FileSystemObject
Dim fl1 As Folder
Dim fl2 As Folder
Set fl1 = fs.GetFolder(startfolder)
For Each fl2 In fl1.SubFolders
Debug.Print fl2.Path
listfolders fl2.Path
Next
End Sub
Here is a VBA solution, without using external objects.
Because of the limitations of the Dir() function you need to get the whole content of each folder at once, not while crawling with a recursive algorithm.
Function GetFilesIn(Folder As String) As Collection
Dim F As String
Set GetFilesIn = New Collection
F = Dir(Folder & "\*")
Do While F <> ""
GetFilesIn.Add F
F = Dir
Loop
End Function
Function GetFoldersIn(Folder As String) As Collection
Dim F As String
Set GetFoldersIn = New Collection
F = Dir(Folder & "\*", vbDirectory)
Do While F <> ""
If GetAttr(Folder & "\" & F) And vbDirectory Then GetFoldersIn.Add F
F = Dir
Loop
End Function
Sub Test()
Dim C As Collection, F
Debug.Print
Debug.Print "Files in C:\"
Set C = GetFilesIn("C:\")
For Each F In C
Debug.Print F
Next F
Debug.Print
Debug.Print "Folders in C:\"
Set C = GetFoldersIn("C:\")
For Each F In C
Debug.Print F
Next F
End Sub
EDIT
This version digs into subfolders and returns full path names instead of returning just the file or folder name.
Do NOT run the test with on the whole C drive!!
Function GetFilesIn(Folder As String, Optional Recursive As Boolean = False) As Collection
Dim F As String
Set GetFilesIn = New Collection
F = Dir(Folder & "\*")
Do While F <> ""
GetFilesIn.Add JoinPaths(Folder, F)
F = Dir
Loop
If Recursive Then
Dim SubFolder, SubFile
For Each SubFolder In GetFoldersIn(Folder)
If Right(SubFolder, 2) <> "\." And Right(SubFolder, 3) <> "\.." Then
For Each SubFile In GetFilesIn(CStr(SubFolder), True)
GetFilesIn.Add SubFile
Next SubFile
End If
Next SubFolder
End If
End Function
Function GetFoldersIn(Folder As String) As Collection
Dim F As String
Set GetFoldersIn = New Collection
F = Dir(Folder & "\*", vbDirectory)
Do While F <> ""
If GetAttr(Folder & "\" & F) And vbDirectory Then GetFoldersIn.Add JoinPaths(Folder, F)
F = Dir
Loop
End Function
Function JoinPaths(Path1 As String, Path2 As String) As String
JoinPaths = Replace(Path1 & "\" & Path2, "\\", "\")
End Function
Sub Test()
Dim C As Collection, F
Debug.Print
Debug.Print "Files in C:\"
Set C = GetFilesIn("C:\")
For Each F In C
Debug.Print F
Next F
Debug.Print
Debug.Print "Folders in C:\"
Set C = GetFoldersIn("C:\")
For Each F In C
Debug.Print F
Next F
Debug.Print
Debug.Print "All files in C:\"
Set C = GetFilesIn("C:\", True)
For Each F In C
Debug.Print F
Next F
End Sub
Here is a Simple version without using Scripting.FileSystemObject because I found it slow and unreliable. In particular the .Name method, was slowing everything down. Also I tested this in Excel but I don't think anything I used wouldn't be available in Word.
First some functions:
This joins two strings to create a file path, similar to os.path.join in python. It is useful for not needing to remember if you tacked on that "\" at the end of your path.
Const sep as String = "\"
Function pjoin(root_path As String, file_path As String) As String
If right(root_path, 1) = sep Then
pjoin = root_path & file_path
Else
pjoin = root_path & sep & file_path
End If
End Function
This create a collection of sub items of root directory root_path
Function subItems(root_path As String, Optional pat As String = "*", _
Optional vbtype As Integer = vbNormal) As Collection
Set subItems = New Collection
Dim sub_item As String
sub_item= Dir(pjoin(root_path, pat), vbtype)
While sub_item <> ""
subItems.Add (pjoin(root_path, sub_item))
sub_item = Dir()
Wend
End Function
This creates a collection of sub items in directory root_path that including folders and then removes items that are not folders from the collection. And it can optionally remove those nasty . and .. folders
Function subFolders(root_path As String, Optional pat As String = "", _
Optional skipDots As Boolean = True) As Collection
Set subFolders = subItems(root_path, pat, vbDirectory)
If skipDots Then
Dim dot As String
Dim dotdot As String
dot = pjoin(root_path, ".")
dotdot = dot & "."
Do While subFolders.Item(1) = dot _
Or subFolders.Item(1) = dotdot
subFolders.remove (1)
If subFolders.Count = 0 Then Exit Do
Loop
End If
For i = subFolders.Count To 1 Step -1
' This comparison could be replaced by and `fileExists` function
If Dir(subFolders.Item(i), vbNormal) <> "" Then
subFolders.remove (i)
End If
Next i
End Function
Finally is the recursive search function based on someone else function from this site that used Scripting.FileSystemObject I haven't done any comparison tests between it and the original. If I find that post again I will link it. Note collec is passed by reference so create a new collection and call this sub to populate it. Pass vbType:=vbDirectory for all sub folders.
Sub walk(root_path As String, ByRef collec as Collection, Optional pat As String = "*" _
Optional vbType as Integer = vbNormal)
Dim subF as Collection
Dim subD as Collection
Set subF = subItems(root_path, pat, vbType)
For Each sub_file In subF
collec.Add sub_file
Next sub_file
Set subD = subFolders(root_path)
For Each sub_folder In subD
walk sub_folder , collec, pat, vbType
Next sub_folder
End Sub
Late answer, but posting for others who might have a similar problem.
I had a similar challenge but had the restriction of not being able to use FileSystemObject. Therefore, I wrote a Class library that makes heavy use of the Dir() function to parse all the files and folders in a specified directory. It requires you to set no references to additional libraries in the VBA IDE. Although I wrote it for Excel, I tested and verified it runs in Word also.
You can use it to print a list of all folders like this:
Sub PrintFilesAndFolders(Directory As DirectoryManager, Optional indent As String)
'Helper method
Dim folder As DirectoryManager
Dim newIndent As String
For Each folder In Directory.Folders
Debug.Print indent & "+ " & folder.Name
newIndent = indent & " "
PrintFilesAndFolders folder, newIndent
Next folder
End Sub
Sub LoopThroughAllFilesAndFolders()
Dim dm As DirectoryManager
Set dm = New DirectoryManager
dm.Path = ThisDocument.Path & "\Sample Data Set"
PrintFilesAndFolders dm
End Sub
The example documentation shows how you can modify that script to include files too if you wanted.