How can I use the FileSystemObject to "Copy and rename" - vba

Using the FileSystemObject in VB/VBA (or native VBA calls, I guess) how can I:
Copy folder
Rename folder
So, something like:
mFSO.CopyAndRename(targetFolder, copyDirectory, copyFolderName)
I have basically done this myself but I would much prefer a more clean method call such as the above (and the CopyFolder method). This seems like a lot of code and a lot of potential failure points...
'
''requires reference to Microsoft Scripting Runtime
Public Function CopyDirectory(ByVal p_copyDirectory As String, p_targetDirectory As String, Optional p_newName As String = "") As Boolean
CopyDirectory = False
Dim m_fso
Set m_fso = New FileSystemObject
Dim mFolder, mNewFolder
If Not Me.DoesPathExist(p_copyDirectory) Then
Exit Function
Else
On Error GoTo errHandler
Set mFolder = m_fso.GetFolder(p_copyDirectory)
mFolder.Copy p_targetDirectory, False
'rename if a "rename" arg is passed
If p_newName <> "" Then
If DoesPathExist(p_targetDirectory & mFolder.Name) Then
Set mNewFolder = m_fso.GetFolder(p_targetDirectory & mFolder.Name)
mNewFolder.Name = "test" & CStr(Rnd(9999))
Else
End If
End If
CopyDirectory = True
On Error GoTo 0
Exit Function
End If
errHandler:
Exit Function
End Function

There is actually a method on Scripting.FileSystemObject called CopyFolder. It can be used to do both the copy and rename in one step, as follows:
Dim objFSO As Object
Set objFSO = CreateObject("Scripting.FileSystemObject")
objFSO.copyFolder "C:\Path\to\source\folder", "C:\Path\to\destination\folder" true
I found the code here: http://vba-tutorial.com/copy-a-folder-and-all-of-its-contents/
Hope this answers your question.

My Fav: SHFileOperation API
This also gives you the visual presentation of Folders being moved.
Option Explicit
Private Declare Function SHFileOperation Lib "shell32.dll" _
Alias "SHFileOperationA" (lpFileOp As SHFILEOPSTRUCT) As Long
Const FO_COPY = &H2 '~~> Copy File/Folder
Const FOF_SILENT = &H4 '~~> Silent Copy
Private Type SHFILEOPSTRUCT
hwnd As Long
wFunc As Long
pFrom As String
pTo As String
fFlags As Integer
fAborted As Boolean
hNameMaps As Long
sProgress As String
End Type
Private Sub Sample()
Dim lresult As Long, lFlags As Long
Dim SHFileOp As SHFILEOPSTRUCT
With SHFileOp
'~~> For Copy
.wFunc = FO_COPY
.pFrom = "C:\Temp"
.pTo = "C:\Temp2\"
'~~> For Silent Copy
'.fFlags = FOF_SILENT
End With
lresult = SHFileOperation(SHFileOp)
'~~> SHFileOp.fAborted will be true if user presses cancel during operation
If lresult <> 0 Or SHFileOp.fAborted Then Exit Sub
MsgBox "Operation Complete", vbInformation, "File Operations"
End Sub
For renaming a folder, here is a one liner
Sub Sample()
Name "C:\Temp2" As "C:\Temp3"
End Sub

