VBS: For-each loop (sometimes) iterates through file collection indefinitely - vbscript

The goal of the following VBscript is to prepend a user-defined string to all files with a particular extension within a specified directory:
directory = "C:\Users\xxxxxxxx\Desktop\Test\" 'include final backslash
extension = ".doc" 'include period, ex: ".tab"
''''''''''''''''''''''''''''''''''''''''''
addStr = InputBox("Enter the text you would like to prepend:", , "xxxxxxxx_xxxxxxxxxx_x_xx_xxx_")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(directory)
Set colFiles = objFolder.Files
For Each file In colFiles
absPath = objFSO.GetAbsolutePathName(file)
currentExtension = objFSO.GetExtensionName(absPath)
If StrComp(currentExtension, Mid(extension, 2)) = 0 Then
file.Name = addStr & objFSO.GetFileName(file)
End If
Next
The script generally works well, but occasionally demonstrates this problematic behavior:
When running the script on a directory with lots of files and/or with files with long names, the script appears to iterate back over the collection of files (i.e. prepends to files that have already been prepended) and does so until the filenames become too long to be recognized by the FSO, crashing the script.
The threshold of the number of files/length of filenames at which this occurs appears to be very distinct and reproducible. For example, if I create a target directory (e.g. "...\Desktop\Test") with a file named '1.doc' that is copied/pasted several times, the script will properly rename up to 31 files, but it demonstrates the problematic behavior with 32+ files. Similarly, if I run the script twice over 31 files (generated in the same manner), the script demonstrates the problematic behavior on the second run.
Any thoughts as to the underlying issue are very much appreciated--thanks in advance!

You may have issues here because you're modifying files while iterating them. Try creating an array of file names first and then iterate over the array, changing the names.
ReDim a(colFiles.Count - 1)
i = 0
For Each File In colFiles
a(i) = File.Path
i = i + 1
Next
For i = 0 To UBound(a)
If StrComp(objFSO.GetExtensionName(a(i)), Mid(extension, 2)) = 0 Then
With objFSO.GetFile(a(i))
.Name = addStr & .Name
End With
End If
Next

The reason the above behaviour occurs is, because when you initially call Set colFiles = objFolder.Files, the first 32 files are retrieved, and placed into a cache. Once those 32 files are processed, then the system retrieves the first 32 filenames which have not been processed yet.
Since you have renamed the files after the initial call, the system sees those as new filenames that have not been processed yet. Since their names are still first alphabetically, they are placed into the 32-file cache, and processed again.
The solution by #Bond is the standard workaround for this issue. Due to limitations of vbs, this is the only practical resolution of this issue.

Related

Grab file name from file list, if file size greater than 1MB do copy

I have been set out by the following task by one of my managers at work:
We have a database of images for all our clothing styles and colours. For each type of clothing and for each colour we have 3 images. 2 of them are low resolution images and 1 of them is a high resolution image.
We need to copy the high res image for each style and colour from the old database which is made up of sub folders into one folder therefore ignoring the folder structure.
I have come across this Visual Basic script which is quite close to what I need but requires a few tweaks, as I am not really experienced with VB scripts I was hoping I could get some help here over at SO.
What I need to script to be tweaked to is:
-The script to read image names from a list (filelist.txt) (if possible without requiring to add the path for each image to the list, just the name and the extension which is .jpg)
-The script only needs to grab images if the size is greater than 1MB.
-The script to copy the images from sub-folders without keeping the folder structure.
Any and all help will be greatly appreciated, explanations behind the tweaks and any guidance will also be kind but not required.
Here is the script that I have so far. The paths are temporary as I was playing around with the script.
Option Explicit
' The source path for the copy operation.
Const strSourceFolder = "C:\Users\Cou Rou\Desktop\Old database"
' The target path for the copy operation.
Const strTargetFolder = "C:\Users\Cou Rou\Desktop\New database"
' The list of files to copy. Should be a text file with one file on each row. No paths - just file name.
Const strFileList = "C:\Users\Cou Rou\Desktop\Old database\filelist.txt"
' Should files be overwriten if they already exist? TRUE or FALSE.
Const blnOverwrite = FALSE
Dim objFSO
Set objFSO = CreateObject("Scripting.FileSystemObject")
Const ForReading = 1
Dim objFileList
Set objFileList = objFSO.OpenTextFile(strFileList, ForReading, False)
Dim strFileToCopy, strSourceFilePath, strTargetFilePath
On Error Resume Next
Do Until objFileList.AtEndOfStream
' Read next line from file list and build filepaths
strFileToCopy = objFileList.Readline
strSourceFilePath = objFSO.BuildPath(strSourceFolder, strFileToCopy)
strTargetFilePath = objFSO.BuildPath(strTargetFolder, strFileToCopy)
' Copy file to specified target folder.
Err.Clear
objFSO.CopyFile strSourceFilePath, strTargetFilePath, blnOverwrite
If Err.Number = 0 Then
' File copied successfully
Else
' Error copying file
Wscript.Echo "Error " & Err.Number & " (" & Err.Description & "). Copying " & strFileToCopy
End If
Loop

