Best way to add items to a list in VBScript? - vbscript

Essentially, I want to get the names of all .zip files in a given directory and unzip them. Right now, I'm working on just getting the names of the files into some sort of array or list. It's grabbing the files correctly, but I don't know a whole lot about VBScript, could someone point me in the right direction?
Dim fileList
Set objFSO = CreateObject("Scripting.FileSystemObject")
objStartFolder = "C:\Test"
Set objFolder = objFSO.GetFolder(objStartFolder)
Wscript.Echo objFolder.Path
Set colFiles = objFolder.Files
For Each objFile in colFiles
If UCase(objFSO.GetExtensionName(objFile.name)) = "ZIP" Then
Wscript.Echo objFile.Name
'Add file names to fileList variable
End If
Next

There is...
' using vbArray
ReDim fileArray(-1)
Set objFSO = CreateObject("Scripting.FileSystemObject")
objStartFolder = "C:\Test"
Set objFolder = objFSO.GetFolder(objStartFolder)
For Each objFile In objFolder.Files
If UCase(objFSO.GetExtensionName(objFile.Name)) = "ZIP" Then
ReDim Preserve fileArray(UBound(fileArray) + 1)
fileArray(UBound(fileArray)) = objFile.Name
End If
Next
WScript.Echo Join(fileArray, vbNewLine)
' using .NET ArrayList (as no biult-in Lists in VBScript)
Dim fileList
Set fileList = CreateObject("System.Collections.ArrayList")
For Each objFile In objFolder.Files
If UCase(objFSO.GetExtensionName(objFile.Name)) = "ZIP" Then
fileList.Add objFile.Name
End If
Next
WScript.Echo Join(fileList.ToArray, vbNewLine)
P.P.S:
Is Files collection dynamic:
Set oFSO = CreateObject("Scripting.FileSystemObject")
curDir = CreateObject("WScript.Shell").CurrentDirectory
Set oFolder = oFSO.GetFolder(curDir)
Set oFiles = oFolder.Files
WScript.Echo "Files count: " & oFiles.Count
Set oFile = oFSO.CreateTextFile(oFSO.GetTempName)
oFile.Close
WScript.Echo "Files count: " & oFiles.Count
[EDIT] But looks like then iterate VBS use snapshot of Files collection, so we can presume that unzip may go safety without need of file list.

As you have the files in a collection already (colFiles), the first reason to have a collection of items - process them in turn - does not make you put them in a second list. Why not just unzip each file instead of putting it in a list? To unzip a file A you don't need info about/access to the other files X in the folder. So the second reason for a collection - needing all elements at the same time/when processing one item - doesn't apply either.
If you insist on a second list, all depends on what you want to do with it/the items. If you just want a list of names, the easiest way would be to put the names/pathes as keys in a Dictionary. If you want the names sorted, a System.Collections.ArrayList/SortedList will be more convenient. If you want to work with more properties/attributes of the files - size, dates, access rights, ... - a Disconnected ADODB Recordset will allow you to store all those properties in a (SQL) Table.
Last, but not least, a simple native VBScript Array (dimensioned for the attributes for each file) could be used: As you know the possible number of elements (colFiles.Count), you can define the size of the array before the loop, assign the items in the loop, and ReDim Preserve it after looping.
Take your pick, and I'll add sample code to this posting.
P.S.:
As #Panayot gave you sample code for Arrays and ArrayLists, it would make sense to ask for an ADO Recordset.
P.P.S:
For worriers:
Dim oFS : Set oFS = CreateObject( "Scripting.FileSystemObject" )
Dim sDir : sDir = "..\testdata\testFilesCollection"
Dim nFile, sFile, oFile, i
If oFS.FolderExists( sDir ) Then oFS.DeleteFolder sDir
oFS.CreateFolder sDir
WScript.Echo "----- Creating", cnMax, "files"
For nFile = 1 To cnMax
sFile = nFile & ".txt"
oFS.CreateTextFile oFS.BuildPath( sDir, sFile )
WScript.Echo sFile, "created"
Next
WScript.Echo "----- Looping over", cnMax, "files and creating", cnMax, "more"
i = 0
For Each oFile In oFS.GetFolder( sDir ).Files
If i <= cnMax Then
sFile = Chr(65 + i) & ".txt"
oFS.CreateTextFile oFS.BuildPath( sDir, sFile )
End If
i = i + 1
WScript.Echo oFile.Name, "seen", sFile, "created"
Next
WScript.Echo "----- Looping over", 2 * cnMax, "files"
For Each oFile In oFS.GetFolder( sDir ).Files
WScript.Echo oFile.Name, "seen"
Next
output:
----- Creating 3 files
1.txt created
2.txt created
3.txt created
----- Looping over 3 files and creating 3 more
2.txt seen A.txt created
3.txt seen B.txt created
1.txt seen C.txt created
----- Looping over 6 files
B.txt seen
2.txt seen
C.txt seen
A.txt seen
3.txt seen
1.txt seen
[The files collection is a snapshot] == #Panayot's argument/evidence ==> The For Each loops over a snapshot of the Files collection - at least wrt additions. Testing for deletions is left as an exercise.