Posting this for reference in the future. Using syntax from this answer I fleshed out a class I'd been writing.
I've created a directory manager class in VBA which may be relevant to anyone coming here in the future.
Private m_fso As New FileSystemObject
'
''requires reference to Microsoft Scripting Runtime
Public Function CopyAndRenameDirectory(ByVal p_copyDirectory As String, p_targetDirectory As String, p_newName As String) As Boolean
'example
'p_copyDirectory = "C:\temp\myGoingToBeCopiedDir
'p_targetDirectory = "C:\Temp2"
'p_newName = "AwesomeDir"
'results:
'myGoingToBeCopiedDir --> C:\Temp2\AwesomeDir
CopyAndRenameDirectory = False
p_targetDirectory = p_targetDirectory & "\"
If Not Me.DoesPathExist(p_copyDirectory) Or Not Me.DoesPathExist(p_targetDirectory) Then
Exit Function
End If
On Error GoTo errHandler
m_fso.CopyFolder p_copyDirectory, p_targetDirectory & p_newName, True
On Error GoTo 0
Exit Function
errHandler:
If PRINT_DEBUG Then Debug.Print "Error in CopyAndRenameDirectory: " & Err.Description
Exit Function
End Function
Public Function CopyDirectory(ByVal p_copyDirectory As String, p_targetDirectory As String) As Boolean
'example
'p_copyDirectory = "C:\temp\myGoingToBeCopiedDir
'p_targetDirectory = "C:\Temp2"
'p_newName = ""
'results:
'myGoingToBeCopiedDir --> C:\Temp2\myGoingToBeCopiedDir
CopyDirectory = False
If Not Me.DoesPathExist(p_copyDirectory) Or Not Me.DoesPathExist(p_targetDirectory) Then
Exit Function
End If
p_targetDirectory = p_targetDirectory & "\"
On Error GoTo errHandler
m_fso.CopyFolder p_copyDirectory, p_targetDirectory, True
On Error GoTo 0
Exit Function
errHandler:
If PRINT_DEBUG Then Debug.Print "Error in CopyDirectory: " & Err.Description
Exit Function
End Function
Public Function CreateFolder(ByVal p_path As String) As Boolean
CreateFolder = True
If Me.DoesPathExist(p_path) Then
Exit Function
Else
On Error GoTo errHandler
m_fso.CreateFolder p_path ' could there be any error with this, like if the path is really screwed up?
Exit Function
End If
errHandler:
'MsgBox "A folder could not be created for the following path: " & path & ". Check the path name and try again."
CreateFolder = False
Exit Function
End Function
Public Function DoesPathExist(ByVal p_path As String) As Boolean
DoesPathExist = False
If m_fso.FolderExists(p_path) Then DoesPathExist = True
End Function

Related

Access VBA calling function does nothing when run

I have a macro that I'm trying to build in Access which will change the source table on a set of queries. Here is what I've built (based off already written code that I found on a forum):
Function ReplaceSources()
Call SourceQueries
End Function
Sub SourceQueries()
Call UpdateSource("YYYY_Count_of_items_by_floor", Building_Audit_2021, Building_Audit_YYYY)
End Sub
Sub UpdateSource(QueryName, CurrentSourceTable, NewSourceTable)
Dim strQryName, strCTbl, strNTbl, strCsql, strNsql As String
Dim defqry As DAO.QueryDef
strQryName = QueryName
strCTbl = CurrentSourceTable
strNTbl = NewSourceTable
Set defqry = CurrentDb.QueryDefs(strQryName)
strCsql = defqry.SQL
strNsql = Replace(strCsql, strCTbl, strNTbl)
defqry.SQL = strNsql
defqry.Close
End Sub
When I use the RunCode option in the macro builder using function name ReplaceSources(), nothing happens. I get no errors, I can step through the code with no issues, and adding Debug.Print lines throughout the function and subs does nothing. What is preventing this function from doing anything?
ETA:
Maybe it will help if I add the other two parts of code that I'm piecing together with this one. Before the above code, I run:
Function Copy_audit_table()
On Error GoTo Copy_audit_table_Err
Dim strPath As String
strPath = CurrentProject.FullName
DoCmd.TransferDatabase acImport, "Microsoft Access", strPath, acTable, "Building_Audit_2021", "Building_Audit_YYYY", True
DoCmd.CopyObject "", "YYYY_Count_of_items_by_floor", acQuery, "2021_Count_of_items_by_floor"
Copy_audit_table_Exit:
Exit Function
Copy_audit_table_Err:
MsgBox Error$
Resume Copy_audit_table_Exit
End Function
Then after the code in question, I run:
Function Copy_audit_table_rename()
On Error GoTo Copy_audit_table_rename_Err
Dim AuditYear As Variant
AuditYear = InputBox("Enter audit year (YYYY)")
Dim strPath As String
strPath = CurrentProject.FullName
DoCmd.Rename "Building_Audit_" & AuditYear, acTable, "Building_Audit_YYYY"
DoCmd.Rename AuditYear & "_Count_of_items_by_floor", acQuery, "YYYY_Count_of_items_by_floor"
Copy_audit_table_rename_Exit:
Exit Function
Copy_audit_table_rename_Err:
MsgBox Error$
Resume Copy_audit_table_rename_Exit
End Function
I'm not too familiar with VBA, so most of this is code that I found elsewhere that I was able to piece together. I know that I can use the macro builder to run each Function, but I really don't know any other ways. If there are any recommended tutorials that will help me code what I want to do, I'd like to read them.
You must execute the query for something to happen.
And skip the macro and the function. All you need is to call:
Sub UpdateSource(QueryName As String, CurrentSourceTable As String, NewSourceTable As String)
Dim strCsql As String
Dim defqry As DAO.QueryDef
Set defqry = CurrentDb.QueryDefs(QueryName)
strCsql = defqry.SQL
strNsql = Replace(strCsql, CurrentSourceTable, NewSourceTable)
defqry.SQL = strNsql
defqry.Execute
defqry.SQL = strCsql
defqry.Close
End Sub