Move files older than x minutes

I want the script to move all files (all file extensions *.*) older than 5 minutes from an INN folder to and ERROR folder. In my example C:\CopyFlow\Directory test\Inn\ to C:\CopyFlow\Directory test\Inn\Error
So I figured how to move files and how to find files older than x-time after looking it up. Howover, putting this together is the issue for me. Does anyone know how I can nail this?
This is what I got so far...
Dim age_threshold
age_threshold = 5
Dim folder_path
folder_path = WScript.Arguments(0)
Dim fso, f
Set fso = CreateObject("Scripting.FileSystemObject")
Set f = fso.getFolder(folder_path)
Dim old_file_not_found
old_file_found = 0
For Each file in f.Files
Dim age
age = DateDiff("n", file.DateLastModified, Now)
If age > age_threshold Then
old_file_found = 1
.MoveFile "C:\CopyFlow\Directory test\Inn\*.*", "C:\CopyFlow\Directory test\Inn\Error"
Exit for
end if
Next
WScript.Quit
I'm used to batch, so this is a little bit greek to me (source http://www.evalesco.com/check-any-file-older-x-minutes-directory).
Now where do I set (dim?) my INN and ERROR folder in this script? And I'm pretty sure the if age followed by .movefile is wrong, so I probably need a little correction there.
Update Whats missing in the image is a backslash after error (\error\) in the move.file line.
You can't call methods without an object providing the method, so .MoveFile should be fso.MoveFile. However, in its current form the script would move all files from C:\CopyFlow\Directory test\Inn if any of the files in the folder passed as argument to the script is older than 5 minutes.
What you need to do is pass C:\test\inn as the argument to the script, and move only those files that actually are older:
If age > age_threshold Then
file.Move "C:\test\inn\error\"
End If

VBScript Continues Loop to Rename File

Trying to create a VBScript that loops continuously until it finds the specific file and renames it to a different extension with minute and seconds:
do
Set fso = CreateObject("Scripting.FileSystemObject")
Set myFile = fso.GetFile("C:\Users\user\Downloads\test.txt")
If (fso.FileExists(myFile)) Then
myFile.Move "C:\Users\user\Downloads\test.xml"
End If
WScript.Sleep 1000
loop
The above works and renames the file but when looped it errors with "file cannot be found". Will need to add a else statement but having a hard time doing that.
Try checking for the files existence first before attempting to instantiate the File object reference.
Dim fso, myFile, source, dest
Set fso = CreateObject("Scripting.FileSystemObject")
Do
source = "C:\Users\user\Downloads\test.txt"
dest = "C:\Users\user\Downloads\test.xml"
If fso.FileExists(source) Then
Set myFile = fso.GetFile(source)
Call myFile.Move(dest)
Set myFile = Nothing
End If
WScript.Sleep 1000
Loop
Set fso = Nothing
* This is a pseudo coded example based on the original example
Moved the fso instantiation outside of the loop to avoid re-instantiating it on each iteration.
At present not sure what purpose the loop serves but if you are going to be running this for long periods it best to un-instantiate any references to help with script memory optimisation by setting them to Nothing.
Would also recommend while testing the loop to limit the iterations using a counter and exiting the loop if the count is exceeded.

