Excel VBA with Microstation Folder Search - vba

i currently have this code in one of the macros for work. It is located under a button for browsing which folder to look into, and it will get the .DGNs and add them to a listbox.
I don't quite understand the code fully was hoping someone can give me a quick run down. Also, the code only looks at the selected folder for .DGNs, i want it to look into sub folders as well, is that possible?
Dim myFSO As New Scripting.FileSystemObject
Dim myFolder As Scripting.Folder
Dim myFile As Scripting.File
Dim myShell As New Shell32.Shell
Dim myRootFolder As Shell32.Folder3
Set myRootFolder = myShell.BrowseForFolder(0, "Pick", 0)
If myRootFolder Is Nothing Then Exit Sub
Set myFolder = myFSO.GetFolder(myRootFolder.Self.path)
txtCurrentFolder.Text = myRootFolder.Self.path
lstFilesInFolder.Clear
For Each myFile In myFolder.Files
Select Case UCase(Right(myFile.Name, 3))
Case "DGN"
If IsFileIn(myFile.path, lstFilesToProcess) = False Then
lstFilesInFolder.AddItem myFile.path
End If
End Select
Next

The code shows a GUI to select a folder, then iterates through the folder's child files testing if their names end in DGN and if so then testing if the file is already in some collection (lstFilesInFolder) and if not then adding it.
I think the approach seems a little complicated (picking a folder(s) can be done simply without using the Shell through Application.FileDialog) and I cannot judge some parts (like is it necessary to test lstFilesInFolder etc) without the rest of the code, and just personally I dislike the use of myX as a variable naming convention. Nevertheless, it does what it seems it is meant to do.
I like a stack/queue based approach to 'recursion' rather than actual recursive calls.
An example of converting your code to something that looks in subfolders as well is: (see comments on my added lines)
Dim myFSO As Scripting.FileSystemObject 'changed from late-binding
Set myFSO = New Scripting.FileSystemObject
Dim folderQueue As Collection 'queue
Set folderQueue = New Collection 'instantiate
Dim myFolder As Scripting.Folder
Dim subfolder As Scripting.Folder 'var for enumerating subfolders
Dim myFile As Scripting.File
Dim myShell As New Shell32.Shell
Dim myRootFolder As Shell32.Folder3
Set myRootFolder = myShell.BrowseForFolder(0, "Pick", 0)
If myRootFolder Is Nothing Then Exit Sub
folderQueue.Add myFSO.GetFolder(myRootFolder.Self.path) 'enqueue
Do While folderQueue.Count > 0 ''recursive' loop
Set myFolder = folderQueue(1) 'get next folder
folderQueue.Remove 1 'dequeue
txtCurrentFolder.Text = myRootFolder.Self.path
lstFilesInFolder.Clear
For Each subfolder in myFolder.SubFolders 'loop through subfolders adding for processing
folderQueue.Add subfolder 'enqueue
Next
For Each myFile In myFolder.Files
Select Case UCase(Right(myFile.Name, 3))
Case "DGN"
If IsFileIn(myFile.path, lstFilesToProcess) = False Then
lstFilesInFolder.AddItem myFile.path
End If
End Select
Next
Loop
As a final point, it is sometimes considered good practice to switch the use of a reference to a specific version of the Scripting library (nice for static typing) to using e.g. CreateObject("Scripting.FileSystemObject") before releasing to other users as the use of a reference can sometimes cause issues.

Related

Vba check if file starts with values from list and if not kill it

