From 56eb52738c9162a2937a0353a53a4a17ebd5e443 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Gerdes" <jeremy.gerdes@navy.mil> Date: Thu, 26 Jan 2017 09:16:20 -0500 Subject: [PATCH 1/6] File and Folder actions build directory tree, get relative path accepting '..\'as parent folder --- VB_FileFolder.bas | 107 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 VB_FileFolder.bas diff --git a/VB_FileFolder.bas b/VB_FileFolder.bas new file mode 100644 index 0000000..b43d990 --- /dev/null +++ b/VB_FileFolder.bas @@ -0,0 +1,107 @@ +Attribute VB_Name = "VB_FileFolder" +Option Explicit +'Authored 2015-2017 by Jeremy Dean Gerdes <jeremy.gerdes@navy.mil> + 'Public Domain in the United States of America, + 'any international rights are relinquished under CC0 1.0 <https://creativecommons.org/publicdomain/zero/1.0/legalcode> + 'http://www.copyright.gov/title17/ + 'In accrordance with 17 U.S.C. � 105 This work is 'noncopyright' or in the 'public domain' + 'Subject matter of copyright: United States Government works + 'protection under this title is not available for + 'any work of the United States Government, but the United States + 'Government is not precluded from receiving and holding copyrights + 'transferred to it by assignment, bequest, or otherwise. + 'as defined by 17 U.S.C � 101 + '... + 'A �work of the United States Government� is a work prepared by an + 'officer or employee of the United States Government as part of that + 'person�s official duties. + '... +Public Function BuildDir(strPath) As Boolean + On Error Resume Next + Dim fso As Object ' As Scripting.FileSystemObject + Dim arryPaths As Variant + Dim strBuiltPath As String, intDir As Integer, fRestore As Boolean: fRestore = False + If Left(strPath, 2) = "\\" Then + strPath = Right(strPath, Len(strPath) - 2) + fRestore = True + End If + Set fso = CreateObject("Scripting.FileSystemObject") ' New Scripting.FileSystemObject + arryPaths = Split(strPath, "\") + 'Restore Server file path + If fRestore Then + arryPaths(0) = "\\" & arryPaths(0) + End If + For intDir = 0 To UBound(arryPaths) + strBuiltPath = strBuiltPath & arryPaths(intDir) + If Not fso.FolderExists(strBuiltPath) Then + fso.CreateFolder strBuiltPath + End If + strBuiltPath = strBuiltPath & "\" + Next + BuildDir = (Err.Number = 0) 'True if no errors +End Function + +Public Function GetRelativePathViaParentAlternateRoot(ByVal strRootPath As String, ByVal strDestination As String, Optional ByRef intParentCount As Integer) + If Left(strDestination, 3) = "..\" Then + intParentCount = intParentCount + 1 + strRootPath = Left(strRootPath, InStrRev(strRootPath, "\") - 1) + strDestination = Right(strDestination, Len(strDestination) - 3) + GetRelativePathViaParentAlternateRoot = GetRelativePathViaParentAlternateRoot(strRootPath, strDestination, intParentCount) + ElseIf Left(strDestination, 1) = "\" And Not (Left(strDestination, 2) = "\\") Then + strDestination = Right(strDestination, Len(strDestination) - 1) + ElseIf Right(strDestination, 1) = "\" Then + strDestination = Left(strDestination, Len(strDestination) - 1) + End If + If intParentCount <> -1 Then + GetRelativePathViaParentAlternateRoot = StripTrailingBackSlash(strRootPath) & "\" & strDestination + End If + intParentCount = -1 +End Function + +Public Function GetRelativePathViaParent(Optional ByVal strPath) +'Usage for up 2 dirs is GetRelativePathViaParent("..\..\Destination") +Dim strCurrentPath As String, strVal As String +Dim oThisApplication As Object: Set oThisApplication = Application +Dim fIsServerPath As Boolean: fIsServerPath = False +Dim aryCurrentFolder As Variant, aryParentPath As Variant + Select Case True + Case InStrRev(oThisApplication.Name, "Excel") > 0 + strCurrentPath = oThisApplication.ThisWorkbook.Path + Case InStrRev(oThisApplication.Name, "Access") > 0 + strCurrentPath = oThisApplication.CurrentProject.Path + End Select + If Left(strCurrentPath, 2) = "\\" Then + strCurrentPath = Right(strCurrentPath, Len(strCurrentPath) - 2) + fIsServerPath = True + End If + aryCurrentFolder = Split(strCurrentPath, "\") + If IsMissing(strPath) Then + strPath = vbNullString + End If + aryParentPath = Split(strPath, "..\") + If fIsServerPath Then + aryCurrentFolder(0) = "\\" & aryCurrentFolder(0) + End If + Dim intDir As Integer, intParentCount As Integer + If UBound(aryParentPath) = -1 Then + intParentCount = 0 + Else + intParentCount = UBound(aryParentPath) + End If + For intDir = 0 To UBound(aryCurrentFolder) - intParentCount + strVal = strVal & aryCurrentFolder(intDir) & "\" + Next + strVal = StripTrailingBackSlash(strVal) + If IsArrayAllocated(aryParentPath) Then + strVal = strVal & "\" & aryParentPath(UBound(aryParentPath)) + End If + GetRelativePathViaParent = strVal +End Function + +Public Function StripTrailingBackSlash(ByRef strPath As String) + If Right(strPath, 1) = "\" Then + StripTrailingBackSlash = Left(strPath, Len(strPath) - 1) + Else + StripTrailingBackSlash = strPath + End If +End Function From 19c9da3a29a4187ebe9222b33b0a4bc49a69c6dd Mon Sep 17 00:00:00 2001 From: "Jeremy D. Gerdes" <jeremy.gerdes@navy.mil> Date: Thu, 26 Jan 2017 09:17:20 -0500 Subject: [PATCH 2/6] Lisense clarification --- VB_FileFolder.bas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VB_FileFolder.bas b/VB_FileFolder.bas index b43d990..98cc874 100644 --- a/VB_FileFolder.bas +++ b/VB_FileFolder.bas @@ -2,7 +2,7 @@ Attribute VB_Name = "VB_FileFolder" Option Explicit 'Authored 2015-2017 by Jeremy Dean Gerdes <jeremy.gerdes@navy.mil> 'Public Domain in the United States of America, - 'any international rights are relinquished under CC0 1.0 <https://creativecommons.org/publicdomain/zero/1.0/legalcode> + 'any international rights are waived through the CC0 1.0 Universal public domain dedication <https://creativecommons.org/publicdomain/zero/1.0/legalcode> 'http://www.copyright.gov/title17/ 'In accrordance with 17 U.S.C. � 105 This work is 'noncopyright' or in the 'public domain' 'Subject matter of copyright: United States Government works From d8ec7717d410e9afa2532d08097103add84a8ff3 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Gerdes" <jeremy.gerdes@navy.mil> Date: Thu, 26 Jan 2017 09:18:46 -0500 Subject: [PATCH 3/6] Renamed VBALib_FolderUtilis.bas --- VB_FileFolder.bas => VBALib_FolderUtilis.bas | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename VB_FileFolder.bas => VBALib_FolderUtilis.bas (100%) diff --git a/VB_FileFolder.bas b/VBALib_FolderUtilis.bas similarity index 100% rename from VB_FileFolder.bas rename to VBALib_FolderUtilis.bas From adc9960ecc9aa5ecda10dc6a95b7208deb25fa78 Mon Sep 17 00:00:00 2001 From: Hans Voll <33080008+hvoll44@users.noreply.github.com> Date: Fri, 1 May 2020 20:07:37 -0400 Subject: [PATCH 4/6] Update VBALib_ArrayUtils.bas Fixed external library call to be 64 bit compatible --- VBALib_ArrayUtils.bas | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/VBALib_ArrayUtils.bas b/VBALib_ArrayUtils.bas index 958323a..c2e1019 100644 --- a/VBALib_ArrayUtils.bas +++ b/VBALib_ArrayUtils.bas @@ -5,8 +5,11 @@ Attribute VB_Name = "VBALib_ArrayUtils" Option Explicit -Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ - (dest As Any, source As Any, ByVal bytes As Long) +#If VBA7 Then + Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, source As Any, ByVal bytes As Long) +#Else + Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, source As Any, ByVal bytes As Long) +#End If Private Const NORMALIZE_LBOUND = 1 From ea2daeca7f7e116db87915f87876afc7892af23a Mon Sep 17 00:00:00 2001 From: "Jeremy D. Gerdes" <seakintruth@gmail.com> Date: Fri, 1 May 2020 22:33:18 -0400 Subject: [PATCH 5/6] Update VBALib_VERSION.bas --- VBALib_VERSION.bas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VBALib_VERSION.bas b/VBALib_VERSION.bas index 99c9125..91117aa 100644 --- a/VBALib_VERSION.bas +++ b/VBALib_VERSION.bas @@ -1,4 +1,4 @@ Attribute VB_Name = "VBALib_VERSION" -' Common VBA Library, version 2014-06-16.1 +' Common VBA Library, version 2020-05-01.1 ' The other modules in this workbook whose names start with "VBALib_" provide ' commonly-used functions and types that are lacking in the VBA language. From 99a8702cd54136968d259bb97cc55b72e88d6e85 Mon Sep 17 00:00:00 2001 From: Hans Voll <33080008+hvoll44@users.noreply.github.com> Date: Sat, 23 May 2020 20:05:14 -0400 Subject: [PATCH 6/6] Create ListTableAndFieldProperties Module to list all properties for all fields in an access database. --- ListTableAndFieldProperties | 375 ++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 ListTableAndFieldProperties diff --git a/ListTableAndFieldProperties b/ListTableAndFieldProperties new file mode 100644 index 0000000..7890f05 --- /dev/null +++ b/ListTableAndFieldProperties @@ -0,0 +1,375 @@ +Option Compare Database +Option Explicit + +Public Sub ListAllTablesProperties(Optional fWriteToFile As Boolean = True, Optional fOpenFile As Boolean = True) +On Error GoTo HandleError + Dim db As Database + Dim tdf As TableDef + Dim x As Double + Set db = CurrentDb + + 'text file header + Dim strLine As String + strLine = _ + "Table Name,Table Validation Rule,Table Validation Text," & _ + "Table Attributes,Table Date Created,Table Indexes Count, Table Indexes, " & _ + "Table Record Count,Table Properties Count,Table Properties," & _ + "FieldName,Field Allow Zero Length, Field Attributes, Field Default Value " & _ + "Field Required,Field Size, Field Type, Field Validation Rule, Field Validation Text, Field Count, Field Properties" & _ + "Field Description,Field DataType Name, Field DataType,Field DataSize" + + 'Write text file body + For Each tdf In db.TableDefs + If Left(tdf.Name, 4) <> "MSys" Then ' Don't enumerate the system tables + Dim fld As DAO.Field + Dim strTbfPrefix As String + strTbfPrefix = HandleCsvColumn(tdf.Name) & "," + strTbfPrefix = strTbfPrefix & HandleCsvColumn(tdf.ValidationRule) & "," + strTbfPrefix = strTbfPrefix & HandleCsvColumn(tdf.ValidationText) & "," + strTbfPrefix = strTbfPrefix & HandleCsvColumn(tdf.Attributes) & "," + strTbfPrefix = strTbfPrefix & HandleCsvColumn(tdf.DateCreated) & "," + strTbfPrefix = strTbfPrefix & HandleCsvColumn(tdf.Indexes.Count) & "," + If tdf.Indexes.Count = 0 Then + strTbfPrefix = strTbfPrefix & HandleCsvColumn(vbNullString) & "," + Else + Dim strIndexValue As String + strIndexValue = "|" + Dim varIndex As DAO.Index + For Each varIndex In tdf.Indexes + strIndexValue = strIndexValue & "Name=" & varIndex.Name & ";" + strIndexValue = strIndexValue & "Clustered=" & varIndex.Clustered & ";" + strIndexValue = strIndexValue & "DistinctCount=" & varIndex.DistinctCount & ";" + strIndexValue = strIndexValue & "Field Count=" & varIndex.Fields.Count & ";" + strIndexValue = strIndexValue & "Foreign=" & varIndex.Foreign & ";" + strIndexValue = strIndexValue & "IgnoreNulls=" & varIndex.IgnoreNulls & ";" + strIndexValue = strIndexValue & "Primary=" & varIndex.Primary & ";" + strIndexValue = strIndexValue & "Properties Count=" & varIndex.Properties.Count & ";" + strIndexValue = strIndexValue & "Required=" & varIndex.Required & ";" + strIndexValue = strIndexValue & "Unique=" & varIndex.Unique & ";" + strIndexValue = strIndexValue & "|" + Next + strTbfPrefix = strTbfPrefix & HandleCsvColumn(strIndexValue) & "," + End If + + strTbfPrefix = strTbfPrefix & HandleCsvColumn(tdf.RecordCount) & "," + strTbfPrefix = strTbfPrefix & HandleCsvColumn(tdf.Properties.Count) & "," + strTbfPrefix = strTbfPrefix & HandleCsvColumn(GetPropertyValue(tdf.Properties)) + For Each fld In tdf.Fields + Dim strFldPrefix As String + strFldPrefix = HandleCsvColumn(fld.Name) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.AllowZeroLength) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.Attributes) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.DefaultValue) & "," +' strFldPrefix = strFldPrefix & HandleCsvColumn(fld.FieldSize) & "," +' strFldPrefix = strFldPrefix & HandleCsvColumn(fld.OriginalValue) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.Required) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.Size) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.Type) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.ValidationRule) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.ValidationText) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(fld.Properties.Count) & "," + strFldPrefix = strFldPrefix & HandleCsvColumn(GetPropertyValue(fld.Properties)) + strLine = strLine & vbCrLf & strTbfPrefix & "," & strFldPrefix + Debug.Print strTbfPrefix & "," & strFldPrefix + Next fld + End If + Next tdf + 'Create text file + If fWriteToFile Then + Dim strFilename As String + strFilename = RemoveFilenameExtention(CurrentProject.Name) & "_ListAllTableProperties.csv" + SaveTextToFile strLine, strFilename, fOpenNewFile:=fOpenFile + End If +ExitSub: +Exit Sub +HandleError: + Select Case Err.Number + Case 3270 + strLine = strLine & _ + HandleCsvColumn("[Err:" & Err.Number & ":" & Err.Description & "]") & "," + Resume Next + Case Else + Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext + Resume + 'GoTo ExitSub + End Select +End Sub + +'Finds all tables, field names, and field descriptions, and writes output to a text file +Public Sub ListAllTableFieldDescTypeSize(Optional fWriteToFile As Boolean = True, Optional fOpenFile As Boolean = True) +On Error GoTo HandleError + Dim db As Database + Dim tdf As TableDef + Dim x As Double + Set db = CurrentDb + + 'text file header + Dim strDescription As String + strDescription = "Table,Field,Description,DataType Name,DataType,DataSize" + + Debug.Print (strDescription) + + 'Write text file body + For Each tdf In db.TableDefs + If Left(tdf.Name, 4) <> "MSys" Then ' Don't enumerate the system tables + For x = 0 To tdf.Fields.Count - 1 + Dim strDataType As String + strDataType = GetPropertyValue(tdf.Fields(x).Properties("Type")) + 'Ignore errors in case no description exists TODO: imporve error handler or find a way to test if "Description" property exists w/o throwing an error + strDescription = strDescription & vbCrLf + strDescription = strDescription & HandleCsvColumn(tdf.Name) & "," + strDescription = strDescription & HandleCsvColumn(tdf.Fields(x).Name) & "," + strDescription = strDescription & HandleCsvColumn(GetPropertyValue(tdf.Fields(x).Properties("Description"))) & "," + strDescription = strDescription & HandleCsvColumn(GetJetDataTypeEnumName(CLng(strDataType))) & "," + strDescription = strDescription & HandleCsvColumn((strDataType)) & "," + strDescription = strDescription & HandleCsvColumn(GetPropertyValue(tdf.Fields(x).Properties("Size"))) + Next x + End If + Next tdf + Debug.Print strDescription + 'Create text file + If fWriteToFile Then + Dim strFilename As String + strFilename = RemoveFilenameExtention(CurrentProject.Name) & "_FieldDescTypesSizes.csv" + SaveTextToFile strDescription, strFilename, fOpenNewFile:=fOpenFile + End If +ExitSub: +Exit Sub +HandleError: + Select Case Err.Number + Case 3270 + strDescription = strDescription & _ + HandleCsvColumn("[Err:" & Err.Number & ":" & Err.Description & "]") & "," + Resume Next + Case Else + Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext + Resume + 'GoTo ExitSub + End Select +End Sub + +Private Function GetPropertyValue(prp As Variant, Optional intChildDepth As Integer = 0) As String +On Error GoTo HandleError + Dim strValue As String + strValue = vbNullString + Dim prpChild As Property + Select Case TypeName(prp) + Case "Property" + strValue = prp.Name & "=" + On Error Resume Next + strValue = strValue & prp.Value + On Error GoTo HandleError + Case "Properties" + Dim intProperty As Integer + For intProperty = 1 To prp.Count + strValue = strValue & prp(intProperty).Name & "=" + On Error Resume Next + strValue = strValue & prp(intProperty).Value + On Error GoTo HandleError + strValue = strValue & ";" + 'strValue = intChildDepth & ":" & strValue & ";" & GetPropertyValue(prp(intProperty), intChildDepth + 1) + Next intProperty + Case Else + strValue = prp.Value + End Select + GetPropertyValue = strValue +ExitSub: +Exit Function +HandleError: + Select Case Err.Number + Case 3270, 438 + strValue = Err.Description + Resume Next + Case Else + Debug.Print "Err:" & Err.Number & ":" & Err.Description + strValue = Err.Description + Resume Next + End Select +End Function + +Private Function HandleCsvColumn(ByVal strText As String) + strText = IIf(Left(strText, 1) = "=", "'" & strText, strText) + If Len(strText) > 0 Then + HandleCsvColumn = """" & Replace(strText, """", """""") & """" + End If +End Function + +Public Function RemoveFilenameExtention(strFilename) + RemoveFilenameExtention = Left(strFilename, InStrRev(strFilename, ".", , vbBinaryCompare) - 1) +End Function + +Public Function GetParentPath(strFilename) + GetParentPath = Left(strFilename, InStrRev(strFilename, "\", , vbBinaryCompare) - 1) +End Function + +Public Function FileExists(ByVal strPath As String) As Boolean +Dim FSO As Object + ' Note*: We used to use the vba.Dir function but using that function + ' will lock the folder and prevent it from being deleted. + Set FSO = CreateObject("Scripting.FileSystemObject") + FileExists = FSO.FileExists(strPath) + ' Clean up + Set FSO = Nothing +End Function + +Public Function FolderExists(ByVal strPath As String) As Boolean +Dim FSO As Object + ' Note*: We used to use the vba.Dir function but using that function + ' will lock the folder and prevent it from being deleted. + Set FSO = CreateObject("Scripting.FileSystemObject") + FolderExists = FSO.FolderExists(strPath) + ' Clean up + Set FSO = Nothing +End Function + +Public Sub SaveTextToFile( _ + strTextToPass As String, _ + strFilename As String, _ + Optional strPath As String = vbNullString, _ + Optional fOpenNewFile As Boolean = False _ +) + If strPath = vbNullString Then + strPath = CurrentProject.Path + Else + BuildDir strPath + End If + strTextToPass = Trim(strTextToPass) + 'save text to file + Dim strFullPath As String + strFullPath = strPath & "\" & RemoveForbiddenFilenameCharacters(strFilename) + Dim fCancel As Boolean + If FileExists(strFullPath) Then + If IsFileOpenLocked(strFullPath) Then + If MsgBox("File appears to be open, click OK after closing the file:" & vbCrLf & strFullPath & _ + vbCrLf & vbTab & "Cancel will stop attempts to write to the file", vbCritical + vbOKCancel, RemoveFilenameExtention(CurrentProject.Name)) = vbOK _ + Then + Debug.Print "User attests file is closed" + Else + Debug.Print "User canceled writing to file" + fCancel = True + End If + End If + End If + If Not fCancel Then + 'VBS Method + Dim FSO As Object + Set FSO = CreateObject("Scripting.FileSystemObject") + Dim oFile As Object + Set oFile = FSO.CreateTextFile(strFullPath, True) + oFile.WriteLine strTextToPass + + 'VBA method + 'Open strFullPath For Output As #1 + 'Write #1, strTextToPass + 'Close #1 + oFile.Close + + If fOpenNewFile Then + 'Open file + 'Shell "Notepad.exe " & CurrentProject.Path & "\" & strFileName, vbNormalFocus + OpenFileWithExplorer strFullPath + End If + 'cleanup + Set FSO = Nothing + Set oFile = Nothing + End If +End Sub + +Public Function GetJetDataTypeEnumName(intEnum As Long) + Select Case intEnum + Case 101 + GetJetDataTypeEnumName = "dbAttachment" + Case 16 + GetJetDataTypeEnumName = "dbBigInt" + Case 9 + GetJetDataTypeEnumName = "dbBinary" + Case 1 + GetJetDataTypeEnumName = "dbBoolean" + Case 2 + GetJetDataTypeEnumName = "dbByte" + Case 4 + GetJetDataTypeEnumName = "dbLong" + Case 10 + GetJetDataTypeEnumName = "bdText" + Case 7 + GetJetDataTypeEnumName = "dbDouble" + Case 12 + GetJetDataTypeEnumName = "dbMemo" + Case 8 + GetJetDataTypeEnumName = "dbDate" + Case 6 + GetJetDataTypeEnumName = "dbSingle" + + End Select +End Function + +Public Function BuildDir(strPath) As Boolean + On Error Resume Next + Dim FSO As Object ' As Scripting.FileSystemObject + Set FSO = CreateObject("Scripting.FileSystemObject") ' New Scripting.FileSystemObject + If Not FSO.FolderExists(strPath) Then + Err.Clear + Dim arryPaths As Variant + Dim strBuiltPath As String, intDir As Integer, fRestore As Boolean: fRestore = False + If Left(strPath, 2) = "\\" Then + strPath = Right(strPath, Len(strPath) - 2) + fRestore = True + End If + arryPaths = Split(strPath, "\") + 'Restore Server file path + If fRestore Then + arryPaths(0) = "\\" & arryPaths(0) + End If + For intDir = 0 To UBound(arryPaths) + strBuiltPath = strBuiltPath & arryPaths(intDir) + If Not FSO.FolderExists(strBuiltPath) Then + FSO.CreateFolder strBuiltPath + End If + strBuiltPath = strBuiltPath & "\" + Next + End If + BuildDir = (Err.Number = 0) 'True if no errors + Set FSO = Nothing +End Function + +Public Function RemoveForbiddenFilenameCharacters(strFilename As String) As String +'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx +'< (less than) +'> (greater than) +': (colon) +'" (double quote) +'/ (forward slash) +'\ (backslash) +'| (vertical bar or pipe) +'? (question mark) +'* (asterisk) +Dim strForbidden As Variant + For Each strForbidden In Array("/", "\", "|", ":", "*", "?", "<", ">", """") + strFilename = Replace(strFilename, strForbidden, "_") + Next + RemoveForbiddenFilenameCharacters = strFilename +End Function + +Public Sub OpenFileWithExplorer(ByRef strFilePath As String) + Dim wshShell + Set wshShell = CreateObject("WScript.Shell") + wshShell.Exec ("Explorer.exe " & strFilePath) + Set wshShell = Nothing +End Sub + +Function IsFileOpenLocked(FileName As String) As Boolean + Dim ff As Long, ErrNo As Long + + On Error Resume Next + ff = FreeFile() + Open FileName For Input Lock Read As #ff + Close ff + ErrNo = Err + On Error GoTo 0 + + Select Case ErrNo + Case 0: IsFileOpenLocked = False + Case 70: IsFileOpenLocked = True + Case Else: Error ErrNo + End Select +End Function