Move first 10 files from a folder to another

I would like to know how I can move the first 10 files in folder to another folder? I have a folder which contains more than 50K files that need to be moved to another location for processing. I want to move 10 files at a time.
Please help to find a way using VBScript.
If you just need to move any 10 files from a folder, just maintain a file count as you iterate the Files collection of a Folder object.
For example:
intCount = 0
Set objFSO = CreateObject("Scripting.FileSystemObject")
For Each objFile In objFSO.GetFolder("c:\path\to\your\files").Files
objFile.Move "c:\new\path\"
intCount = intCount + 1
If intCount = 10 Then Exit For
Next
As I mentioned in the comments, there's no guarantee here which 10 files will get moved. Also of note, make sure to use a blackslash \ at the end of the path you're moving the file to (c:\new\path\) so that it's treated as a folder and not a new file name.

VBScript to move file with wildcard, if it exists

I am attempting to create a script that checks for the existence of archived eventlog files and, if any files exist, moves them to another folder. Running this script does nothing and gives no errors. I believe the wildcard in the If statement is what is giving me issues. I am new to vbscript, and scripting in general, and would appreciate some advice.
Set fso = CreateObject("Scripting.FileSystemObject")
If (fso.FileExists("d:\eventlogs\Archive*.evtx")) Then
FSO.CopyFile "d:\eventlogs\Archive*.evtx" , "d:\eventlogs\archive\"
FSO.Deletefile "d:\eventlogs\archive*.evtx"
End if
You can replicate a wild card search by using a combination of instr() and right(), or just multiple instr().
Set objFSO = CreateObject("Scripting.FileSystemObject")
objStartFolder = "d:\eventlogs\"
Set objFolder = objFSO.GetFolder(objStartFolder)
Set colFiles = objFolder.Files
For Each objFile in colFiles
if instr(objFile.Name,"Archive") <> 0 AND instr(objFile.Name,".evtx") <> 0 then
objFSO.MoveFile objFile.Name, "archive\" + objFile.Name
end if
Next
The appropriate approach of finding files with wildcards in VBScript:
Get the file collection from the containing folder
For each file in the filecollection:
Test the filename with a regular expression on a certain pattern
If the test passes, do some action with this file
Next file
Late answer, but might be useful because apparently nobody spotted the mistake.
From the VBScript documentation (script56.chm in my case), help page for CopyFile method says:
FileExists Method
Returns True if a specified file exists; False if it does
not.
object.FileExists(filespec)
Arguments
object
Required. Always the name of a FileSystemObject.
filespec
Required. The name of the file whose existence is to be determined. A complete path specification (either absolute or relative) must be provided if the file isn't expected to exist in the current folder.
Hence your expression fso.FileExists("d:\eventlogs\Archive*.evtx") returns False here; indeed there isn't any file named Archive*.evtx in your folder.
Either you remove your test, but you'll have to deal with the error the CopyFile method might generate, as doc says:
An error also occurs if a source using wildcard characters doesn't match any files.
As suggested by #automatedchaos in his answer https://stackoverflow.com/a/20907209/666414 you can also loop through files of the folder and decide what to do whenever the filename/extension matches your pattern.
Lastly, you can mix both solutions: loop through files of the folder, then set a flag to True and Exit Loop as soon as you encounter an expected file, then use the CopyFile method.
Like this:
With CreateObject("Scripting.FileSystemObject")
For Each objFile in .GetFolder("d:\eventlogs\").Files
If Left(objFile.Name, 7) = "Archive" And .GetExtensionName(objFile) = "evtx" Then
archiveFound = True
End If
Next
If archiveFound Then
.CopyFile "d:\eventlogs\Archive*.evtx", "d:\eventlogs\archive\"
.DeleteFile "d:\eventlogs\Archive*.evtx"
End If
End With
Note the wildcards work with the DeleteFile method too!

Resources