In my never ending story to learn VBA I am trying to create a macro that deletes files based on the files starting characters and unsure how to proceed.
I have an excel file with numbers in column a ,these numbers are either 4,5 or 6 digits.
I have a file folder with files which may or may not start with these digits from a range from excel file. These files in folders are of different types
But I reckon this may not be an issue still,the naming convention is as follows : ie. 4563_listofitems.pdf,65475_skusdec.doc etc.
My goal is to loop through files and check if the starting characters of the file are on included in the A range of the excel sheet,if so (there may be up to 6 files starting with such number) create a folder named with the found starting characters and move the files starting with these characters into the folder,else if file doesn't start with fixed characters from the list then just delete (kill) that file. My issue is idk how to check the files names against the list.
My code as now for looping trough
Sub loopf
Dim filen as variant
Filen =dir("c:\test\")
While filen <>""
If instr(1,filen,10000)=1 then
'Here I want check against the values from range but unsure how ,should I somehow loop through the range ?
Filen=dir
End if
Wend
End sub
To check if a value is contained within a known list, I like using the Dictionary Object. It has the function Exists which checks if a value is listed within the Dictionary.
So before you loop through the files, you just need to add every one of your accepted numbers into the dictionary. Then while looping though the files check if Dictionary.Exists(Value). If it exists, then the value is good, if not then Kill.
Here's how I would set that up:
Sub loopf()
Dim AcceptedPrefixes As Object
Set AcceptedPrefixes = CreateObject("Scripting.Dictionary")
Dim PrefixRange As Range
Set PrefixRange = ThisWorkbook.Sheets(1).Range("A1:A5")
Dim Cell As Range
For Each Cell In PrefixRange.Cells
If Cell <> "" And Not AcceptedPrefixes.exists(Cell.Value) Then
AcceptedPrefixes.Add CStr(Cell.Value), 0
End If
Next
Dim Directory As String
Directory = "c:\test\"
Dim filen As Variant
filen = Dir(Directory)
While filen <> ""
Dim FilePrefix As String
FilePrefix = Split(filen, "_")(0)
If Not AcceptedPrefixes.exists(FilePrefix) Then
Kill Directory & filen
End If
filen = Dir
Wend
End Sub
Sub Files()
Dim oFSO As Object
Dim oFolder As Object
Dim oFile As Object
Dim i As Integer
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFolder = oFSO.GetFolder("C:\test")
For Each oFile In oFolder.Files
'do somthing
Next oFile
End Sub

Outlook VBA to Replicate 'Sort Subfolders A to Z' in the Folder Pane

Is there a method in VBA to achieve the same effect of right-clicking on a folder in the folder pane and selecting 'Sort Subfolders A to Z'?
As a comparison, the code below from Microsoft.com sorts Items in a folder; however, it does not appear that the .Sort method used in this code is available for the Folders object like it is for the Items object.
Sub SortByDueDate()
Dim myNameSpace As Outlook.NameSpace
Dim myFolder As Outlook.Folder
Dim myItem As Outlook.TaskItem
Dim myItems As Outlook.Items
Set myNameSpace = Application.GetNamespace("MAPI")
Set myFolder = myNameSpace.GetDefaultFolder(olFolderTasks)
Set myItems = myFolder.Items
myItems.Sort "[DueDate]", False
For Each myItem In myItems
MsgBox myItem.Subject & "-- " & myItem.DueDate
Next myItem
End Sub
Additionally, it does not appear that there are any methods available for moving folders in the tree.
Is more extensive code required to replicate the native 'Sort Subfolders A to Z' action with VBA?
Can this be achieved with PropertyAssessor and, if so, what is the proper syntax for setting the PR_SORT_POSITION property? For example, the code below results in an error, as commented in the code.
Sub Example()
Dim myProp As String
Dim myValue As Variant
Dim oFolder As Folder
Set oFolder = Application.Session.GetDefaultFolder(olFolderInbox)
myProp = "http://schemas.microsoft.com/mapi/proptag/0x30200102"
myValue = "FD7F"
oFolder.PropertyAssessor.SetProperty myProp, myValue 'Run-time error '438': Object doesn't support this property or method
End Sub
The Outlook object model doesn't provide any property or method to sort folders. You may find the NavigationPane object helpful. See Customizing the Navigation Pane for more information.
You can sort the folders in the Outlook UI by explicitly setting the PR_SORT_POSITION property on each subfolder - see Get folder list ordered as displayed
I posted my code here because this was high in Google results and all other threads were closed
https://answers.microsoft.com/en-us/outlook_com/forum/all/sorting-outlook-subfolders-z-a/9aef727c-510c-49e0-869d-4234373b71d7
https://answers.microsoft.com/en-us/outlook_com/forum/all/sort-order-of-subfolders/a3b55181-4f5a-43c1-82b3-94eb68a8407b
I've made custom VBA code to sort subfolders Z-A - it will load the folder order [unfortunately you still need to order it A-Z within outlook] and then reverse it so it is Z-A
I needed to quickly adjust a tonne of folders and couldn't find any code anywhere, so I quickly made the below to help patch the issue.
I didn't have the time to write lots of detail about how it works.
Known issues with the code:
It doesn't always sort the first folder. No idea why.
It doesn't seem to like it when you're looking at the list of subfolders - minimise it then run the code
This code is used to reverse the sorting of subfolders under Inbox, you'll need to adjust as required.
Sub sortZA()
Dim email_name: email_name = "email#emails.com" 'write the name of the mailbox as it appears in outlook
Dim objMainFolder As Outlook.Folder
Dim Folders As Outlook.Folders
Dim Folderx As Outlook.Folder
Dim sort_order, sort_order_b, arr
Set arr = CreateObject("System.Collections.ArrayList")
Set arr_sorted = CreateObject("System.Collections.ArrayList")
dim found_folder: found_folder=0
Set Folders = Application.ActiveExplorer.Session.Folders
For Each Folderx In Folders
If LCase(Folderx.Name) = LCase(email_name) Then
Set objMainFolder = Folderx.Folders("Inbox") 'adjust as required. Add more folders via .folders("name")
found_folder=1
End If
Next
if found_folder =0 then
msgbox "the email folder with the name '" & email_name & "' was not found"
exit sub
end if
Dim reloadFolder As Outlook.Folder
Dim propertyAccessor As Outlook.propertyAccessor
For Each Folderx In objMainFolder.Folders
' if there is an error, then there might not be any order data. Try reordering them manually. Also make sure loading the email as the main profile instead of as an additional mailbox.
'On Error Resume Next
Set propertyAccessor = Folderx.propertyAccessor
sort_order = propertyAccessor.BinaryToString(propertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x30200102"))
arr.Add Folderx.Name & "##~~##" & sort_order
arr_sorted.Add Folderx.Name & "##~~##" & sort_order
Next
arr.Sort 'keep A-Z (the original list from outlook isn't in A-Z order)
arr_sorted.Sort 'make A-Z
arr_sorted.Reverse 'make Z-A
Dim t, a, b, i, t2, a2, b2
i = 0
For Each arr_folder In arr
t = Split(arr_folder, "##~~##")
a = t(0) 'which folder name?
b = t(1) 'what is the original order? [should already be A-Z]
Set Folders = Application.ActiveExplorer.Session.Folders
For Each Folderx In Folders
'On Error Resume Next
If LCase(Folderx.Name) = LCase(email_name) Then
Set reloadFolder = Folderx.Folders("Inbox").Folders(a)
End If
Next
t2 = Split(arr_sorted(i), "##~~##")
a2 = t2(0) 'which folder name?
b2 = t2(1) 'what is the reversed order?
Set propertyAccessor = reloadFolder.propertyAccessor
propertyAccessor.SetProperty "http://schemas.microsoft.com/mapi/proptag/0x30200102", propertyAccessor.StringToBinary(b2)
i = i + 1
Next
End Sub
Additional Notes: I did try experimenting with applying ordering data manually. I couldn't get it to work properly. All the binary converting code wasn't producing the correct values, and I ended up using HEX(). Here is an example of what I was doing:
Dim custom_order As Long
custom_order = 15
For Each arr_folder In arr
'the array only contains a list of folder names.. we need to load the folder in outlook to process it again. The below line of code loads the main email inbox, then the subfolder from the array [different from the above code]
Set reloadFolder = Application.Session.GetDefaultFolder(olFolderInbox).Folders(arr_folder)
Set propertyAccessor = reloadFolder.propertyAccessor
hexval = Hex(custom_order)
propertyAccessor.SetProperty "http://schemas.microsoft.com/mapi/proptag/0x30200102", propertyAccessor.StringToBinary(hexval)
custom_order = custom_order + 1
Next
End Sub

VBA subsequent calls to Dir() returns same file

I am trying to search through a directory for Shortcuts, get the path for the Shortcut, and add those paths to a collection, for later usage. However subsequent calls to Dir() returns the same file over and over again. I have isolated the problem to being caused by calling the Function Getlnkpath defined below. This function I haven't written myself, so I am unsure exactly what is causing this behaviour, or how to fix it.
tempPath = Dir(startPath & "*.lnk")
Do Until tempPath = vbNullString
myCollection.Add Getlnkpath(startPath & tempPath) & "\"
tempPath = Dir()
Loop
Function Getlnkpath(ByVal Lnk As String)
On Error Resume Next
With CreateObject("Wscript.Shell").CreateShortcut(Lnk)
Getlnkpath = .TargetPath
.Close
End With
End Function
It might be safer to
first: collect all links paths
then: collect all link target paths
so that the first collection stays stable whatever the subsequent operations may do (unless they delete some link or some folder...)
moreover I'd suggest to initialize one Wscript.Shell object only and handle all calls to its CreateShortcut() with it, instead of instantiating one object for each link
finally I myself am drifting towards the use of FileSystemObject in lieu of Dir() function, due to problems I sometimes meet with the latter. this at the only expense of adding the reference to Microsoft Scripting Runtime library
for what above I propose the following code:
Option Explicit
Sub main()
Dim startPath As String
Dim myLinkTargetPaths As New Collection, myLinkFilePaths As Collection
startPath = "C:\myPath\"
Set myLinkFilePaths = GetLinksPaths(startPath) 'first get the collection of all links path
Set myLinkTargetPaths = GetLinksTarget(myLinkFilePaths) ' then get the collection of all links TargetPaths
End Sub
Function GetLinksTarget(myLinkFilePaths As Collection) As Collection
Dim myColl As New Collection
Dim element As Variant
With CreateObject("Wscript.Shell")
For Each element In myLinkFilePaths
myColl.Add .CreateShortcut(element).TargetPath & "\"
Next element
End With
Set GetLinksTarget = myColl
End Function
Function GetLinksPaths(startPath As String) As Collection
Dim objFso As FileSystemObject '<~~ requires adding reference to `Microsoft Scripting Runtime` library
Dim objFile As File
Dim objFolder As Folder
Dim myColl As New Collection
Set objFso = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFso.GetFolder(startPath)
For Each objFile In objFolder.Files
If objFso.GetExtensionName(objFile.Path) = "lnk" Then myColl.Add objFile.Path
Next
Set GetLinksPaths = myColl
End Function
instead, should you want to go on with Dir() function then just change the GetLinksPaths() function as follows:
Function GetLinksPaths(startPath As String) As Collection
Dim tempPath As String
Dim myColl As New Collection
tempPath = Dir(startPath & "*.lnk")
Do Until tempPath = vbNullString
myColl.Add startPath & tempPath
tempPath = Dir()
Loop
Set GetLinksPaths = myColl
End Function
BTW: the CreateObject("Wscript.Shell").CreateShortcut(Lnk) method returns and object (either a WshShortcut or a WshURLShortcut one) that doesn't support any Close() method as you have in your Getlnkpath() function. So remove it to remove the necessity of On Error Resume Nextstatement
Looks like you are creating a new .lnk file with your function and your dir command finds that newly created link (that has overwritten the old one) next. Try to use GetShortcut instead of CreateShortcut in your function.

VBA FileCopy: file not found issue

I am working on a project in VBA where I'm searching a directory for files of a certain date that also meet other criteria, and all of that is working fine. What I am stuck on, which should be a trivial issue, is that when I try to use FileCopy to copy the file to another folder, I keep getting a path/file access error. This confuses me because I am pulling the path directly from the FileItem I'm using to search for the criteria, I'm the person who created both the source and destination folders, and put the files in the source folder. Any thoughts?
Sub ListFilesInFolder()
Dim counter As Integer
Dim theString1 As String, theString2 As String
theString1 = "ISA*00*"
theString2 = "ISA|00|"
Dim line As String, fileName As String
Dim datestring As String
'datestring = Format(FileItem.DateLastModified, "mm/dd/yyyy")
Dim today As String
today = Format(DateAdd("d", -2, Date), "mm/dd/yyyy")
Dim destinationFolder As String
destinationFolder = "C:\Users\kragan\Desktop\test\folder2"
Dim file As TextStream
Dim FSO As Scripting.FileSystemObject
Set FSO = New Scripting.FileSystemObject
Dim SourceFolder As Scripting.Folder
Set SourceFolder = FSO.GetFolder("C:\Users\kragan\Desktop\test\folder1")
Dim FileItem As Scripting.file
For Each FileItem In SourceFolder.Files
datestring = "10/18/2015"
If (datestring = today) Then
Do While (SourceFolder <> "")
Set file = FSO.OpenTextFile(FileItem)
counter = 0
Do While Not file.AtEndOfLine And counter < 1
line = file.ReadLine
If InStr(1, line, theString1, vbTextCompare) > 0 Or InStr(1, line, theString2, vbTextCompare) > 0 Then
fileName = "C:\Users\kragan\Desktop\test\folder1\" + FSO.GetBaseName(FileItem) + ".AETCLS"
'The line where I get the error:
FileCopy fileName, destinationFolder
file.Close
counter = counter + 1
Exit Do
End If
Loop
Loop
End If
Next FileItem
MsgBox ("done")
End Sub
You need to specify destination folder + destination file name (and not only destination folder).
So this will work:
FileCopy fileName, destinationFolder & "\" & FSO.GetBaseName(FileItem) & ".AETCLS"
You were probably assuming this will work like a file manager application, where giving destination folder is enough... :) But when programming, you need to specify destination path exactly as it is, i.e. including the file name.
One of my friends was getting similar error on creating directory newdir1\newdir2\newdir3. It was not working despite his best effort. But the solution was to create newdir1, then it became possible to specify newdir1\newdir2, then finally newdir1\newdir2\newdir3. Programming file operations does not do the job often seen in file managers, but everything must be specified in detail and performed in elementary steps.
Thanks for your help. What I discovered is that you have to have the full destination path, including the file name, even though it isn't changing. Solved!