VBA - Auto check/uncheck microsoft script run time

I had the following function that auto add Microsoft Script Runtime Reference to the Reference list. However, if the user already had included Microsoft script runtime, it will show error Name conflicts with existing module,project, object library.
How do I set an condition that auto adds Microsoft script runtime if it is not included in the reference and do nothing if it has already being added?
Private Function AddScriptingLibrary() As Boolean
Const GUID As String = "{420B2830-E718-11CF-893D-00A0C9054228}"
On Error GoTo errHandler
ThisWorkbook.VBProject.References.AddFromGuid GUID, 1, 0
AddScriptingLibrary = True
Exit Function
errHandler:
MsgBox Err.Description
End Function
You'll need to enumerate the references of the project first, in order to check if the reference is already present.
I've added a reference to Microsoft Visual Basic for Applications Extensibility 5.3
Option Explicit
Function AddScriptingLibrary()
Const GUID_Scripting = "{420B2830-E718-11CF-893D-00A0C9054228}"
Dim proj As VBIDE.VBProject
Dim ref As VBIDE.Reference
Dim ScriptingLibraryIsReferenced As Boolean
Set proj = ThisWorkbook.VBProject
For Each ref In proj.References
If ref.GUID = GUID_Scripting Then
ScriptingLibraryIsReferenced = True
AddScriptingLibrary = True
Exit Function
End If
Next ref
If Not ScriptingLibraryIsReferenced Then
On Error GoTo errHandler
proj.References.AddFromGuid GUID_Scripting, 1, 0
AddScriptingLibrary = True
Exit Function
errHandler:
MsgBox Err.Description
End If
End Function
EDIT this does the same, but without the early-bound reference to Visual Basic For Applications Extensibility 5.3 reference:
Option Explicit
Function AddScriptingLibrary()
Const GUID_Scripting = "{420B2830-E718-11CF-893D-00A0C9054228}"
Dim proj As Object 'VBIDE.VBProject
Dim ref As Object 'VBIDE.Reference
Dim ScriptingLibraryIsReferenced As Boolean
Set proj = ThisWorkbook.VBProject
For Each ref In proj.References
If ref.GUID = GUID_Scripting Then
ScriptingLibraryIsReferenced = True
AddScriptingLibrary = True
Exit Function
End If
Next ref
If Not ScriptingLibraryIsReferenced Then
On Error GoTo errHandler
proj.References.AddFromGuid GUID_Scripting, 1, 0
AddScriptingLibrary = True
Exit Function
errHandler:
MsgBox Err.Description
End If
End Function
But then, if you're happy with the down-sides of late-bound code, you don't even need the reference to Scripting.Runtime, because you can just use:
Option Explicit
Sub PrintDriveCount()
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
'Print the number of drives in the FileSystemObject
Debug.Print FSO.Drives.Count
End Function

it is posible to do " if error go to sub "

I need to write code that goes to a specific path and imports data from it,
then goes to another path and do the same.
I need that if path num 1 does not exist, it will jump direct to path num 2.
I wrote a sub for each path. there is a way to do something like:
if error goto sub ___ ?
Thanks in advance
Not directly, but you can do something like
On Error Goto error_sub1
and at the bottom of your function, write
error_sub1:
'ToDo - put your calling code here.
Elsewhere in you function you can switch the error handler to a different label:
On Error Goto error_sub2
and so on.
Try this:
Sub testSO()
On Error GoTo err
I=5/0
Exit Sub
err:
<your sub procedure here>
End Sub
Remember to include Exit Sub or else it will still run even without error!
Would it not be better to avoid the error in the first place and check whether the file exists before attempting to open it?
Sub Test()
Dim sFile1 As String
Dim sFile2 As String
Dim wrkBk As Workbook
On Error GoTo Error_Handler
sFile1 = "C:\Users\Desktop\MyFile1.xls"
sFile2 = "C:\Users\Desktop\MyFile2.xls"
If FileExists(sFile1) Then
Set wrkBk = Workbooks.Open(sFile1)
ElseIf FileExists(sFile2) Then
Set wrkBk = Workbooks.Open(sFile2)
Else
Err.Raise 513, , "File Not Found."
End If
wrkBk.Worksheets(1).Range("A1") = "Opened this file."
On Error GoTo 0
Fast_Exit:
'Any tidying up that needs doing.
Exit Sub
Error_Handler:
MsgBox Err.Description, vbExclamation + vbOKCancel, _
"Error: " & CStr(Err.Number)
Err.Clear
Resume Fast_Exit
End Sub
Public Function FileExists(ByVal FileName As String) As Boolean
Dim oFSO As Object
Set oFSO = CreateObject("Scripting.FileSystemObject")
FileExists = oFSO.FileExists(FileName)
End Function

