DougalScott
Technical User
Hi, I am a self taught vbscript user, and have a VB script that is used to maintain local copy of server data on laptop for access when disconnected. This runs on a system with limited access (can't add executables), but I can use vbs. I have been using ROBOCOPY to do the grunt work of mirroring the data in the past, but the system has expanded and I now have to get it to work on another system that doesn't have ROBOCOPY available. I have also considered creating my own tree sync code for a while, and now I have the motivation.
I have written some test code to replace just the ROBOCOPY functionality that works, but it is slow compared to ROBOCOPY. Using a test local data source (2318 files in 118 subfolders), it takes ROBOCOPY 1 sec to compare these two trees (that are in sync eg no actual file data to copy), but 20 secs for my code. Is there something obvious in my code that I could fix to improve the speed of the compare part of the operation?
Most of the code is fairly standard (I think), although I have used a variable in the recursive function call to hold the recurse depth, rather than the more common (in forums) global scope variable. This is so the code is easier to incorporate into other projects (don't have to remember to dim the global variable, worry about variable name conflicts nor increment and decrement the level each time it is called and returns). I only use this to report the source and destinations folders when first called (recurse level is 0), but will also use for optional output tree indenting.
I saw a reference to string appending being slow, and intend to look at skipping appending the return value to the log string which gets quite large as it includes all the files. If that is the bottleneck I will look at using a global dictionary object to save the log entries.
I would also welcome any other advice on improving the code - I expect there is plenty of room for improvement .
Cheers
I have written some test code to replace just the ROBOCOPY functionality that works, but it is slow compared to ROBOCOPY. Using a test local data source (2318 files in 118 subfolders), it takes ROBOCOPY 1 sec to compare these two trees (that are in sync eg no actual file data to copy), but 20 secs for my code. Is there something obvious in my code that I could fix to improve the speed of the compare part of the operation?
Most of the code is fairly standard (I think), although I have used a variable in the recursive function call to hold the recurse depth, rather than the more common (in forums) global scope variable. This is so the code is easier to incorporate into other projects (don't have to remember to dim the global variable, worry about variable name conflicts nor increment and decrement the level each time it is called and returns). I only use this to report the source and destinations folders when first called (recurse level is 0), but will also use for optional output tree indenting.
I saw a reference to string appending being slow, and intend to look at skipping appending the return value to the log string which gets quite large as it includes all the files. If that is the bottleneck I will look at using a global dictionary object to save the log entries.
I would also welcome any other advice on improving the code - I expect there is plenty of room for improvement .
Cheers
Code:
Option Explicit
Dim objFSO
Set objFSO = WScript.CreateObject("Scripting.Filesystemobject")
Dim strSrc, strDest, strMethod, intStart, strReport
strSrc="Q:\PEMS\FSR\Updating\Masters"
strDest = "T:\PFPS\MPA\Masters"
strMethod = "Mirror"
intStart = Now()
strReport = strCopyTree(strSrc, strDest, strMethod, 0)
MsgBox (Now - intStart) *24 *60 *60 & "secs"
'******************************************************************
Function strCopyTree(ByVal strSrc, ByVal strDest, ByVal strMethod, ByVal intLevel)
Const intIndent = 4, intMargin = 20
Dim objSrcFile, objDestFile, objDestFolder, strSrcFile, strDestFile, objSrcFolder, strSrcFolder, strResult, objSubFolder, blnCopy, strStatus
Dim strLog
If intLevel = 0 Then Wscript.Echo strSrc & " ---> " & strDest & vbCrLf
strMethod = LCase(strMethod)
Wscript.Echo String(intMargin, " ") & strSrc
' check source folder exists, exit and return error if not
If Not objFSO.FolderExists(strSrc) Then
strCopyTree = "Error: Source (" & strSrc & ") doesn't exist"
Exit Function
End If
Set objSrcFolder = objFSO.GetFolder(strSrc)
' check dest exists and create if doesn't
If Not objFSO.FolderExists(strDest) Then
If strCreateFolder(strDest) <> "" Then ' Dest not created so abort
strCopyTree = "Error: Folder (" & strDest & ") doesn't exist and couldn't be created"
Exit Function
End If
End If
' compare and copy files
For Each objSrcFile In objFSO.GetFolder(strSrc).Files
blnCopy = False
strDestFile = Replace(objSrcFile.path, strSrc, strDest, 1, -1, vbTextCompare)
If Not objFSO.FileExists(strDestFile) Then ' doesn't exist in dest
strStatus = "New file"
blnCopy = (InStr(1, "mirror,update", strMethod, vbTextCompare) > 0) ' copy orphan for mirror and update methods
Else
Set objDestFile = objFSO.GetFile(strDestFile)
If objSrcFile.DateLastModified > objDestFile.DateLastModified Then ' copy newer always
strStatus = "Newer"
blnCopy = True
ElseIf objSrcFile.DateLastModified < objDestFile.DateLastModified Then
strStatus = "Older"
ElseIf objSrcFile.Size <> objDestFile.Size Then
strStaus = "Modified"
Else
strStatus = "Same"
End If
If Not blnCopy Then blnCopy = ((strMethod = "mirror") And (InStr(1, "older,modified", strStatus, vbTextCompare) > 0)) ' copy older and/or different size for mirror method
End If
If blnCopy Then
strStatus = strStatus & " : copy"
WScript.Echo strStatus & String(intMargin + intIndent - Len(strStatus), " ") & objSrcFile.Name
strResult = strCopyFile(objSrcFile.Path, strDestFile)
If strResult = "Failed" Then Wscript.Echo "**Copy failed**"
strLog = strLog & strResult & vbCrLf
Else
strStatus = strStatus & " : skip"
Wscript.Echo strStatus & String(intMargin + intIndent - Len(strStatus), " ") & objSrcFile.name
strLog = strLog & "Skipped: " & objSrcFile.Path & vbCrLf
End If
Next
' delete dest orphan files and folders for mirror method
If strMethod = "mirror" Then
For Each objDestFile In objFSO.GetFolder(strDest).Files
strSrcFile = Replace(objDestFile.Path, strDest, strSrc, 1, -1, vbTextCompare)
If Not objFSO.FileExists(strSrcFile) Then
WScript.Echo "Extra file" & String(intMargin-Len("Extra file")," ") & "Deleted: " & strDestFile
On Error Resume Next
Call objFSO.DeleteFile(strDestFile, True)
If Err.Number <> 0 Then
strLog = strLog & "Error: delete file " & strDestFile & " failed" & vbCrLf
Wscript.Echo vbTab & "Error: delete file " & strDestFile & " failed"
End If
On Error Goto 0
End If
Next
For Each objDestFolder In objFSO.GetFolder(strDest).SubFolders
strSrcFolder = Replace(objDestFolder.Path, strDest, strSrc, 1, - 1, vbTextCompare)
If Not objFSO.FolderExists(strSrcFolder) Then
Wscript.Echo "Extra folder" & String(intMargin-Len("Extra folder")," ") & "Deleted: " & strDestFile
On Error Resume Next
call objFSO.DeleteFolder(strDestFolder, True)
If Err.Number <> 0 Then
strLog = strLog & "Error: delete folder " & strDestFolder & " failed" & vbCrLf
Wscript.Echo vbTab & "Error: delete folder " & strDestFolder & " failed"
End If
On Error Goto 0
End If
Next
End If
' recurse subfolders
For Each objSubFolder In objSrcFolder.SubFolders ' walk parallel folders in src and dest tree
strLog = strLog & strCopyTree(objSubFolder.Path, objFSO.BuildPath(strDest, objFSO.GetFileName(objSubFolder.Name)), strMethod, intLevel + 1)
Next
' return log
strCopyTree = strLog
End Function
'******************************************************************
Function strCreateFolder(ByVal strFolder)
Dim strError
If Not objFSO.FolderExists(objFSO.GetParentFolderName(strFolder)) Then ' recurse parent folder
strError = strCreateFolder(objFSO.GetParentFolderName(strFolder))
If strError <> "" Then ' pass error back
strCreateFolder = strError
Exit Function
End If
End If
' try to create folder and catch error if occurs
On Error Resume Next
objFSO.CreateFolder(strFolder)
If Err.Number <> 0 Then
strCreateFolder = "Error"
Else
strCreateFolder = "Created"
End If
On Error Goto 0
End Function
'******************************************************************
Function strCopyFile(ByVal strSrc, ByVal strDest)
' clear dest file RO attribute if exists and set
Const intRO = 1, intHidden = 2, intSystem = 4
If objFSO.FileExists(strDest) Then
Dim objFile
Set objFile = objFSO.GetFile(strDest)
objFile.Attributes = objFile.Attributes And -(intRO + intHidden + intSystem + 1) ' remove these attributes before copying
Set objFile = Nothing
End If
' copy the file
On Error Resume Next
Call objFSO.CopyFile(strSrc, strDest, True)
If Err.Number <> 0 Then
strCopyFile = "Failed"
Else
strCopyFile = "Copied"
End If
On Error Goto 0
End Function