Related

Saving file into another file using VBScript after modification

I have some XML files in a folder \\demo.US\Modified\. The files in the folder are:
USA.xml
Canada.xml
Mexico.xml
The code below is changing the encoding from UTF-8 to windows-1252 and is creating a modified file mod.xml.
This mod.xml file have data from all three XML files concatenated.
I need help so I can save files separately.
If value of objFile.Name is USA.xml then it should save modified file name as USA_mod.xml. the output for \\demo.US\Modified\ folder after execution is complete should have mod files in it as below.
USA.xml
Canada.xml
Mexico.xml
USA_mod.xml
Canada_mod.xml
Mexico_mod.xml
The code I used is as follows.
Set objFSO = CreateObject("Scripting.FileSystemObject")
objStartFolder = "\\demo.US\Modified\"
Set objFolder = objFSO.GetFolder(objStartFolder)
Set colFiles = objFolder.Files
For Each objFile In colFiles
WScript.Echo objFile.Name
Set objFile = objFSO.OpenTextFile(objStartFolder & objFile.Name, 1)
Set outFile = objFSO.OpenTextFile(objStartFolder & "mod.xml", 2, True)
Do Until objFile.AtEndOfStream
strContent = strContent & objFile.ReadLine
Loop
MsgBox strContent
strContent = Replace(strContent, "encoding=""UTF-8""", "encoding=""windows-1252""")
outFile.WriteLine strContent
outFile.Close
objFile.Close
Next
As others have already pointed out, you shouldn't do what you're attempting to do here, because it is very likely to create more problems down the road. Find the cause of the issue and fix that instead of trying to handle symptoms. You have been warned.
With that said, the reason why the content of all input files is written to the same output file is because you always specify the same output file. That file should contain only the content of the last input file, though, because you open the file for writing (thus erasing previous content) rather than for appending.
Replace these lines:
Set objFile = objFSO.OpenTextFile(objStartFolder & objFile.Name, 1)
Set outFile = objFSO.OpenTextFile(objStartFolder & "mod.xml", 2, True)
with this:
Set inFile = objFile.OpenAsTextStream
outFilename = objFSO.BuildPath(objStartFolder, objFSO.GetBaseName(objFile) & "_mod.xml")
Set outFile = objFSO.OpenTextFile(outFilename, 2, True)
and also replace the other occurrences of objFile after that with inFile (always avoid changing the value of a loop variable), and the code should do what you expect it to do. But again, be warned that the output may not be valid XML.
I managed to made it working, below is the code I used
Dim objFSO, filePath, objFile, colFiles, s , FName
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set filePath = objFSO.GetFolder("\\demo.US\Modified\")
Set colFiles = filePath.Files
For Each FName in colFiles
set objFile = objFSO.OpenTextFile(FName.Path,1)
set outFile = objFSO.OpenTextFile(LEFT(FName.Path,instr(FName.Path,".xml")-1) &"_mod.xml",2,True)
do until objFile.AtEndOfStream
strContent=objFile.ReadLine
Loop
strContent = Replace(strContent, "encoding=""UTF-8""", "encoding=""windows-1252""")
outFile.WriteLine strContent
outFile.Close
objFile.Close
Next

Too many iterations in loop

This script collects all files in a folder and renames the files by appending the number of lines to the file name. All files are .txt files. The method (since fso.MoveFile and fso.DeleteFile are too particular, generating permissions errors) is to
create the text files,
then create a collection of the files in the folder,
then copy each file into the same folder with a new name, and
finally to delete the original file that was copied.
The script works ok, unless there are no empty text files in the collection. What happens is, the collection gets rebuilt with the new files and the script once again renames the files. I know I can prevent this by checking each file for the existence of certain repeating character strings, but I'd like to know what's happening? Why does the script rebuild the file collection and run through them again renaming each one? This continues on until I kill the process.
Another interesting factoid is, if I happen to trap an empty text file, my message is displayed and the script stops there, but has still reprocessed the first file in the collection a second time. Note that the empty file just happens to be the last one in the collection, but the first filed is once again processed.
So, by design a created text file named 'ab0.txt' gets renamed to 'ab0-15.txt' since it has 15 lines of text in it. What happens is this newly renamed file looks like 'ab0-15-15-15-15-15-15-15-15-15-15.txt'
Questions: What's going on? And is there a better and more efficient way to accomplish this objective?
Here's the code pertinent to the issue:
Set fso = CreateObject("Scripting.FileSystemObject")
Set oFolder = fso.GetFolder(strSaveTo)
Set colFiles = oFolder.Files
' Call Sub to copy and rename
ChangeFileName colFiles
MsgBox("File renaming complete.")
' Exit code
Sub ChangeFileName(collectionSet)
Const ForReading = 1
Dim oFile
For Each oFile In collectionSet
Set LineCnt = fso.OpenTextFile(oFile, ForReading)
If oFile.Size = 0 then
'if this msg is not included, weird things happen
MsgBox("The file named " & oFile & _
" is empty.You may want to verify and manually delete it.")
'[I had some code in here to delete the empty file, but nothing worked]
Else
Do While LineCnt.AtEndOfStream <> True
LineCnt.SkipLine
Loop
lineVar = lineCnt.Line-1
strNewFile = strSaveTo & Left(oFile.name, Len(oFile.name)-4) & _
"-" & lineVar & ".txt"
fso.CopyFile oFile, strNewFile
LineCnt.Close
fso.DeleteFile oFile, True
End If
Next
End Sub
I've heard anecdotal evidence that the Files collection is "live", meaning that newly created files will be added to the collection and iterated over, but I can't find any documentation that says one way or the other. In any case, it's probably a good idea to copy the File objects in the collection to an array first before processing them:
Dim oFile
Dim fileArray()
Dim i
ReDim fileArray(collectionSet - 1)
i = 0
For Each oFile in collectionSet
Set fileArray(i) = oFile
i = i + 1
Next
For Each oFile In fileArray
' Count lines and rename
Next
It seems that collectionSet is the collection of files in the folder that you are trying to modify. The problem is that with each pass through the for-each loop you are adding files to this folder, some of which are fed back into the loop. What you need to do is the find a way to take a snapshot of the folder before you try to iterate over it. The way to do this would be to replace the folder collectionSet by a collection of strings which are the names of the files before you iterate over it, and modify your code to open the files by their name (instead of via a file object). That way the collection won't be expanding while you iterate over it.
You should create your vars in the scope they are used (e.g. your
file/folder objects are used in the sub.
Always explicit(ly) declare your vars.
You don't need to copy the file and rename it then do the delete.
Just rename it with the FileObject.Name property.
Here is an example:
Option Explicit 'always declare your vars!
Dim strFolder: strFolder = "c:\temp\Rename Test"
Dim strExtension: strExtension = "txt"
' Call Sub to rename the files in the folder
ChangeFileName strFolder, strExtension
Sub ChangeFileName(strFolder, strExtension)
Const ForReading = 1
Dim FSO: set FSO = CreateObject("Scripting.FileSystemObject")
Dim objFolder: set objFolder = FSO.GetFolder(strFolder)
Dim colFiles: set colFiles = objFolder.Files
Dim objFile
Dim intCount
Dim strFileName
Dim objTextStream
For Each objFile In colFiles
msgbox "File: " & objfile.path & vbcrlf & FSO.GetExtensionName(objFile.path)
if UCase(FSO.GetExtensionName(objFile.Path)) = UCase(strExtension) and _
objFile.Size > 0 then
'set LineCnt = FSO.OpenTextFile(objFile, ForReading)
set objTextStream = objFile.OpenAsTextStream(ForReading,-2)
intCount = 0
strFileName = objFile.Name
Do While objTextStream.AtEndOfStream <> True
intCount = intCount + 1
objTextStream.ReadLine
Loop
objTextStream.Close
objFile.Name = FSO.GetBaseName(objFile.Path) & "-" & _
intCount & "." & FSO.GetExtensionName(objFile.Path)
end if
Next
End Sub

Sort files in a folder

I am trying to sort the files of a folder and perform action on them in a specific order (by name or by date modified for example)
I have tried the following method:
Dim objFSO
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(source_folder.Path)
Set colFiles = objFolder.Files
Set outputLines = CreateObject("System.Collections.ArrayList")
For Each objFile in colFiles
outputLines.Add(objFile.Name)
next
outputLines.Sort()
For Each outputLine in outputLines
set objFile = colFiles.item (outputLine&"")
*rest of the code*
the problem is if the file name of the file contains a char with an ASCII value which is less the ".", for example, "test!.exe" will be before "test.exe", which is not good for me.
I had an idea to make a class which contains the "extension" and "file name without extension" as field and sort by the "file name without extension" but I found out I cannot sort an Arraylist with custom objects as elements.
I have two questions on the same subject:
1) Is there a way to sort the files of a folder and perform action on them in a specific order which handles such cases (like test!.exe and test.exe)
2) Is there a default order to the files in a folder? I tested it a bit and it seems like the order is by the files name, but I saw in an official Microsoft documentation that there is no order to the files, so I am a bit confused.
Here's one idea. You could substitute a control character (or some other invalid filename char) between the file's base name and its extension. For example, put a vbTab in as a delimiter.
For Each objFile in colFiles
strBaseName = objFSO.GetBaseName(objFile.Path)
strExtension = objFSO.GetExtensionName(objFile.Path)
outputLines.Add strBaseName & vbTab & strExtension
Next
After sorting, you'll just have to remember to swap it back out before doing anything with the file.
For Each outputLine in outputLines
strFile = Replace(outputLine, vbTab, ".")
...
Next