Test whether a property name exists

I'm getting this error:
Run-time error '424' object required
when I try to run this code:
Sub SuperSaveAs()
Set objFSO = CreateObject("Scripting.FileSystemObject")
Dim pathName As String
Dim myFileName As String
If (ActiveDocument.CustomDocumentProperties("_CheckOutSrcUrl").Value = True) Then
pathName = ActiveDocument.CustomDocumentProperties("_CheckOutSrcUrl").Value
myFileName = pathName + ActiveWorkbook.Name
ActiveWorkbook.SaveAs Filename:= _
myFileName _
, FileFormat:=xlOpenXMLWorkbookMacroEnabled, CreateBackup:=False
Else
MsgBox "_CheckOutSrcUrl is missing"
End If
End Sub
This macro is connected with a button in Excel. The macro checks if the custom document property exists. If the custom document property exists the macro should save the file to the Value of _CheckOutSrcUrl (SharePoint Directory).
How can I fix the error?
You cannot use the above method to test whether a property name exists or not. There are two apparent approaches, and these are not my own personal answers:
Use a loop to examine all the property names and see if "_CheckOutSrcUrl" gets found. See https://answers.microsoft.com/en-us/office/forum/office_2007-word/using-customdocumentproperties-with-vba/91ef15eb-b089-4c9b-a8a7-1685d073fb9f
Use VBA error detection to see if the property "_CheckOutSrcUrl" exists. See http://www.vbaexpress.com/forum/showthread.php?15366-Solved-CustomDocumentProperties-Problem
A snippet example of #1 adapted to your code - would be best in a function:
Dim propertyExists As Boolean
Dim prop As DocumentProperty
propertyExists = False
For Each prop In ActiveDocument.CustomDocumentProperties
If prop.Name = "_CheckOutSrcUrl" Then
propertyExists = True
Exit For
End If
Next prop
A snippet example of #2 adapted to your code:
Dim propertyExists As Boolean
Dim tempObj
On Error Resume Next
Set tempObj = ActiveDocument.CustomDocumentProperties.Item("_CheckOutSrcUrl")
propertyExists = (Err = 0)
On Error Goto 0
Based on #Cybermike:
Function propertyExists(propName) As Boolean
Dim tempObj
On Error Resume Next
Set tempObj = ActiveDocument.CustomDocumentProperties.Item(propName)
propertyExists = (Err = 0)
On Error GoTo 0
End Function

VBA: Sub to Write to a Log File