Excel VBA using Workbook.Open with results of Dir(Directory)

This seems so simple and I've had it working multiple times, but something keeps breaking between my Dir call (to iterate through a directory) and opening the current file. Here's the pertinent code:
SourceLoc = "C:\ExcelWIP\TestSource\"
SourceCurrentFile = Dir(SourceLoc)
'Start looping through directory
While (SourceCurrentFile <> "")
Application.Workbooks.Open (SourceCurrentFile)
What I get with this is a file access error as the Application.Workbooks.Open is trying to open "C:\ExcelWIP\TestSource\\FILENAME" (note extra slash)
However when I take the final slash out of SourceLoc, the results of Dir(SourceLoc) are "" (it doesn't search the directory).
The frustrating thing is that as I've edited the sub in other ways, the functionality of this code has come and gone. I've had it work as-is, and I've had taking the '/' out of the directory path make it work, and at the moment, I just can't get these to work right together.
I've scoured online help and ms articles but nothing seems to point to a reason why this would keep going up and down (without being edited except for when it stops working) and why the format of the directory path will sometimes work with the final '/' and sometimes without.
any ideas?
This would open all .xlxs files in that directory son.
Sub OpenFiles()
Dim SourceCurrentFile As String
Dim FileExtension as String: FileExtension = "*.xlxs"
SourceLoc = "C:\ExcelWIP\TestSource\"
SourceCurrentFile = Dir(SourceLoc)
SourceCurrentFile = Dir()
'Start looping through directory
Do While (SourceCurrentFile <> "")
Application.Workbooks.Open (SourceLoc &"\"& SourceCurrentFile)
SourceCurrentFile = Dir(FileExtension)
Loop
End Sub
JLILI Aman hit on the answer which was to take the results of Dir() as a string. Using that combined with the path on Application.Open allows for stable behaviors from the code.
New Code:
Dim SourceLoc as String
Dim SourceCurrentFile as String
SourceLoc = "C:\ExcelWIP\TestSource\"
SourceCurrentFile = Dir(SourceLoc)
'Start looping through directory
While (SourceCurrentFile <> "")
Application.Workbooks.Open (SourceLoc & "/" & SourceCurrentFile)
I didn't include the recommended file extension because I'm dealing with xls, xlsx, and xlsm files all in one directory. This code opens all of them.
Warning - this code will set current file to each file in the directory including non-excel files. In my case, I'm only dealing with excel files so that's not a problem.
As to why this happens, it does not appear that Application.Open will accept the full object results of Dir(), so the return of Dir() needs to be a String. I didn't dig deeper into the why of it beyond that.
Consider using VBA's FileSystemObject which includes the folder and file property:
Sub xlFilesOpen()
Dim strPath As String
Dim objFSO As Object, objFolder As Object, xlFile As Object
strPath = "C:\ExcelWIP\TestSource"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(strPath)
For Each xlFile In objFolder.Files
If Right(xlFile, 4) = "xlsx" Or Right(xlFile, 3) = "xls" Then
Application.Workbooks.Open (xlFile)
End If
Next xlFile
Set objFSO = Nothing
Set objFolder = Nothing
End Sub