Print out files in a directory sorted by FileName

I'm trying to print documents in a directory, ordered by file name ascending. I have the script below to print out the documents, which works, but it's in a random order. Is there any way to sort the "files" collection based on name?
'Set the TargetFolder
TargetFolder = "C:\Temp\Hewitt\TestPrintFolder"
Set shellApplication = CreateObject("Shell.Application")
Set folder = shellApplication.Namespace(TargetFolder)
Set files = folder.Items
For Each file In files
file.InvokeVerbEx ("Print")
Next
The are many ways to get an odered list of the file(name)s in a directory. One uses the .NET ArrayList - like this:
Option Explicit
Dim oFS : Set oFS = CreateObject("Scripting.FileSystemObject")
Dim sDir : sDir = "... your folder ..."
Dim oFiles : Set oFiles = CreateObject("System.Collections.ArrayList")
Dim oFile
For Each oFile In oFS.GetFolder(sDir).Files
WScript.Echo oFile.Name
oFiles.Add oFile.Path
Next
WScript.Echo "----------"
oFiles.Sort
Dim sFile
For Each sFile In oFiles
WScript.Echo oFS.GetFile(sFile).Name
Next
If you can't harness .Net, you could
store the names in a VBScript array and find/write a Sort Sub/Function
use a disconnected ADODB recordset
shell out to dir /o:n

