Add hyperlinks to linked images - vba

I'm trying to add hyperlinks to images, which were added via IncludePicture fields.
For example, this is an image:
{ IncludePicture "C:\\Test\\Image 1.png" \d }
And so, it should be added hyperlink to it:
C:\\Test\\Image 1.png
After that, I can click on my image in document with mouse, and it will be opened in file manager.
Here is the code. For some reason, it doesn't properly work. How it should be fixed?
Sub AddHyperlinksToImages()
On Error Resume Next
Application.ScreenUpdating = False
Dim iShp As InlineShape
For Each iShp In ActiveDocument.InlineShapes
iShp.Hyperlink.Address = iShp.LinkFormat.SourceFullName 'Doesn't work
'Just for testing
'fullPath = iShp.LinkFormat.SourceFullName
'MsgBox fullPath
Next
Application.ScreenUpdating = True
End Sub

Please try this code.
Sub AddHyperlinksToImages()
' 22 Sep 2017
Dim Fld As Field
Dim FilePath As String
Dim Tmp As String
Dim i As Integer
Application.ScreenUpdating = False
ActiveDocument.Fields.Update
For Each Fld In ActiveDocument.Fields
With Fld
If InStr(1, Trim(.Code), "includepicture", vbTextCompare) = 1 Then
If .InlineShape.Hyperlink Is Nothing Then
i = InStr(.Code, Chr(34))
If i Then
FilePath = Replace(Mid(.Code, i + 1), "\\", "\")
i = InStr(FilePath, "\*")
If i Then FilePath = Left(FilePath, i - 1)
Do While Len(FilePath) > 1
i = Asc(Right(FilePath, 1))
FilePath = Left(FilePath, Len(FilePath) - 1)
If i = 34 Then Exit Do
Loop
If i > 1 Then ActiveDocument.Hyperlinks.Add .InlineShape, FilePath
End If
End If
End If
End With
Next Fld
Application.ScreenUpdating = True
End Sub

Related

split pdf based on the text using vba acrobat api

I am trying to split pdf, based on the pages where it finds ".pdf" however when I try to save the pdf with a dynamic string variable, it do not save the pdf but when I write hardcode file path it output the pdf. do not know what is going on here.
the following code is not finished yet I am stuck in creating new pdf with deleted pages:
Function Extract_PDF()
Dim aApp As Acrobat.CAcroApp
Dim av_Doc As Acrobat.CAcroAVDoc
Dim pdf_Doc As Acrobat.CAcroPDDoc '
Dim newPDFdoc As Acrobat.CAcroPDDoc
Dim Sel_Text As Acrobat.CAcroPDTextSelect
Dim i As Long, j As Long
Dim pageNum, Content
Dim pageContent As Acrobat.CAcroHiliteList
Dim found As Boolean
Dim foundPage As Integer
Dim PDF_Path As String
Dim pdfName As String
Dim folerPath As String
Dim FileExplorer As FileDialog
Set FileExplorer = Application.FileDialog(msoFileDialogFilePicker)
With FileExplorer
.AllowMultiSelect = False
.InitialFileName = ActiveDocument.Path
.Filters.Clear
.Filters.Add "PDF File", "*.pdf"
If .Show = -1 Then
PDF_Path = .SelectedItems.Item(1)
Else
PagesLB = "Catch me Next Time ;)"
PDF_Path = ""
Exit Function
End If
End With
Set aApp = CreateObject("AcroExch.App")
Set av_Doc = CreateObject("AcroExch.AVDoc")
If av_Doc.Open(PDF_Path, vbNull) <> True Then Exit Function
While av_Doc Is Nothing
Set av_Doc = aApp.GetActiveDoc
Wend
av_Doc.BringToFront
aApp.Show
Set pdf_Doc = av_Doc.GetPDDoc
For i = pdf_Doc.GetNumPages - 1 To 0 Step -1
Set pageNum = pdf_Doc.AcquirePage(i)
Set pageContent = CreateObject("AcroExch.HiliteList")
If pageContent.Add(0, 9000) <> True Then Exit Function
Set Sel_Text = pageNum.CreatePageHilite(pageContent)
Content = ""
found = False
For j = 0 To Sel_Text.GetNumText - 1
Content = Content & Sel_Text.GetText(j)
If InStr(1, Content, ".pdf") > 0 Then
found = True
foundPage = i
pdfName = Content
Exit For
End If
Next j
If found Then
PDF_Path = Left(PDF_Path, InStrRev(PDF_Path, "\")) & ValidWBName(pdfName)
Set newPDFdoc = CreateObject("AcroExch.PDDoc")
Set newPDFdoc = av_Doc.GetPDDoc
If newPDFdoc.DeletePages(0, i - 1) = False Then
Debug.Print "Failed"
Else
Debug.Print "done"
End If
If newPDFdoc.Save(PDSaveFull, PDF_Path) = False Then
Debug.Print "Failed to save pdf "
Else
Debug.Print "Saved"
End If
newPDFdoc.Close
End If
Next i
av_Doc.Close False
aApp.Exit
Set av_Doc = Nothing
Set pdf_Doc = Nothing
Set aApp = Nothing
End Function
ValidWBName:
Function ValidWBName(agr As String) As String
Dim RegEx As Object
Set RegEx = CreateObject("VBScript.RegExp")
With RegEx
.Pattern = "[\\/:\*\?""<>\|]"
.Global = True
ValidWBName = .Replace(agr, "")
End With
End Function
in above function when it finds the word pdf it try to create a new instance of pdf and remove previous pages.
If found Then
PDF_Path = Left(PDF_Path, InStrRev(PDF_Path, "\")) & ValidWBName(pdfName)
Set newPDFdoc = CreateObject("AcroExch.PDDoc")
Set newPDFdoc = av_Doc.GetPDDoc
If newPDFdoc.DeletePages(0, i - 1) = False Then
Debug.Print "Failed"
Else
Debug.Print "done"
End If
If newPDFdoc.Save(PDSaveFull, PDF_Path) = False Then
Debug.Print "Failed to save pdf "
Else
Debug.Print "Saved"
End If
newPDFdoc.Close
End If
this line "Failed to save pdf"
If newPDFdoc.Save(PDSaveFull, PDF_Path) = False Then
but when I write hardcode path it create the pdf
If newPDFdoc.Save(PDSaveFull, "C:\Users\MBA\Desktop\PDF Project 2\Murdoch_Michael__Hilary_PIA_19.pdf") = False Then
the culprit HAD to be in ValidWBName() function, which didn't handle all possible not allowed chars for a valid file name
since it came out vbCr char was one of them, you could change it as follows:
Function ValidWBName(agr As String) As String
Dim RegEx As Object
Set RegEx = CreateObject("VBScript.RegExp")
With RegEx
.Pattern = "[\\/:\*\?""<>\|" & Chr(13) & "]" ' <-- added vbCr
.Global = True
ValidWBName = .Replace(agr, "")
End With
End Function

Exclude images with specific name

I have a VBA code that pulls images and inserts in Excel file based on cell value in column A. But in my P drive, from where it pulls images, I have images that end with ' -TH ' and I want to exclude them. i.e. I have image in P drive, that named as "CITY-B" and the other one as "CITY-B-TH". And when I type 'CITY'(this is how I need the name to be typed in excel), I want it to insert the one without "TH". How can i do that?
Private Sub Worksheet_Change(ByVal Target As Range)
If (Split(Target.Address, "$")(1) <> "A") Then Exit Sub
Call Inser_Image(Target)
End Sub
Private Sub Inser_Image(Ac_Cells As Range)
Dim myRng As Range
Dim Mycell As Range
Dim St As String
Dim myPath As String
Dim My_Pic As Shape
Dim My_File As String
Dim Ac_cell As Range
myPath = Sheet1.Cells(1, 5).Value
If Len(myPath) > 3 Then
If Right(myPath, 1) <> "\" Then
myPath = myPath + "\"
End If
End If
For Each Ac_cell In Ac_Cells
For Each My_Pic In Sheet1.Shapes
If My_Pic.Left = Ac_cell.Offset(0, 1).Left And My_Pic.Top = Ac_cell.Offset(0, 1).Top Then
My_Pic.Delete
Exit For
End If
Next
St = Trim(Ac_cell.Value)
If Len(St) > 4 Then
If LCase(Left(St, 4)) = "http" Then
Call Insert_Picture(St, Ac_cell.Offset(0, 1))
GoTo Nextse1
End If
End If
myPath = "P:\"
If Right(myPath, 1) <> "\" Then myPath = myPath + "\"
If Not (Dir(myPath + St)) = "" Then
My_File = St
Else
My_File = Find_File(myPath, St)
End If
If My_File > " " Then
Call Insert_Picture(myPath + My_File, Ac_cell.Offset(0, 1))
End If
Application.ScreenUpdating = True
Nextse1:
Next
End Sub
Sub Insert_Picture(thePath As String, theRange As Range)
On Error GoTo Err3
Dim myPict As Shape
Sheet1.Shapes.AddPicture thePath, True, True, theRange.Left, theRange.Top, theRange.Width, theRange.Height
Set myPict = Sheet1.Shapes(Sheet1.Shapes.Count)
With myPict
.LockAspectRatio = msoFalse
.Placement = xlMoveAndSize
End With
Set myPict = Nothing
Exit Sub
Err3:
MsgBox Err.Description
End Sub
Function Find_File(thePath As String, F_N As String) As String
file = Dir(thePath)
Do Until file = ""
If Len(file) < Len(F_N) Then GoTo EXT_N1
If LCase(Left(file, Len(F_N))) = LCase(F_N) Then
Find_File = file
Exit Function
End If
EXT_N1:
file = Dir()
Loop
Find_File = ""
End Function
Put the EndsWith function in your code. (I included a starts with if it helps down the road) and use it like this:
If My_File > " " Then
If EndsWith(My_File,"-TH") Then
else
Call Insert_Picture(myPath + My_File, Ac_cell.Offset(0, 1))
End If
End If
Public Function EndsWith(str As String, ending As String) As Boolean
Dim endingLen As Integer
endingLen = Len(ending)
EndsWith = (Right(Trim(UCase(str)), endingLen) = UCase(ending))
End Function
Public Function StartsWith(str As String, start As String) As Boolean
Dim startLen As Integer
startLen = Len(start)
StartsWith = (Left(Trim(UCase(str)), startLen) = UCase(start))
End Function
Use InStr to search in the filename your pattern "-TH"
Dim pos As Integer
pos = InStr("find the comma, in the string", ",")

Putting images onto an excel sheet via URL links

My sheet has three columns, "A" = Images, "B" = Image Names, and "C" = URL Links, with Rows 1 and 2 being used as headers and rows 3 to 1002 for user input. The Current working code will search for the image names in Column "B" in the folder you select, and inserts them into Column "A". This macro runs off of a commandbutton I have placed on a userform I have created.
Working code is as follows (this is a edited version of the accepted answer here):
Private Sub Add_Images_Click()
Const EXIT_TEXT As String = ""
Const NO_PICTURE_FOUND As String = "No picture found"
Dim picName As String
Dim picFullName As String
Dim rowIndex As Long
Dim lastRow As Long
Dim selectedFolder As String
Dim data() As Variant
Dim wks As Worksheet
Dim Cell As Range
Dim pic As Picture
On Error GoTo ErrorHandler
selectedFolder = GetFolder
If Len(selectedFolder) = 0 Then GoTo ExitRoutine
Application.ScreenUpdating = False
Set wks = ActiveSheet
lastRow = wks.Cells(2, "B").End(xlDown).Row
data = wks.Range(wks.Cells(3, "B"), wks.Cells(lastRow, "B")).Value2
For rowIndex = 3 To UBound(data, 1)
If StrComp(data(rowIndex, 1), EXIT_TEXT, vbTextCompare) = 0 Then GoTo ExitRoutine
picName = data(rowIndex, 1)
picFullName = selectedFolder & picName
If Len(Dir(picFullName)) > 0 Then
Set Cell = wks.Cells(rowIndex, "A")
Set pic = wks.Pictures.Insert(picFullName)
With pic
.ShapeRange.LockAspectRatio = msoFalse
.Height = Cell.Height
.Width = Cell.Width
.Top = Cell.Top
.Left = Cell.Left
.Placement = xlMoveAndSize
End With
Else
wks.Cells(rowIndex, "A").Value = NO_PICTURE_FOUND
End If
Next rowIndex
ExitRoutine:
Set wks = Nothing
Set pic = Nothing
Application.ScreenUpdating = True
UserForm.Hide
Exit Sub
ErrorHandler:
MsgBox Prompt:="Unable to find photo", _
Title:="An error occured", _
Buttons:=vbExclamation
Resume ExitRoutine
End Sub
Private Function GetFolder() As String
Dim selectedFolder As String
With Application.FileDialog(msoFileDialogFolderPicker)
.InitialFileName = Application.DefaultFilePath & "\"
.Title = "Select the folder containing the Image/PDF files."
.Show
If .SelectedItems.count > 0 Then
selectedFolder = .SelectedItems(1)
If Right$(selectedFolder, 1) <> Application.PathSeparator Then _
selectedFolder = selectedFolder & Application.PathSeparator
End If
End With
GetFolder = selectedFolder
End Function
I'm looking for a way to edit this macro so that it would be able to use the URL links for the images in Column "C" and find and insert the images into Column "A" that way. I found a working code (can't remember where, or I'd link it) that I tried to adapt with my current code to achieve the desired results.
The sample code I found online:
Sub Images_Via_URL()
Dim url_column As Range
Dim image_column As Range
Set url_column = Worksheets(1).UsedRange.Columns("A")
Set image_column = Worksheets(1).UsedRange.Columns("B")
Dim i As Long
For i = 2 To url_column.Cells.Count
With image_column.Worksheet.Pictures.Insert(url_column.Cells(i).Value)
.Left = image_column.Cells(i).Left
.Top = image_column.Cells(i).Top
.Height = 100
.Width = 100
End With
Next
End Sub
The following code is my failed attempt to edit it myself. It worked once for a list of 7 URL links, then I deleted one of the links in the middle to see if it would handle the blank cell correctly, and now it flat out wont work. It goes into the "ExitRoutine" every time.
Not Working Code:
Option Explicit
Private Sub URL_Images_Click()
Const EXIT_TEXT As String = ""
Const NO_PICTURE_FOUND As String = "No picture found"
Dim picURL As String
Dim rowIndex As Long
Dim lastRow As Long
Dim data() As Variant
Dim wks As Worksheet
Dim Cell As Range
Dim pic As Picture
On Error GoTo ErrorHandler
Application.ScreenUpdating = False
Set wks = ActiveSheet
lastRow = wks.Cells(2, "B").End(xlDown).Row
data = wks.Range(wks.Cells(3, "C"), wks.Cells(lastRow, "C")).Value2
For rowIndex = 3 To UBound(data, 1)
**If StrComp(data(rowIndex, 1), EXIT_TEXT, vbTextCompare) = 0 Then GoTo ExitRoutine**
picURL = data(rowIndex, 1)
If Len(picURL) > 0 Then
Set Cell = wks.Cells(rowIndex, "A")
Set pic = wks.Pictures.Insert(picURL)
With pic
.ShapeRange.LockAspectRatio = msoFalse
.Height = Cell.Height
.Width = Cell.Width
.Top = Cell.Top
.Left = Cell.Left
.Placement = xlMoveAndSize
End With
Else
wks.Cells(rowIndex, "A").Value = NO_PICTURE_FOUND
End If
Next rowIndex
ExitRoutine:
Set wks = Nothing
Set pic = Nothing
Application.ScreenUpdating = True
UserForm.Hide
Exit Sub
ErrorHandler:
MsgBox Prompt:="Unable to find photo", _
Title:="An error occured", _
Buttons:=vbExclamation
Resume ExitRoutine
End Sub
I've bolded the line that is forcing it to the "ExitRoutine". I'm not sure how exactly that line works as I am not the one who originally wrote it. Any help would be great!
lastRow = wks.Cells(2, "B").End(xlDown).Row
data = wks.Range(wks.Cells(3, "C"), wks.Cells(lastRow, "C")).Value2
For rowIndex = 3 To UBound(data, 1)
'....
If you start at rowIndex = 3 then you're skipping the first two rows of your input data: a 2-D array from a range always has lower bounds of 1 for both dimensions, regardless of the location of the range.
In this case data(1,1) will correspond to C3, whereas data(3,1) is C5

VBA invalid qualifier with string.copy

I'm writing a code that loops through the textboxes of a word document. These textboxes contain a picture and a caption. So far, I have written a code that gets the caption from the textbox (which I checked through MsgBox caption).
I want to copy the caption, clear the textbox of all content, and then paste the old caption back in (because I'm trying to replace the pictures with an updated one). However, I keep getting an error with caption.Copy and have no idea why. It says that caption is an "Invalid Qualifier." I did some digging around online but haven't solved my problem.
This was the most-related thing I found: Invalid Qualifier error in Visual Basic 6.0
Anyway, here's my code. Any help would be appreciated!
Sub ReplaceImages()
Dim str As String
Dim captionTag As String
Dim imageTag As String
'Dim objShape As Variant Type Mismatch?
Dim fileName As String
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False
'Select directory to match .PNG to figure in document
Set SelectFolder = Application.FileDialog(msoFileDialogFolderPicker)
With SelectFolder
.Title = "Select Directory"
.AllowMultiSelect = False
If .Show <> -1 Then GoTo ResetSettings
sPath = .SelectedItems(1) & "\"
End With
sFile = Dir(sPath & "*png")
Do While sFile <> ""
fileName = sFile
MsgBox fileName
imageTag = BetweenParentheses(fileName)
For Each objShape In ActiveDocument.Shapes
If objShape.Type = msoTextBox Then
Set shapePicture = objShape
str = objShape.TextFrame.TextRange.Text
If InStr(str, "(") > 0 Then
captionTag = BetweenParentheses(objShape.TextFrame.TextRange)
If captionTag = imageTag Then
If InStr(str, "Figure") > 0 Then
Dim firstTerm As String
Dim secondTerm As String
Dim caption As String
firstTerm = "F"
secondTerm = ")"
Dim startPos As Long
Dim stopPos As Long
Dim nextPosition As Long
nextPosition = 1
caption = objShape.TextFrame.TextRange.Text
Do Until nextPosition = 0
startPos = InStr(nextPosition, caption, firstTerm, vbTextCompare) - 1
stopPos = InStr(startPos, caption, secondTerm, vbTextCompare) + 1
caption = Mid$(caption, startPos + Len(firstTerm), stopPos - startPos - Len(firstTerm))
nextPosition = InStr(stopPos, caption, firstTerm, vbTextCompare)
Loop
caption.Copy 'This is where the error is
End If
End If
End If
End If
Next objShape
sFile = Dir
Loop
ResetSettings:
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False
The caption variable is a string while the Copy method only applies to objects in the Word object model.
You store the text from the TextFrame into the caption variable:
caption = objShape.TextFrame.TextRange.Text
And then manipulate it inside your loop.
If you want to keep the value of the caption variable, then assign the value to another variable:
Dim someOtherVariable As String
someOtherVariable = caption
There might be some different between Excel and Word VBA in embedded shapes, but the following should be easy enough to adopt to word:
Sub test()
Dim shp As Shape, s As String
Set shp = ActiveSheet.Shapes(1)
s = shp.TextFrame2.TextRange.Text ' this is a string which doesn't have a Copy method
Debug.Print s
'but:
shp.TextFrame2.TextRange.Copy 'copies to clipboard!
End Sub
You can double check that the text is in the clipboard by pasting it directly into the immediate window (or wherever).

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.