I have a set of macros defined in my workbook, and I'd like to offer the user the option to log events related to those macros in a log file.
I initiate the log by creating the following in ThisWorkbook:
Public writeLog as Boolean
Public logWrite as Object
Public log as Object
Private Sub Worksheet_Open()
Dim prompt as Integer
prompt = MsgBox("Would you like to log events for this session?", vbYesNo, "Log Events?")
If prompt Then
writeLog = True
Set logWrite = CreateObject("Scripting.FileSystemObject")
Set log = logWrite.CreateTextFile("C:/TEST.txt", False)
Else
writeLog = False
End If
End Sub
I then created a procedure that I can use to write an argument to this object, which I've stored in its own module:
Public Sub PrintLog(obj as Object, argument as String)
If writeLog = True Then
obj.WriteLine argument
End If
End Sub
Unfortunately, this doesn't work, and I'm not sure why: even if I don't include obj as an argument to the function (since log and logWrite were created as global variables), I'm not able to Call WriteLog("String here.") or Call WriteLog(log, "String here.") without an error (Compile Error: Argument Not Optional.)
Is it possible to get such a Sub() to work, so that I can call it from anywhere in the workbook (after a button is pressed in a userform, for example) without having to define a new Scripting.FileSystemObject in every module?
I think that you can solve your problem by making some minor changes to your code. I tried the following setup:
logger module:
Option Explicit
Private log As Object
Public Sub initLog()
Dim prompt As VbMsgBoxResult
Dim fso As Object
prompt = MsgBox("Would you like to log events for this session?", vbYesNo, "Log Events?")
If prompt = vbYes Then
Set fso = CreateObject("Scripting.FileSystemObject")
Set log = fso.CreateTextFile("C:/TEST.txt", False)
End If
End Sub
Public Sub PrintLog(argument As String)
If Not log Is Nothing Then
log.WriteLine argument
End If
End Sub
Public Sub yadda()
'test
PrintLog "yadda"
End Sub
ThisWorkbook:
Private Sub Workbook_Open()
initLog
End Sub
This is my no-frills drop in replacement for Debug.Print(), that logs to "Log.txt" at your Workbook path.
To install : Just search and replace "Debug.Print" with "Log", and optionally call LogClear() at the start of your program.
Public Function Log(ByRef a_stringLogThis As String)
' send to TTY
Debug.Print (a_stringLogThis)
' append (not write) to disk
Open ThisWorkbook.path & "\Log.txt" For Append As #1
Print #1, a_stringLogThis
Close #1
End Function
OPTIONAL : And here's a helper you COULD call at the beginning of your to clear out the previous logs.
Public Function LogClear()
Debug.Print ("Erasing the previous logs.")
Open ThisWorkbook.path & "\Log.txt" For Output As #1
Print #1, ""
Close #1
End Function
OPTIONAL : Finally, if can't live without date and time in your logging, use this Log statement instead:
Public Function Log(ByRef a_stringLogThis As String)
' prepare date
l_stringDateTimeNow = Now
l_stringToday = Format(l_stringDateTimeNow, "YYYY-MM-DD hh:mm:ss")
' concatenate date and what the user wants logged
l_stringLogStatement = l_stringToday & " " & a_stringLogThis
' send to TTY
Debug.Print (l_stringLogStatement)
' append (not write) to disk
Open ThisWorkbook.path & "\Log.txt" For Append As #1
Print #1, l_stringLogStatement
Close #1
End Function
I believe you're having issues as writeLog already exists as a boolean. Error should be popping up "Ambiguous name detected"
Try the following,
Public bLog as Boolean
Public logWrite as Object
Public log as Object
Private Sub Worksheet_Open()
Dim prompt as Integer
prompt = MsgBox("Would you like to log events for this session?", vbYesNo, "Log Events?")
If prompt Then
bLog = True
Set logWrite = CreateObject("Scripting.FileSystemObject")
Set log = logWrite.CreateTextFile("C:/TEST.txt", False)
Else
bLog = False
End If
End Sub
Public Sub WriteLog(Optional obj as Object, Optional argument as String)
If bLog = True Then
obj.WriteLine argument
End If
End Sub
Edit: made parameters optional in WriteLog (or PrintLog) for further testing
' Write to a log file using Separator and Array of variant Parameters
' Auto generate the file
' USE EndLog to close
'use:
' PrintLog vbtab, "one", 2, 3
' PrintLog vbtab, "Apple","Windows","Linux","Android","Commodore","Amiga","Spectrum"
' EndLog
' Generate a csv file:
' PrintLog ";", rst!ID, rst!Name
Private FileLog As Object
Private fso As Object
Const DEBUG_LOG_FILE = "C:\log.txt"
Public Sub PrintLog(ByVal Separator As String, ParamArray Arguments() As Variant)
Dim ele As Variant
Dim line As String
If FileLog Is Nothing Then
Set fso = CreateObject("Scripting.FileSystemObject")
Set FileLog = fso.CreateTextFile(DEBUG_LOG_FILE, True, True)
End If
line = CStr(Now()) ' Print Timestamp
For Each ele In Arguments
If line > "" Then line = line & Separator
line = line & CStr(ele)
Next
If line > "" Then FileLog.WriteLine line
End Sub
Public Sub EndLog()
On Error Resume Next
FileLog.Close
Set FileLog = Nothing
Set fso = Nothing
On Error GoTo 0
End Sub