-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathLibFileTools.bas
3521 lines (3441 loc) · 144 KB
/
LibFileTools.bas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Attribute VB_Name = "LibFileTools"
'''=============================================================================
''' VBA FileTools
''' ---------------------------------------------
''' https://github.com/cristianbuse/VBA-FileTools
''' ---------------------------------------------
''' MIT License
'''
''' Copyright (c) 2012 Ion Cristian Buse
'''
''' Permission is hereby granted, free of charge, to any person obtaining a copy
''' of this software and associated documentation files (the "Software"), to
''' deal in the Software without restriction, including without limitation the
''' rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
''' sell copies of the Software, and to permit persons to whom the Software is
''' furnished to do so, subject to the following conditions:
'''
''' The above copyright notice and this permission notice shall be included in
''' all copies or substantial portions of the Software.
'''
''' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
''' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
''' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
''' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
''' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
''' FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
''' IN THE SOFTWARE.
'''=============================================================================
'*******************************************************************************
'' Functions in this library module allow easy file system manipulation in VBA
'' regardless of:
'' - the host Application (Excel, Word, AutoCAD etc.)
'' - the operating system (Mac, Windows)
'' - application environment (x32, x64)
'' No extra library references are needed (e.g. Microsoft Scripting Runtime)
''
'' Public/Exposed methods:
'' - BrowseForFiles (Windows only)
'' - BrowseForFolder (Windows only)
'' - BuildPath
'' - ConvertText
'' - CopyFile
'' - CopyFolder
'' - CreateFolder
'' - DecodeURL
'' - DeleteFile
'' - DeleteFolder
'' - FixFileName
'' - FixPathSeparators
'' - GetFileOwner (Windows only)
'' - GetFiles
'' - GetFolders
'' - GetKnownFolderCLSID (Windows only)
'' - GetKnownFolderPath (Windows only)
'' - GetLocalPath
'' - GetRelativePath
'' - GetRemotePath
'' - GetSpecialFolderConstant (Mac only)
'' - GetSpecialFolderDomain (Mac only)
'' - GetSpecialFolderPath (Mac only)
'' - IsFile
'' - IsFolder
'' - IsFolderEditable
'' - MoveFile
'' - MoveFolder
'' - ParentFolder
'' - ReadBytes
'*******************************************************************************
Option Explicit
Option Private Module
#Const Windows = (Mac = 0)
#If Mac Then
#If VBA7 Then 'https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/iconv.3.html
Private Declare PtrSafe Function iconv Lib "/usr/lib/libiconv.dylib" (ByVal cd As LongPtr, ByRef inBuf As LongPtr, ByRef inBytesLeft As LongPtr, ByRef outBuf As LongPtr, ByRef outBytesLeft As LongPtr) As LongPtr
Private Declare PtrSafe Function iconv_open Lib "/usr/lib/libiconv.dylib" (ByVal toCode As LongPtr, ByVal fromCode As LongPtr) As LongPtr
Private Declare PtrSafe Function iconv_close Lib "/usr/lib/libiconv.dylib" (ByVal cd As LongPtr) As Long
#Else
Private Declare Function iconv Lib "/usr/lib/libiconv.dylib" (ByVal cd As Long, ByRef inBuf As Long, ByRef inBytesLeft As Long, ByRef outBuf As Long, ByRef outBytesLeft As Long) As Long
Private Declare Function iconv_open Lib "/usr/lib/libiconv.dylib" (ByVal toCode As Long, ByVal fromCode As Long) As Long
Private Declare Function iconv_close Lib "/usr/lib/libiconv.dylib" (ByVal cd As Long) As Long
#End If
#Else
#If VBA7 Then
Private Declare PtrSafe Function CommDlgExtendedError Lib "comdlg32.dll" () As Long
Private Declare PtrSafe Function GetOpenFileNameW Lib "comdlg32.dll" (pOpenfilename As OPENFILENAME) As Long
Private Declare PtrSafe Function CopyFileW Lib "kernel32" (ByVal lpExistingFileName As LongPtr, ByVal lpNewFileName As LongPtr, ByVal bFailIfExists As Long) As Long
Private Declare PtrSafe Function DeleteFileW Lib "kernel32" (ByVal lpFileName As LongPtr) As Long
Private Declare PtrSafe Function RemoveDirectoryW Lib "kernel32" (ByVal lpPathName As LongPtr) As Long
Private Declare PtrSafe Function GetFileSecurity Lib "advapi32.dll" Alias "GetFileSecurityA" (ByVal lpFileName As String, ByVal RequestedInformation As Long, pSecurityDescriptor As Byte, ByVal nLength As Long, lpnLengthNeeded As Long) As Long
Private Declare PtrSafe Function GetSecurityDescriptorOwner Lib "advapi32.dll" (pSecurityDescriptor As Byte, pOwner As LongPtr, lpbOwnerDefaulted As LongPtr) As Long
Private Declare PtrSafe Function LookupAccountSid Lib "advapi32.dll" Alias "LookupAccountSidA" (ByVal lpSystemName As String, ByVal Sid As LongPtr, ByVal Name As String, cbName As Long, ByVal ReferencedDomainName As String, cbReferencedDomainName As Long, peUse As LongPtr) As Long
Private Declare PtrSafe Function MultiByteToWideChar Lib "kernel32" (ByVal codePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long) As Long
Private Declare PtrSafe Function WideCharToMultiByte Lib "kernel32" (ByVal codePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long, ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, ByVal lpDefaultChar As LongPtr, ByVal lpUsedDefaultChar As LongPtr) As Long
Private Declare PtrSafe Function SHGetKnownFolderPath Lib "shell32" (ByRef rfID As GUID, ByVal dwFlags As Long, ByVal hToken As Long, ByRef pszPath As LongPtr) As Long
Private Declare PtrSafe Function CLSIDFromString Lib "ole32" (ByVal lpszGuid As LongPtr, ByRef pGuid As GUID) As Long
Private Declare PtrSafe Function lstrlenW Lib "kernel32" (ByVal lpString As LongPtr) As Long
Private Declare PtrSafe Sub CoTaskMemFree Lib "ole32" (ByVal hMem As LongPtr)
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr)
#Else
Private Declare Function CommDlgExtendedError Lib "comdlg32.dll" () As Long
Private Declare Function GetOpenFileNameW Lib "comdlg32.dll" (pOpenfilename As OPENFILENAME) As Long
Private Declare Function CopyFileW Lib "kernel32" (ByVal lpExistingFileName As Long, ByVal lpNewFileName As Long, ByVal bFailIfExists As Long) As Long
Private Declare Function DeleteFileW Lib "kernel32" (ByVal lpFileName As Long) As Long
Private Declare Function RemoveDirectoryW Lib "kernel32" (ByVal lpPathName As Long) As Long
Private Declare Function GetFileSecurity Lib "advapi32.dll" Alias "GetFileSecurityA" (ByVal lpFileName As String, ByVal RequestedInformation As Long, pSecurityDescriptor As Byte, ByVal nLength As Long, lpnLengthNeeded As Long) As Long
Private Declare Function GetSecurityDescriptorOwner Lib "advapi32.dll" (pSecurityDescriptor As Byte, pOwner As Long, lpbOwnerDefaulted As Long) As Long
Private Declare Function LookupAccountSid Lib "advapi32.dll" Alias "LookupAccountSidA" (ByVal lpSystemName As String, ByVal Sid As Long, ByVal Name As String, cbName As Long, ByVal ReferencedDomainName As String, cbReferencedDomainName As Long, peUse As Long) As Long
Private Declare Function MultiByteToWideChar Lib "kernel32" (ByVal codePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As Long, ByVal cchMultiByte As Long, ByVal lpWideCharStr As Long, ByVal cchWideChar As Long) As Long
Private Declare Function WideCharToMultiByte Lib "kernel32" (ByVal codePage As Long, ByVal dwFlags As Long, ByVal lpWideCharStr As Long, ByVal cchWideChar As Long, ByVal lpMultiByteStr As Long, ByVal cchMultiByte As Long, ByVal lpDefaultChar As Long, ByVal lpUsedDefaultChar As Long) As Long
Private Declare Function SHGetKnownFolderPath Lib "shell32" (rfID As Any, ByVal dwFlags As Long, ByVal hToken As Long, ppszPath As Long) As Long
Private Declare Function CLSIDFromString Lib "ole32" (ByVal lpszGuid As Long, pGuid As Any) As Long
Private Declare Function lstrlenW Lib "kernel32" (ByVal lpString As Long) As Long
Private Declare Sub CoTaskMemFree Lib "ole32" (ByVal pv As Long)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long)
#End If
#End If
#If VBA7 = 0 Then
Private Enum LongPtr
[_]
End Enum
#End If
Public Enum PageCode
[_pcCount] = 5
codeUTF8 = 65001
codeUTF16LE = 1200
codeUTF16BE = 1201
#If Mac Then
codeUTF32LE = 12000
codeUTF32BE = 12001
#End If
End Enum
#If Mac Then
Public Enum SpecialFolderConstant 'See 'GetSpecialFolderConstant'
sfc_ApplicationSupport
[_minSFC] = sfc_ApplicationSupport
sfc_ApplicationsFolder
sfc_Desktop
sfc_DesktopPicturesFolder
sfc_DocumentsFolder
sfc_DownloadsFolder
sfc_FavoritesFolder
sfc_FolderActionScripts
sfc_Fonts
sfc_Help
sfc_HomeFolder
sfc_InternetPlugins
sfc_KeychainFolder
sfc_LibraryFolder
sfc_ModemScripts
sfc_MoviesFolder
sfc_MusicFolder
sfc_PicturesFolder
sfc_Preferences
sfc_PrinterDescriptions
sfc_PublicFolder
sfc_ScriptingAdditions
sfc_ScriptsFolder
sfc_ServicesFolder
sfc_SharedDocuments
sfc_SharedLibraries
sfc_SitesFolder
sfc_StartupDisk
sfc_StartupItems
sfc_SystemFolder
sfc_SystemPreferences
sfc_TemporaryItems
sfc_Trash
sfc_UsersFolder
sfc_UtilitiesFolder
sfc_WorkflowsFolder
'
'Classic domain only
sfc_AppleMenu
sfc_ControlPanels
sfc_ControlStripModules
sfc_Extensions
sfc_LauncherItemsFolder
sfc_PrinterDrivers
sfc_Printmonitor
sfc_ShutdownFolder
sfc_SpeakableItems
sfc_Stationery
sfc_Voices
[_maxSFC] = sfc_Voices
End Enum
Public Enum SpecialFolderDomain 'See 'GetSpecialFolderDomain
[_sfdNone] = 0
[_minSFD] = [_sfdNone]
sfd_System
sfd_Local
sfd_Network
sfd_User
sfd_Classic
[_maxSFD] = sfd_Classic
End Enum
#Else
Public Enum KnownFolderID 'See 'GetKnownFolderCLSID' method
kfID_AccountPictures = 0
[_minKfID] = kfID_AccountPictures
kfID_AddNewPrograms
kfID_AdminTools
kfID_AllAppMods
kfID_AppCaptures
kfID_AppDataDesktop
kfID_AppDataDocuments
kfID_AppDataFavorites
kfID_AppDataProgramData
kfID_ApplicationShortcuts
kfID_AppsFolder
kfID_AppUpdates
kfID_CameraRoll
kfID_CameraRollLibrary
kfID_CDBurning
kfID_ChangeRemovePrograms
kfID_CommonAdminTools
kfID_CommonOEMLinks
kfID_CommonPrograms
kfID_CommonStartMenu
kfID_CommonStartMenuPlaces
kfID_CommonStartup
kfID_CommonTemplates
kfID_ComputerFolder
kfID_ConflictFolder
kfID_ConnectionsFolder
kfID_Contacts
kfID_ControlPanelFolder
kfID_Cookies
kfID_CurrentAppMods
kfID_Desktop
kfID_DevelopmentFiles
kfID_Device
kfID_DeviceMetadataStore
kfID_Documents
kfID_DocumentsLibrary
kfID_Downloads
kfID_Favorites
kfID_Fonts
kfID_Games
kfID_GameTasks
kfID_History
kfID_HomeGroup
kfID_HomeGroupCurrentUser
kfID_ImplicitAppShortcuts
kfID_InternetCache
kfID_InternetFolder
kfID_Libraries
kfID_Links
kfID_LocalAppData
kfID_LocalAppDataLow
kfID_LocalDocuments
kfID_LocalDownloads
kfID_LocalizedResourcesDir
kfID_LocalMusic
kfID_LocalPictures
kfID_LocalStorage
kfID_LocalVideos
kfID_Music
kfID_MusicLibrary
kfID_NetHood
kfID_NetworkFolder
kfID_Objects3D
kfID_OneDrive
kfID_OriginalImages
kfID_PhotoAlbums
kfID_Pictures
kfID_PicturesLibrary
kfID_Playlists
kfID_PrintersFolder
kfID_PrintHood
kfID_Profile
kfID_ProgramData
kfID_ProgramFiles
kfID_ProgramFilesCommon
kfID_ProgramFilesCommonX64
kfID_ProgramFilesCommonX86
kfID_ProgramFilesX64
kfID_ProgramFilesX86
kfID_Programs
kfID_Public
kfID_PublicDesktop
kfID_PublicDocuments
kfID_PublicDownloads
kfID_PublicGameTasks
kfID_PublicLibraries
kfID_PublicMusic
kfID_PublicPictures
kfID_PublicRingtones
kfID_PublicUserTiles
kfID_PublicVideos
kfID_QuickLaunch
kfID_Recent
kfID_RecordedCalls
kfID_RecordedTVLibrary
kfID_RecycleBinFolder
kfID_ResourceDir
kfID_RetailDemo
kfID_Ringtones
kfID_RoamedTileImages
kfID_RoamingAppData
kfID_RoamingTiles
kfID_SampleMusic
kfID_SamplePictures
kfID_SamplePlaylists
kfID_SampleVideos
kfID_SavedGames
kfID_SavedPictures
kfID_SavedPicturesLibrary
kfID_SavedSearches
kfID_Screenshots
kfID_SEARCH_CSC
kfID_SEARCH_MAPI
kfID_SearchHistory
kfID_SearchHome
kfID_SearchTemplates
kfID_SendTo
kfID_SidebarDefaultParts
kfID_SidebarParts
kfID_SkyDrive
kfID_SkyDriveCameraRoll
kfID_SkyDriveDocuments
kfID_SkyDriveMusic
kfID_SkyDrivePictures
kfID_StartMenu
kfID_StartMenuAllPrograms
kfID_Startup
kfID_SyncManagerFolder
kfID_SyncResultsFolder
kfID_SyncSetupFolder
kfID_System
kfID_SystemX86
kfID_Templates
kfID_UserPinned
kfID_UserProfiles
kfID_UserProgramFiles
kfID_UserProgramFilesCommon
kfID_UsersFiles
kfID_UsersLibraries
kfID_Videos
kfID_VideosLibrary
kfID_Windows
[_maxKfID] = kfID_Windows
End Enum
#End If
Private Type DRIVE_INFO
driveName As String
driveLetter As String
fileSystem As String
shareName As String
End Type
#If Windows Then
Private Type GUID
data1 As Long
data2 As Integer
data3 As Integer
data4(0 To 7) As Byte
End Type
'
'https://docs.microsoft.com/en-gb/windows/win32/api/commdlg/ns-commdlg-openfilenamea
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As LongPtr
hInstance As LongPtr
lpstrFilter As LongPtr
lpstrCustomFilter As LongPtr
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As LongPtr
nMaxFile As Long
lpstrFileTitle As LongPtr
nMaxFileTitle As Long
lpstrInitialDir As LongPtr
lpstrTitle As LongPtr
flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As LongPtr
lCustData As LongPtr
lpfnHook As LongPtr
lpTemplateName As LongPtr
pvReserved As LongPtr
dwReserved As Long
flagsEx As Long
End Type
#End If
Private Type ONEDRIVE_PROVIDER
webPath As String
mountPoint As String
isBusiness As Boolean
isMain As Boolean
accountIndex As Long
baseMount As String
syncID As String
#If Mac Then
syncDir As String
#End If
End Type
Private Type ONEDRIVE_PROVIDERS
arr() As ONEDRIVE_PROVIDER
pCount As Long
isSet As Boolean
lastCacheUpdate As Date
End Type
Private Type ONEDRIVE_ACCOUNT_INFO
accountIndex As Long
accountName As String
cID As String
clientPath As String
datPath As String
dbPath As String
folderPath As String
globalPath As String
groupPath As String
iniDateTime As Date
iniPath As String
isPersonal As Boolean
isValid As Boolean
hasDatFile As Boolean
End Type
Private Type ONEDRIVE_ACCOUNTS_INFO
arr() As ONEDRIVE_ACCOUNT_INFO
pCount As Long
isSet As Boolean
End Type
Private Type DirInfo
dirID As String
parentID As String
dirName As String
isNameASCII As Boolean
End Type
Private Type DirsInfo
idToIndex As Collection
arrDirs() As DirInfo
dirCount As Long
dirUBound As Long
End Type
#If Mac Then
Public Const PATH_SEPARATOR = "/"
#Else
Public Const PATH_SEPARATOR = "\"
#End If
Private Const vbErrInvalidProcedureCall As Long = 5
Private Const vbErrInternalError As Long = 51
Private Const vbErrPathFileAccessError As Long = 75
Private Const vbErrPathNotFound As Long = 76
Private Const vbErrInvalidFormatInResourceFile As Long = 325
Private Const vbErrComponentNotRegistered As Long = 336
Private m_providers As ONEDRIVE_PROVIDERS
#If Mac Then
Private m_conversionDescriptors As New Collection
#End If
'*******************************************************************************
'Returns a Collection of file paths by using a FilePicker FileDialog
'Always returns an instantiated Collection
'
'More than one file extension may be specified in the 'filterExtensions' param
' and each must be separated by a semi-colon. For example: "*.txt;*.csv".
' Spaces will be ignored
'*******************************************************************************
Public Function BrowseForFiles(Optional ByRef initialPath As String _
, Optional ByRef dialogTitle As String _
, Optional ByRef filterDesc As String _
, Optional ByRef filterExtensions As String _
, Optional ByVal allowMultiFiles As Boolean = True) As Collection
'msoFileDialogFilePicker = 3 - only available for some Microsoft apps
Const dialogTypeFilePicker As Long = 3
Const actionButton As Long = -1
Dim filePicker As Object
Dim app As Object: Set app = Application 'Late-binded for compatibility
'
On Error Resume Next
Set filePicker = app.FileDialog(dialogTypeFilePicker)
On Error GoTo 0
'
If filePicker Is Nothing Then
#If Mac Then
'Not implemented
'Seems achievable via script:
' - https://stackoverflow.com/a/15546518/8488913
' - https://stackoverflow.com/a/37411960/8488913
#Else
Set BrowseForFiles = BrowseFilesAPI(initialPath, dialogTitle, filterDesc _
, filterExtensions, allowMultiFiles)
#End If
Exit Function
End If
'
With filePicker
If LenB(dialogTitle) > 0 Then .Title = dialogTitle
If LenB(initialPath) > 0 Then .InitialFileName = initialPath
.allowMultiSelect = allowMultiFiles
.filters.Clear
If LenB(filterExtensions) > 0 Then
On Error Resume Next
.filters.Add filterDesc, filterExtensions
On Error GoTo 0
End If
If .filters.Count = 0 Then .filters.Add "All Files", "*.*"
'
Set BrowseForFiles = New Collection
If .Show = actionButton Then
Dim v As Variant
'
For Each v In .SelectedItems
BrowseForFiles.Add v
Next v
End If
End With
End Function
'*******************************************************************************
'Returns a Collection of file paths by creating an Open dialog box that lets the
' user specify the drive, directory, and the name of the file(s)
'*******************************************************************************
#If Windows Then
Private Function BrowseFilesAPI(ByRef initialPath As String _
, ByRef dialogTitle As String _
, ByRef filterDesc As String _
, ByRef filterExtensions As String _
, ByVal allowMultiFiles As Boolean) As Collection
Dim ofName As OPENFILENAME
Dim resultPaths As New Collection
Dim buffFiles As String
Dim buffFilter As String
Dim temp As String
'
With ofName
On Error Resume Next
Dim app As Object: Set app = Application
.hwndOwner = app.Hwnd
On Error GoTo 0
'
.lStructSize = LenB(ofName)
If LenB(filterExtensions) = 0 Then
buffFilter = "All Files (*.*)" & vbNullChar & "*.*"
Else
temp = Replace(filterExtensions, ",", ";")
buffFilter = filterDesc & " (" & temp & ")" & vbNullChar & temp
End If
buffFilter = buffFilter & vbNullChar & vbNullChar
.lpstrFilter = StrPtr(buffFilter)
'
.nMaxFile = &H100000
buffFiles = VBA.Space$(.nMaxFile)
.lpstrFile = StrPtr(buffFiles)
.lpstrInitialDir = StrPtr(initialPath)
.lpstrTitle = StrPtr(dialogTitle)
'
Const OFN_HIDEREADONLY As Long = &H4&
Const OFN_ALLOWMULTISELECT As Long = &H200&
Const OFN_PATHMUSTEXIST As Long = &H800&
Const OFN_FILEMUSTEXIST As Long = &H1000&
Const OFN_EXPLORER As Long = &H80000
'
.flags = OFN_HIDEREADONLY Or OFN_PATHMUSTEXIST Or OFN_FILEMUSTEXIST
If allowMultiFiles Then
.flags = .flags Or OFN_ALLOWMULTISELECT Or OFN_EXPLORER
End If
End With
'
Do
Const FNERR_BUFFERTOOSMALL As Long = &H3003&
Dim mustRetry As Boolean: mustRetry = False
Dim i As Long
Dim j As Long
'
If GetOpenFileNameW(ofName) Then
i = InStr(1, buffFiles, vbNullChar)
temp = Left$(buffFiles, i - 1)
'
If allowMultiFiles Then
j = InStr(i + 1, buffFiles, vbNullChar)
If j = i + 1 Then 'Single file selected
resultPaths.Add temp
Else
temp = BuildPath(temp, vbNullString) 'Parent folder
Do
resultPaths.Add temp & Mid$(buffFiles, i + 1, j - i)
i = j
j = InStr(i + 1, buffFiles, vbNullChar)
Loop Until j = i + 1
End If
Else
resultPaths.Add temp
End If
ElseIf CommDlgExtendedError() = FNERR_BUFFERTOOSMALL Then
Dim b() As Byte: b = LeftB$(buffFiles, 4)
'
If b(3) And &H80 Then
mustRetry = (MsgBox("Try selecting fewer files" _
, vbExclamation + vbRetryCancel _
, "Insufficient memory") = vbRetry)
Else
With ofName
.nMaxFile = b(3)
For i = 2 To 0 Step -1
.nMaxFile = .nMaxFile * &H100& + b(i)
Next i
buffFiles = VBA.Space$(.nMaxFile)
.lpstrFile = StrPtr(buffFiles)
End With
MsgBox "Did not expect so many files. Please select again!" _
, vbInformation, "Repeat selection"
mustRetry = True
End If
End If
Loop Until Not mustRetry
Set BrowseFilesAPI = resultPaths
End Function
#End If
'*******************************************************************************
'Returns a folder path by using a FolderPicker FileDialog
'*******************************************************************************
Public Function BrowseForFolder(Optional ByRef initialPath As String _
, Optional ByRef dialogTitle As String) As String
#If Mac Then
'If user has not accesss [initialPath] previously, will be prompted by
'Mac OS to Grant permission to directory
If LenB(initialPath) > 0 Then
If Not Right(initialPath, 1) = Application.PathSeparator Then
initialPath = initialPath & Application.PathSeparator
End If
Dir initialPath, Attributes:=vbDirectory
End If
Dim retPath
If LenB(dialogTitle) = 0 Then dialogTitle = "Choose Foldler"
retPath = MacScript("choose folder with prompt """ & dialogTitle & """ as string")
If Len(retPath) > 0 Then
retPath = MacScript("POSIX path of """ & retPath & """")
If LenB(retPath) > 0 Then
BrowseForFolder = retPath
End If
End If
#ElseIf Windows Then
'In case reference to Microsoft Office X.XX Object Library is missing
Const dialogTypeFolderPicker As Long = 4 'msoFileDialogFolderPicker
Const actionButton As Long = -1
'
With Application.FileDialog(dialogTypeFolderPicker)
If LenB(dialogTitle) > 0 Then .Title = dialogTitle
If LenB(initialPath) > 0 Then .InitialFileName = initialPath
If LenB(.InitialFileName) = 0 Then
Dim app As Object: Set app = Application 'Needs to be late-binded
Select Case Application.Name
Case "Microsoft Excel": .InitialFileName = GetLocalPath(app.ThisWorkbook.Path, , True)
Case "Microsoft Word": .InitialFileName = GetLocalPath(app.ThisDocument.Path, , True)
End Select
End If
If .Show = actionButton Then
.InitialFileName = .SelectedItems.Item(1)
BrowseForFolder = .InitialFileName
End If
End With
#End If
End Function
'*******************************************************************************
'Combines a folder path with a file/folder name or an incomplete path (ex. \a\b)
'*******************************************************************************
Public Function BuildPath(ParamArray PathComponents() As Variant) As String
BuildPath = FixPathSeparators(Join(PathComponents, PATH_SEPARATOR))
End Function
'*******************************************************************************
'Converts a text between 2 page codes
'*******************************************************************************
#If Mac Then
Public Function ConvertText(ByRef textToConvert As String _
, ByVal toCode As PageCode _
, ByVal fromCode As PageCode _
, Optional ByVal persistDescriptor As Boolean = False) As String
#Else
Public Function ConvertText(ByRef textToConvert As String _
, ByVal toCode As PageCode _
, ByVal fromCode As PageCode) As String
#End If
If toCode = fromCode Then
ConvertText = textToConvert
Exit Function
End If
#If Mac Then
Dim inBytesLeft As LongPtr: inBytesLeft = LenB(textToConvert)
Dim outBytesLeft As LongPtr: outBytesLeft = inBytesLeft * 4
Dim buffer As String: buffer = Space$(CLng(inBytesLeft) * 2)
Dim inBuf As LongPtr: inBuf = StrPtr(textToConvert)
Dim outBuf As LongPtr: outBuf = StrPtr(buffer)
Dim cd As LongPtr
Dim cdKey As String: cdKey = fromCode & "_" & toCode
Dim cdFound As Boolean
'
On Error Resume Next
cd = m_conversionDescriptors(cdKey)
cdFound = (cd <> 0)
On Error GoTo 0
If Not cdFound Then
cd = iconv_open(StrPtr(PageCodeToText(toCode)) _
, StrPtr(PageCodeToText(fromCode)))
If persistDescriptor Then m_conversionDescriptors.Add cd, cdKey
End If
If iconv(cd, inBuf, inBytesLeft, outBuf, outBytesLeft) <> -1 Then
ConvertText = LeftB$(buffer, LenB(buffer) - CLng(outBytesLeft))
End If
If Not (cdFound Or persistDescriptor) Then iconv_close cd
#Else
If toCode = codeUTF16LE Then
ConvertText = EncodeToUTF16LE(textToConvert, fromCode)
ElseIf fromCode = codeUTF16LE Then
ConvertText = EncodeFromUTF16LE(textToConvert, toCode)
Else
ConvertText = EncodeFromUTF16LE( _
EncodeToUTF16LE(textToConvert, fromCode), toCode)
End If
#End If
End Function
#If Mac Then
Public Sub ClearConversionDescriptors()
If m_conversionDescriptors.Count = 0 Then Exit Sub
Dim v As Variant
'
For Each v In m_conversionDescriptors
iconv_close v
Next v
Set m_conversionDescriptors = Nothing
End Sub
Private Function PageCodeToText(ByVal pc As PageCode) As String
Dim result As String
Select Case pc
Case codeUTF8: result = "UTF-8"
Case codeUTF16LE: result = "UTF-16LE"
Case codeUTF16BE: result = "UTF-16BE"
Case codeUTF32LE: result = "UTF-32LE"
Case codeUTF32BE: result = "UTF-32BE"
End Select
PageCodeToText = StrConv(result, vbFromUnicode)
End Function
#Else
Private Function EncodeToUTF16LE(ByRef textToConvert As String _
, ByVal fromCode As PageCode) As String
Dim charCount As Long
charCount = MultiByteToWideChar(fromCode, 0, StrPtr(textToConvert) _
, LenB(textToConvert), 0, 0)
If charCount = 0 Then Exit Function
EncodeToUTF16LE = Space$(charCount)
MultiByteToWideChar fromCode, 0, StrPtr(textToConvert) _
, LenB(textToConvert), StrPtr(EncodeToUTF16LE), charCount
End Function
Private Function EncodeFromUTF16LE(ByRef textToConvert As String _
, ByVal toCode As PageCode) As String
Dim byteCount As Long
byteCount = WideCharToMultiByte(toCode, 0, StrPtr(textToConvert) _
, Len(textToConvert), 0, 0, 0, 0)
If byteCount = 0 Then Exit Function
'
EncodeFromUTF16LE = Space$((byteCount + 1) \ 2)
If byteCount Mod 2 = 1 Then
EncodeFromUTF16LE = LeftB$(EncodeFromUTF16LE, byteCount)
End If
WideCharToMultiByte toCode, 0, StrPtr(textToConvert), Len(textToConvert) _
, StrPtr(EncodeFromUTF16LE), byteCount, 0, 0
End Function
#End If
'*******************************************************************************
'Copies a file. Overwrites existing files unless 'failIfExists' is set to True
'Note that VBA.FileCopy does not copy opened files on Windows but it does on Mac
'If the destination file already exists and 'failIfExists' is set to False
' then this method must be able to overwrite the destination file. Rather than
' failing and then trying again with attribute set to vbNormal this method
' sets the attribute for the destination path to vbNormal before copying.
' This is slightly slower than just copying directly but far outperforms two
' copy operations in the case where the first one fails and the second one is
' done after setting the destination file attribute to vbNormal.
'*******************************************************************************
Public Function CopyFile(ByRef sourcePath As String _
, ByRef destinationPath As String _
, Optional ByVal failIfExists As Boolean = False) As Boolean
If LenB(sourcePath) = 0 Then Exit Function
If LenB(destinationPath) = 0 Then Exit Function
'
#If Mac Then
If failIfExists Then If IsFile(destinationPath) Then Exit Function
'
On Error Resume Next
SetAttr destinationPath, vbNormal 'Too costly to do after Copy fails
Err.Clear 'Ignore any errors raised by 'SetAttr'
FileCopy sourcePath, destinationPath 'Copies opened files as well
CopyFile = (Err.Number = 0)
On Error GoTo 0
#Else
If Not failIfExists Then
On Error Resume Next
SetAttr destinationPath, vbNormal 'Costly to do after Copy fails
On Error GoTo 0
End If
CopyFile = CopyFileW(StrPtr(sourcePath), StrPtr(destinationPath), failIfExists)
#End If
End Function
'*******************************************************************************
'Copies a folder. Ability to copy all subfolders
'If 'failIfExists' is set to True then this method will fail if any file or
' subFolder already exists (including the main 'destinationPath')
'If 'ignoreFailedChildren' is set to True then the method continues to copy the
' remaining files and subfolders. This is useful when reverting a 'MoveFolder'
' call across different disk drives. Use this parameter with care
'*******************************************************************************
Public Function CopyFolder(ByRef sourcePath As String _
, ByRef destinationPath As String _
, Optional ByVal includeSubFolders As Boolean = True _
, Optional ByVal failIfExists As Boolean = False _
, Optional ByVal ignoreFailedChildren As Boolean = False) As Boolean
If Not IsFolder(sourcePath) Then Exit Function
If Not CreateFolder(destinationPath, failIfExists) Then Exit Function
'
Dim fixedSrc As String: fixedSrc = BuildPath(sourcePath, vbNullString)
Dim fixedDst As String: fixedDst = BuildPath(destinationPath, vbNullString)
'
If includeSubFolders Then
Dim subFolderPath As Variant
Dim newFolderPath As String
'
For Each subFolderPath In GetFolders(fixedSrc, True, True, True)
newFolderPath = Replace(subFolderPath, fixedSrc, fixedDst)
If Not CreateFolder(newFolderPath, failIfExists) Then
If Not ignoreFailedChildren Then Exit Function
End If
Next subFolderPath
End If
'
Dim filePath As Variant
Dim newFilePath As String
'
For Each filePath In GetFiles(fixedSrc, includeSubFolders, True, True)
newFilePath = Replace(filePath, fixedSrc, fixedDst)
If Not CopyFile(CStr(filePath), newFilePath, failIfExists) Then
If Not ignoreFailedChildren Then Exit Function
End If
Next filePath
'
CopyFolder = True
End Function
'*******************************************************************************
'Creates a folder including parent folders if needed
'*******************************************************************************
Public Function CreateFolder(ByRef folderPath As String _
, Optional ByVal failIfExists As Boolean = False) As Boolean
If IsFolder(folderPath) Then
CreateFolder = Not failIfExists
Exit Function
End If
'
Dim fullPath As String
'
fullPath = BuildPath(folderPath, vbNullString)
fullPath = Left$(fullPath, Len(fullPath) - 1) 'Removing trailing separator
'
Dim sepIndex As Long
Dim collFoldersToCreate As New Collection
Dim v As Variant
'
'Note that the same outcome could be achieved via recursivity but this
' approach avoids adding extra stack frames to the call stack
collFoldersToCreate.Add fullPath
Do
sepIndex = InStrRev(fullPath, PATH_SEPARATOR)
If sepIndex < 3 Then Exit Do
'
fullPath = Left$(fullPath, sepIndex - 1)
If IsFolder(fullPath) Then Exit Do
collFoldersToCreate.Add fullPath, Before:=1
Loop
On Error Resume Next
For Each v In collFoldersToCreate
'MkDir does not support all Unicode characters, unlike FSO
#If Mac Then
MkDir v
#Else
GetFSO.CreateFolder v
#End If
If Err.Number <> 0 Then Exit For
Next v
CreateFolder = (Err.Number = 0)
On Error GoTo 0
End Function
'*******************************************************************************
'Deletes a file only. Does not support wildcards * ?
'*******************************************************************************
Public Function DeleteFile(ByRef filePath As String) As Boolean
If LenB(filePath) = 0 Then Exit Function
If Not IsFile(filePath) Then Exit Function 'Avoid 'Kill' on folder
'
On Error Resume Next
#If Windows Then
GetFSO.DeleteFile filePath, True
DeleteFile = (Err.Number = 0)
If DeleteFile Then Exit Function
Err.Clear
#End If
SetAttr filePath, vbNormal 'Too costly to do after failing Kill
Err.Clear
Kill filePath
DeleteFile = (Err.Number = 0)
On Error GoTo 0
'
#If Windows Then
If Not DeleteFile Then DeleteFile = CBool(DeleteFileW(StrPtr(filePath)))
#End If
End Function
'*******************************************************************************
'Deletes a folder
'If the 'deleteContents' parameter is set to True then all files/folders inside
' all subfolders will be deleted before attempting to delete the main folder.
' In this case no attempt is made to roll back any deleted files/folders in
' case the method fails (ex. after deleting some files/folders the method
' cannot delete a particular file that is locked/open and so the method stops
' and returns False without rolling back the already deleted files/folders)
'*******************************************************************************
Public Function DeleteFolder(ByRef folderPath As String _
, Optional ByVal deleteContents As Boolean = False _
, Optional ByVal failIfMissing As Boolean = False) As Boolean
If LenB(folderPath) = 0 Then Exit Function
'
If Not IsFolder(folderPath) Then
DeleteFolder = Not failIfMissing
Exit Function
End If
'
On Error Resume Next
RmDir folderPath 'Assume the folder is empty
DeleteFolder = (Err.Number = 0)
If DeleteFolder Then Exit Function
'
#If Windows Then
Err.Clear
GetFSO.DeleteFolder folderPath, True
DeleteFolder = (Err.Number = 0)
If DeleteFolder Then Exit Function
#End If
On Error GoTo 0
If Not deleteContents Then Exit Function
'
Dim collFolders As Collection
Dim i As Long
'
Set collFolders = GetFolders(folderPath, True, True, True)
For i = collFolders.Count To 1 Step -1 'From bottom to top level
If Not DeleteBottomMostFolder(collFolders.Item(i)) Then Exit Function
Next i
'
DeleteFolder = DeleteBottomMostFolder(folderPath)
End Function
'*******************************************************************************
'Utility for 'DeleteFolder'
'Deletes a folder that can contain files but does NOT contain any other folders
'*******************************************************************************
Private Function DeleteBottomMostFolder(ByRef folderPath As String) As Boolean
Dim fixedPath As String: fixedPath = BuildPath(folderPath, vbNullString)
Dim filePath As Variant
'
On Error Resume Next
Kill fixedPath 'Try to batch delete all files (if any)
Err.Clear 'Kill fails if there are no files so ignore any error
RmDir fixedPath 'Try to delete folder
If Err.Number = 0 Then
DeleteBottomMostFolder = True
Exit Function
End If
On Error GoTo 0
'
For Each filePath In GetFiles(fixedPath, False, True, True)
If Not DeleteFile(CStr(filePath)) Then Exit Function
Next filePath
'
On Error Resume Next
RmDir fixedPath
DeleteBottomMostFolder = (Err.Number = 0)
On Error GoTo 0
'
#If Windows Then