VB.Net - List files & subfolders from a specified Directory and save to a text document, and sort results

I am working on a project that requires me to search and list all files in a folder that could have multiple sub folders and write it to text documents.
Primarily the file extension i will be searching for is a .Doc, but I will need to list the other files found in said directory as well.
To make things slightly more difficult I want the text documents to be sorted by File type and another by Directory.
I do not know how possible this is, but I have search for methods online, but have as of yet found correct syntax.
Any help will be greatly appreciated.
I write this in the past, should server as a base for your version. I know it's not .NET, still I hope it helps something. It prompts the user for a path to scan, recurses into folders, and writes the file name, path, and owner into a CSV file. Probably really inefficient and slow, but does the job.
Main() ' trickster yo
Dim rootFolder 'As String
Dim FSO 'As Object
Dim ObjOutFile
Dim objWMIService 'As Object
Sub Main()
StartTime = Timer()
If Wscript.Arguments.Count = 1 Then ' if path provided with the argument, use it.
rootFolder = Wscript.Arguments.Item(0)
Else
rootFolder = InputBox("Give me the search path : ") ' if not, ask for it
End If
Set FSO = CreateObject("Scripting.FileSystemObject")
Set ObjOutFile = FSO.CreateTextFile("OutputFiles.csv")
Set objWMIService = GetObject("winmgmts:")
ObjOutFile.WriteLine ("Path, Owner") ' set headers
Gather (rootFolder)
ObjOutFile.Close ' close the stream
EndTime = Timer()
MsgBox ("Done. (ran for " & FormatNumber(EndTime - StartTime, 2) & "s.)")
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function Gather(FolderName)
On Error Resume Next
Dim ObjFolder
Dim ObjSubFolders
Dim ObjSubFolder
Dim ObjFiles
Dim ObjFile
Set ObjFolder = FSO.GetFolder(FolderName)
Set ObjFiles = ObjFolder.Files
For Each ObjFile In ObjFiles 'Write all files to output files
Set objFileSecuritySettings = _
objWMIService.Get("Win32_LogicalFileSecuritySetting='" & ObjFile.Path & "'")
intRetVal = objFileSecuritySettings.GetSecurityDescriptor(objSD)
If intRetVal = 0 Then
owner = objSD.owner.Domain & "\" & objSD.owner.Name
ObjOutFile.WriteLine (ObjFile.Path & ";" & owner) ' write in CSV format
End If
Next
Set ObjSubFolders = ObjFolder.SubFolders 'Getting all subfolders
For Each ObjFolder In ObjSubFolders
Set objFolderSecuritySettings = _
objWMIService.Get("Win32_LogicalFileSecuritySetting='" & ObjFile.Path & "'")
intRetVal = objFolderSecuritySettings.GetSecurityDescriptor(objSD)
If intRetVal = 0 Then
owner = objSD.owner.Domain & "\" & objSD.owner.Name
ObjOutFile.WriteLine (ObjFolder.Path & ";" & owner) ' write in CSV format
End If
Gather (ObjFolder.Path)
Next
End Function

Resources