Serializing and deserializing a file in a spreadsheet - excel

I feel dirty asking this because it's such a hacky workaround, but I have a project where the deliverable must be a single .xlsm file. However, we have no restrictions on what files that deliverable may write or execute. VBA and Excel's macro editor have limitations that don't work with the project.
So I'm trying to save the binary values of a Python interpreter in a worksheet and then write that .exe to the client's computer when the macro is run. (It's pretty much a virus and a bad idea, I know, but the requirements are strict and unchangeable.)
I have a macro to read python.exe into a worksheet:
Function ReadFromFile(path)
Dim bytes() As Byte
Dim fileInt As Integer: fileInt = FreeFile
Open path For Binary Access Read As #fileInt
ReDim bytes(0 To LOF(fileInt) - 1)
Get #fileInt, , bytes
Close #fileInt
Set ReadFromFile = bytes
End Function
Sub ReadCompiler_Click()
Dim path As String: path = ActiveWorkbook.path & "\python.exe.original"
Dim bytes() As Byte
bytes = ReadFromFile(path)
Dim cell As Range
Set cell = Worksheets("PythonEXE").Range("A1")
For Each chunk In bytes
cell.Value = chunk
Set cell = cell.Offset(1, 0)
Next chunk
End Sub
I have verified that this copies the binary file byte-for-byte into column A of my PythonEXE worksheet.
My problem is when writing the bytes back to a file, the written file is significantly different than the original. I'm using the following functions to write from the worksheet to the output file:
Function WriteToFile(path, data)
Dim fileNo As Integer
fileNo = FreeFile
Open path For Binary Access Write As #fileNo
Put #fileNo, 1, data
Close #fileNo
End Function
Sub WriteCompiler_Click()
Dim TotalRows As Long
Dim bytes() As Byte
TotalRows = Worksheets("PythonEXE").Rows(Worksheets("PythonEXE").Rows.Count).End(xlUp).Row
ReDim bytes(TotalRows)
For i = 1 To TotalRows
bytes(i) = CByte(Worksheets("PythonEXE").Cells(i, 1).Value)
Next i
Dim path As String: path = ActiveWorkbook.path & "\python.exe.written"
WriteToFile path, bytes
End Sub
Why is my output binary different than the input binary? It's not human readable, but their checksums are different and when I open them both in an IDE the output file looks like it has a bunch of rectangle glyphs at the beginning where the input file does not.

I changed a couple of things (guided by this answer to get around the problem:
When VBA writes a Variant, it puts some header info in the output. So I changed WriteToFile to copy data to a Byte array before writing it:
Dim buffer() As Byte
ReDim buffer(UBound(data))
buffer = data
For i = 0 To UBound(data)
Put #fileNo, i + 1, CByte(buffer(i))
Next i
I had an off-by-one error by going to UBound(data) instead of UBound(data) - 1. This is a little hairy because Put takes the write position as one-based instead of zero-based, but array indexers are zero-based:
Dim buffer() As Byte
ReDim buffer(UBound(data))
buffer = data
For i = 0 To (UBound(data) - 1)
Put #fileNo, i + 1, CByte(buffer(i))
Next i
Here's the full solution:
Function WriteToFile(path, data)
Dim fileNo As Integer
fileNo = FreeFile
Open path For Binary Access Write As #fileNo
Dim buffer() As Byte
ReDim buffer(UBound(data))
buffer = data
For i = 0 To (UBound(data) - 1)
Put #fileNo, i + 1, CByte(buffer(i))
Next i
Close #fileNo
' Shell ("explorer.exe " & path)
End Function
Function ReadFromFile(path)
Application.StatusBar = "Reading " & path
Dim bytes() As Byte
Dim fileInt As Integer: fileInt = FreeFile
Open path For Binary Access Read As #fileInt
ReDim bytes(0 To LOF(fileInt) - 1)
Get #fileInt, , bytes
Close #fileInt
ReadFromFile = bytes
End Function
Sub UpdatePython_Click()
Application.Calculation = xlCalculationManual
Dim path As String: path = ActiveWorkbook.path & "\python.exe.original"
Dim bytes() As Byte
bytes = ReadFromFile(path)
Worksheets("PythonEXE").Columns(1).EntireColumn.Clear
Dim cell As range
Set cell = Worksheets("PythonEXE").range("A1")
For Each chunk In bytes
cell.Value = chunk
Set cell = cell.Offset(1, 0)
Next chunk
Application.ActiveWorkbook.Save
Application.Calculation = xlCalculationAutomatic
End Sub
Sub WriteCompiler_Click()
Dim TotalRows As Long
Dim bytes() As Byte
TotalRows = Worksheets("PythonEXE").Rows(Worksheets("PythonEXE").Rows.Count).End(xlUp).Row
ReDim bytes(TotalRows)
For i = 0 To TotalRows
bytes(i) = CByte(Worksheets("PythonEXE").Cells(i + 1, 1).Value)
Next i
Dim path As String: path = ActiveWorkbook.path & "\python.exe.written"
If Dir(path) <> "" Then
Kill path
End If
WriteToFile path, bytes
Shell ActiveWorkbook.path & "\checksum.bat", vbNormalFocus
End Sub

Related

FreeFile Multiple CSVs Error 67 Too many files

Background
I need to open multiple csvs in multiple folders, and for this matter I use FreeFile as input: let's say there are over 1000 csvs (powerquery will not have use here, since I only need the last row of data of each csv and then analyze that). I have seen that expanding to 512 may temporarily fix it in a way, but I do not think that is the core cause, hence, not providing a long term solution.
Problem
Seems like even if I close the file, the memory is not properly cleared, hence I get an error 67 after some looping on files has been done.
Code
I created a function to retrieve the Last Line within my main sub code, I even attempted to loop until freefile is 1 again (I added some sleep as well), but no luck, at some point, grows at 2.
Function Return_VarInCSVLine(ByRef NumLineToReturnTo As Long, ByRef TxtFilePathCSV As String, Optional ByRef IsLastLine As Boolean) As Variant
If NumLineToReturnTo = 0 Then NumLineToReturnTo = 1
'NumLineToReturnTo has to be at least 1 even if LastLine is set to true so no error is arised from IIF
Dim NumFileInMemory As Long
Dim ArrVarTxtLines() As Variant
Dim CounterArrTxtLines As Long
Dim TxtInLine As String
NumFileInMemory = FreeFile: CounterArrTxtLines = 1
Open TxtFilePathCSV For Input As #NumFileInMemory: DoEvents
Do While Not EOF(NumFileInMemory)
Line Input #NumFileInMemory, TxtInLine
ReDim Preserve ArrVarTxtLines(1 To CounterArrTxtLines)
ArrVarTxtLines(CounterArrTxtLines) = TxtInLine
CounterArrTxtLines = CounterArrTxtLines + 1
Loop
LoopUntilClosed:
Close #NumFileInMemory: Sleep (10): DoEvents
NumFileInMemory = FreeFile
If NumFileInMemory > 1 Then GoTo LoopUntilClosed
Return_VarInCSVLine = IIf(IsLastLine = True, ArrVarTxtLines(UBound(ArrVarTxtLines)), ArrVarTxtLines(NumLineToReturnTo))
End Function
Question
How can I avoid this error in this scenario? Or what are my alternatives? I used to do workbooks.Open but that is slower than just using FreeFile and then Open for input
You could try to use the FileSystemObject on a Windows PC
Function fsoReadLine(fileName As String, lineNo As Long, Optional lastLine As Boolean) As String
Dim fso As Object
Dim textFile As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set textFile = fso.OpenTextFile(fileName, 1)
Dim vDat As Variant
' Read the whole file and split it by lines
vDat = Split(textFile.ReadAll, vbCrLf)
Dim totalLines As Long
totalLines = UBound(vDat) + 1 ' zero based array!
If lastLine Then
fsoReadLine = vDat(totalLines - 1)
Else
If lineNo <= totalLines Then
fsoReadLine = vDat(lineNo - 1)
End If
End If
textFile.Close
End Function
And if you only need the last line you could shorten the code to
Function fsoLastLine(fileName As String) As String
Dim fso As Object
Dim textFile As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set textFile = fso.OpenTextFile(fileName, 1)
Dim vDat As Variant
' Read the whole file and split it by lines
vDat = Split(textFile.ReadAll, vbCrLf)
fsoLastLine = vDat(UBound(vDat))
textFile.Close
End Function

VBA script to run batch file from excel list, read result file, parse result file and write result to primary excel file

So, before I place my code, I'll explain what I am trying to do, because I can't test the script myself due to what it is supposed to do, effecting what it must do. I know this is a bit odd, but bear with me please.
Once every two weeks or so, we currently run batch files to update a specific tool on all the WS's in our organization.
Yes, we do have tool propagation software, but as this specific tool is extremely important, we don't trust it's distribution to any automated method which have proven in most cases to fail without us being able to understand the reason.
So, I wrote a few simple command batch files which run the installation command, and write the output to a text file which we then manually go through to find which ws's it was installed on, and which it wasn't.
The ws's on which it was not installed are the ws's we know we know due to the failure, that we have additional issues with and we then put all our effort into finding and fixing those issues.
As you can imagine, it's a time consuming effort, and I have decided I want to automate as much as possible of the manual check, in order to know quickly which ws's failed, and the fail code.
I start out with a list of ws names in excel.
For example,
K190ASSn1.domainname
m930eastgate.domainname
n190alka.domainname
n190amsv.domainname
n190amzi.domainname
N190ARME.domainname
N190AVMA.domainname
N190AVNT.domainname
n190chockstest.domainname
N190DLCR.domainname
N190DNBS.domainname
N190edsh.domainname
n190ehma2.domainname
N190EISH.domainname
I wrote my script to do the following:
Read all the ws names from column A into an array.
Loop through the array, and use the Shell function to call an external cmd file which then runs, and writes the result of the run into a TXT file located in a directory on the D drive called "Minstall".
I then read the names of all the files created in that directory into a new array.
I sort both arrays from A to Z (using a script I found online) to get everything in the same order for the next stage.
I then loop through the file names in the 2nd array, and read each file into a text field which I then parse to find the result of the script run.
That result is then written into a third array in the same logical position of the file name I read.
Finally, I re-write the file names back to the worksheet, overwriting what was there, and in the adjacent column, I write the run result from the relevant cell position in the third array.
I will then end up with a file that contains all the data in one visible point (I hope).
At a later stage, I will add a script that will email the relevant team with a list of the ws's they need to deal with (Those with any run result different from zero), and what they need to do. But that's not for the here and now.
Since if I run the code and it works (I hope) it would perform the update, and I do not yet want to do that, what I am really looking for, is additional eyes to go over my code, to see if what I wrote for each action as defined above is correct and will work, and if there is a way to perhaps write what I did, better.
In general, I went over each stage and everything "looks" good.
Anyone willing to assist here ?
Added by request of #CDP1802:
Examples of the two different results that can be found in the text files. One contains a result of zero, meaning that the script worked. The other contains a code of 1603, which is a generic "there's a problem captain but I don't know what it is" response from M$ msiexec.
The spaces between the lines of the text are what appear in the actual text file.
Example 1 (0 response)
PsExec v2.33 - Execute processes remotely
Copyright (C) 2001-2021 Mark Russinovich
Sysinternals - www.sysinternals.com
C:\Windows\system32>msiexec /i "\\server\Minstall\Installation.msi" /qn ACCEPTEULA=YES REPORTSERVER=server.domainname USESSL=TRUE REPORTSERVERPORT=443 IGNORESSLERRORS=TRUE InstallCertificates=yes /l*v C:\Windows\TEMP\install_log4258289.txt
Connecting to K190LPRTLV4.iaadom...
Starting PSEXESVC service on K190LPRTLV4.iaadom...
Copying authentication key to K190LPRTLV4.iaadom...
Connecting with PsExec service on K190LPRTLV4.iaadom...
Copying d:\Install425.bat to K190LPRTLV4.iaadom...
Starting d:\Install425.bat on K190LPRTLV4.iaadom...
Install425.bat exited on K190LPRTLV4.iaadom with error code 0.
Example 2 (1603 response)
PsExec v2.33 - Execute processes remotely
Copyright (C) 2001-2021 Mark Russinovich
Sysinternals - www.sysinternals.com
C:\Windows\system32>msiexec /i "\\server\Minstall\Installation.msi" /qn ACCEPTEULA=YES REPORTSERVER=server.domainname USESSL=TRUE REPORTSERVERPORT=443 IGNORESSLERRORS=TRUE InstallCertificates=yes /l*v C:\Windows\TEMP\install_log4258289.txt
Connecting to K190LPRTLV3.iaadom...
Starting PSEXESVC service on K190LPRTLV3.iaadom...
Copying authentication key to K190LPRTLV3.iaadom...
Connecting with PsExec service on K190LPRTLV3.iaadom...
Copying d:\Install425.bat to K190LPRTLV3.iaadom...
Starting d:\Install425.bat on K190LPRTLV3.iaadom...
Install425.bat exited on K190LPRTLV3.iaadom with error code 1603.
The updated code is as follows:
Option Explicit
Sub Check_Files()
Const Col_Names = "A"
Const Col_Result = "B"
Const Row_Text = 4 'first line of text and result
Dim wb As Workbook
Dim wsMain As Worksheet
Dim WSNames() As String 'Will hold all the ws names as an array read from column A
Dim WSResult() 'Will hold result for specific ws
Dim DirectoryListArray() As string
ReDim DirectoryListArray(3000) 'Set the directory listing array size to 3000 as a max count
Dim NumberArray() As Long
Dim lastrow As Long, FileCount As Long, NumberCount As Long, r As Long, i As Long, j As Long
Dim awsname as string, strDir As string, strPath As string
Dim item as variant
Dim ReadFile As String, text As String, textline As String, RetCode As Integer
Set wb = ActiveWorkbook
With wb
Set wsMain = .Sheets("Main")
End With
'Copy ws names into array for speed
With wsMain
lastrow = .Cells(.Rows.Count, Col_Names).End(xlUp).Row
If lastrow < Row_Text Then
MsgBox "No ws names found in column " & Col_Names, vbCritical
Exit Sub
End If
WSNames = .Cells(1, Col_Names).Resize(lastrow).Value2
ReDim WSResult(1 To lastrow)
End With
'Write how many names were read into array
Cells(1,3) = "Number of names read into array is " & lastrow
'loop through all ws names and run the batch file for each one
For r = Row_Text To UBound(WSNames)
awsname = WSNames(r, 1) 'Read in next ws name from array
Runcmd(awsname)
Next r
'Write how many batch files were run into worksheet
Cells(2,3) = "Number of batch files run is " & r
'count how many text files have been created
strDir = "D:\Minstall"
strPath = strDir & "\*.txt"
'Loop through all the files in the directory by using Dir$ function
MyFile = Dir$(strPath)
Do While MyFile <> ""
DirectoryListArray(FileCount) = MyFile
MyFile = Dir$
FileCount = FileCount + 1
Loop
'Reset the size of the array without losing its values by using Redim Preserve
Redim Preserve DirectoryListArray(FileCount - 1)
'Write how many text files were found
Cells(3,3) = "Number of txt files found is " & FileCount
''Debug.Print writes the results to the Immediate window (press Ctrl + G to view it)
'For FileCount = 0 To UBound(DirectoryListArray)
'Debug.Print DirectoryListArray(FileCount)
'Next FileCount
'Sort the arrays so that we have the same order in both arrays
'Since both arrays should in effect have the same amount of elements
'sorting names array from A to Z
For i = LBound(WSNames) To UBound(WSNames)
For j = i + 1 To UBound(WSNames)
If UCase(WSNames(i,1)) > UCase(WSNames(j,1)) Then
Temp = WSNames(j,1)
WSNames(j,1) = WSNames(i,1)
WSNames(i,1) = Temp
End If
Next j
Next i
'sorting file array from A to Z
For i = LBound(DirectoryListArray) To UBound(DirectoryListArray)
For j = i + 1 To UBound(DirectoryListArray)
If UCase(DirectoryListArray(i,1)) > UCase(DirectoryListArray(j,1)) Then
Temp = DirectoryListArray(j,1)
DirectoryListArray(j,1) = DirectoryListArray(i,1)
DirectoryListArray(i,1) = Temp
End If
Next j
Next i
NumberCount = 0
'Loop through files in directory based on what's in array
For i = LBound(DirectoryListArray) To UBound(DirectoryListArray)
ReadFile = "D:\Minstall" & "\" & DirectoryListArray(NumberCount)
ReadFile = Application.GetOpenFilename()
Open myFile For Input As #1
Do Until EOF(1)
Line Input #1, textline
text = text & textline
Loop
Close #1
RetCode = InStr(text, "with error code ")
NumFound = Mid(text, posLat + 16, 1)
If NumFound > 0 Then
NumFound = Mid(text, posLat + 16, 4)
'Write the number found into the number array
NumberArray(NumberCount) = NumFound
NumberCount = NumberCount + 1
Else
'Write the number found into the number array
NumberArray(NumberCount) = NumFound
NumberCount = NumberCount + 1
End If
Next i
'Write the ws name into the worksheet and write the number found to the cell to the right of the ws name in the worksheet
For i = LBound(WSNames) To UBound(WSNames)
Cells(j, Col_Names) = WSNames(i,1)
Cells(j, Col_Result) = NumberCount(i,1)
j = j + 1
Next i
End Sub
Sub Runcmd(awsname)
Dim PathToBatch as string
'Set the path and batch file with the ws name as a parameter for the batch to run
PathToBatch = "D:\min425.cmd" & " " & awsname
Call Shell(PathToBatch, vbNormalFocus)
End Sub
The main changes are using a FileSystemObject to read the text files, a Regular Expression to extract the error code, and a WScript.Shell object to run the batch file so macro waits for the script to complete. I have commented out the RunCmd line and replaced it with a RunTest that creates a text file so you can test it.
Option Explicit
Sub Check_Files()
Const DIR_OUT = "D:\Minstall"
Const COL_NAMES = "A"
Const COL_RESULTS = "B"
Const COL_TS = "C" ' timestamp
Const COL_ERR = "D" ' Shell errors
Const ROW_START = 4 'first line of text and result
Dim wb As Workbook, ws As Worksheet
Dim rng As Range, arNames, awsname As String
Dim result As String, txtfile As String
Dim i As Long, LastRow As Long, n As Long, r As Long, colour As Long
Dim t0 As Single: t0 = Timer
Set wb = ThisWorkbook
Set ws = wb.Sheets("Main")
With ws
' read names into array
LastRow = .Cells(.Rows.Count, COL_NAMES).End(xlUp).Row
n = LastRow - ROW_START + 1
If n < 1 Then
MsgBox "No records found on " & ws.Name, vbCritical
Exit Sub
Else
Set rng = .Cells(ROW_START, COL_NAMES).Resize(n)
arNames = rng.Value2
'Write how many names were read into array
.Cells(1, 3) = "Number of names read into array is " & n
End If
' clear results
With rng.Offset(, 1).Resize(, 3)
.Clear
.Interior.Pattern = xlNone
End With
' run commands with WsSCript
Dim WShell As Object
Set WShell = CreateObject("WScript.Shell")
For i = 1 To UBound(arNames)
awsname = arNames(i, 1)
r = ROW_START + i - 1
' RUN COMMANDS
.Cells(r, COL_ERR) = RunTest(awsname, DIR_OUT)
'.Cells(r, COL_ERR) = RunCmd(WShell, awsname, DIR_OUT)
.Cells(r, COL_TS) = Format(Now, "yyyy-mm-dd HH:MM:SS") ' timestamp
Next
Set WShell = Nothing
'Write how many batch files were run into worksheet
.Cells(2, 3) = "Number of batch files run is " & UBound(arNames)
' read text files with FSO, parse with regex
Dim FSO As Object, ts As Object, regex As Object, txt As String
Set FSO = CreateObject("Scripting.FileSystemObject")
Set regex = CreateObject("VBScript.RegExp")
With regex
.Global = False
.MultiLine = True
.IgnoreCase = True
.Pattern = "with error code (\d+)"
End With
n = 0
' process text file
For i = 1 To UBound(arNames)
r = ROW_START + i - 1
awsname = arNames(i, 1)
txtfile = DIR_OUT & awsname & ".txt"
result = ""
' does file exist for this machine
If FSO.fileExists(txtfile) Then
' read file
n = n + 1
Set ts = FSO.openTextfile(txtfile)
txt = ts.readall
ts.Close
' extract error number from text
If regex.test(txt) Then
result = regex.Execute(txt)(0).submatches(0)
End If
' error codes
If result = "0" Then
colour = RGB(0, 255, 0) ' green
Else
colour = RGB(255, 255, 0) ' yellow
End If
Else
result = "No Text File"
colour = RGB(255, 0, 0) ' red
End If
' result
With .Cells(r, COL_RESULTS)
.Value2 = result
.Interior.Color = colour
End With
Next
.Cells(3, 3) = "Number of txt files found is " & n
.Columns.AutoFit
End With
MsgBox "Text files found for " & n, vbInformation, Format(Timer - t0, "0.0 secs")
End Sub
Function RunTest(awsname As String, folder As String) As String
Dim FSO, ts, errno: Set FSO = CreateObject("Scripting.FileSystemObject")
If Rnd() < 0.3 Then errno = 0 Else errno = Int(10000 * Rnd())
Set ts = FSO.createTextFile(folder & awsname & ".txt")
ts.write "This is with error code " & errno & "." & vbCrLf & vbCrLf
ts.Close
RunTest = "Test"
End Function
Function RunCmd(WShell, awsname As String, folder As String) As String
MsgBox "RunCmd DISABLED", vbCritical: End
'Const SCRIPT = "D:\min425.cmd"
'Dim cmd: cmd = SCRIPT & " " & awsname
'RunCmd = WShell.Run(cmd, vbNormal, True) ' waittocomplete
End Function

Changing delimiter in a loaded csv file

I have loaded a csv file in memory. My csv file uses ";" as the field delimiter.
It seems vba default delimiter is "," because when I try to access certain row and column of the loaded csv file, vba advances through the elements with refrence to number of "," used.
example:
In the 10th row of my data there are five columns: aa 12,34 bb 5,678 (here "," is decimal separator)
in the csv file which the delimiter is ";" it looks like this:
aa;12,34;bb;5,678
so when I write
MyData(10,2)
I am expecting to get 12,34 but vba returns 34;bb;5 because it uses "," as field delimiter.
So my question:
How can I tell vba to search through the loaded csv file with respect to ";" as delimiter instead of ","?
Thanks.
Instead of trying to change the delimiter which excel uses to load a csv file it might be more straightforward to do that on your own
First you use a function to load the lines of a text file into a collection and then you access the wanted line in that collection and go to the wanted column.
Code for this
Option Explicit
Function txtfileinCol(filename As String) As Collection
' loads the content of a textfile line by line into a collection
Dim fileContent As Collection
Set fileContent = New Collection
Dim fileNo As Long
Dim txtLine As String
fileNo = FreeFile
Open filename For Input As #fileNo
Do Until EOF(fileNo)
Line Input #fileNo, txtLine
fileContent.Add txtLine
Loop
Close #fileNo
Set txtfileinCol = fileContent
End Function
Sub Testit()
Const DELIMITER = ";"
Dim filename As String
Dim col As Collection
Dim vdat As Variant
Dim colNo As Long
Dim rowNo As Long
filename = "C:\Temp\FILE.csv"
Set col = txtfileinCol(filename)
colNo = 2
rowNo = 10
vdat = col.Item(rowNo) 'here you get the line you want
vdat = Split(vdat, DELIMITER) ' now you split the line with the DELIMITER you define
Debug.Print vdat(colNo - 1) ' now you print the content of the column you want
End Sub
Update: For accessing the row and column you could also use a function. The code would look like that
Option Explicit
Function txtfileinCol(filename As String) As Collection
' loads the content of a textfile line by line into a collection
Dim fileContent As Collection
Set fileContent = New Collection
Dim fileNo As Long
Dim txtLine As String
fileNo = FreeFile
Open filename For Input As #fileNo
Do Until EOF(fileNo)
Line Input #fileNo, txtLine
fileContent.Add txtLine
Loop
Close #fileNo
Set txtfileinCol = fileContent
End Function
Function getColRow(fileLines As Collection, rowNo As Long, colNo As Long, Optional delimiter As String) As String
Dim vdat As Variant
On Error GoTo EH:
If Len(delimiter) = 0 Then
delimiter = ";"
End If
vdat = fileLines.Item(rowNo) 'here you get the line
vdat = Split(vdat, delimiter) 'now you split the line with the delimiter
getColRow = vdat(colNo - 1) 'now you retrieve the content of the column
Exit Function
EH:
getColRow = ""
End Function
Sub Testit()
Dim filename As String
Dim col As Collection
filename = "C:\Temp\FILE.csv"
Set col = txtfileinCol(filename)
Debug.Print getColRow(col, 10, 2, ";")
End Sub

VBA read file from specific string to end of file

I am trying to use Microsoft Scripting Runtime to open a text file, look for a specific string of text, and then copy that line and everything below it until the end of the file and write that to excel. I don't need it formatted by column, just want it to appear as it is in the file.. Below is the code that I'm trying to use but I think I've made a few errors.
Sub readFile()
Dim sFileName As String
sFileName = "C:\Users\Jamie\Desktop\REPORT.txt"
With New Scripting.FileSystemObject
With .OpenTextFile(sFileName, ForWriting)
If Mid(sFileName, 3, 6) = "PALLET" Then
.ReadAll
Do Until .AtEndOfStream
Loop
End If
End With
End With
End Sub
Here is an example of the REPORT.TXT
RANDOM DATA THAT'S NOT NEEDEDRANDOM DATA THAT'S NOT NEEDED
RANDOM DATA THAT'S NOT NEEDED
RANDOM DATA THAT'S NOT NEEDED
RANDOM DATA THAT'S NOT NEEDEDRANDOM DATA THAT'S NOT NEEDEDRANDOM DATA THAT'S NOT NEEDED
RANDOM DATA THAT'S NOT NEEDED
PALLET INFORMATION
=================================
UNDER 5 HRS 5
6 to 10 HRS 20
11 to 15 HRS 45
OVER 20 HRS 12
=================================
Report Generated on 2/12/19 by IBM z/OS JBL.9897992
Here's your code refactored to achieve what you want. It mainly shows how to use the FileSystemObject to read text files. I suspect you'll want to make changes once you get to grips with reading the file data, to make placing the data into the sheet easier.
Version 1 - if file is small enough to read into a single string
Sub readFile()
Dim sFileName As String
Dim FileData As String
Dim PalletData As String
Dim idx As Long
Dim LocationToPlaceData As Range
sFileName = "C:\Data\Temp\Report.txt" '"C:\Users\Jamie\Desktop\REPORT.txt"
With New Scripting.FileSystemObject
With .OpenTextFile(sFileName, ForReading)
FileData = .ReadAll
.Close
End With
End With
idx = InStr(FileData, "PALLET")
If idx > 0 Then
PalletData = Mid$(FileData, idx)
'get location to place data - update to suit your needs
Set LocationToPlaceData = ActiveSheet.Range("A1")
'Place Data in a single cell
LocationToPlaceData = PalletData
End If
End Sub
Version 2 - if file is too big to read into a single string.
Sub readFile2()
Dim sFileName As String
Dim FileLine As String
Dim PalletData As String
Dim idx As Long
Dim LocationToPlaceData As Range
sFileName = "C:\Data\Temp\Report.txt" '"C:\Users\Jamie\Desktop\REPORT.txt"
With New Scripting.FileSystemObject
With .OpenTextFile(sFileName, ForReading)
Do Until .AtEndOfStream
FileLine = .ReadLine
idx = InStr(FileLine, "PALLET")
If idx > 0 Then
PalletData = Mid$(FileLine, idx)
Do Until .AtEndOfStream
PalletData = PalletData & vbCrLf & .ReadLine
Loop
End If
Loop
.Close
End With
End With
'get location to place data - update to suit your needs
Set LocationToPlaceData = ActiveSheet.Range("A1")
'Place Data in a single cell
LocationToPlaceData = PalletData
End Sub

trying to store text file rows in VBA

Greetings, I'm hoping for help in figuring out how to store each row of a text file read into a VBA program as a string. I want to modify one of the strings and then put them all back together, but do not know how to read through a text file and store each row as a separate variable in an intelligent way. Thanks for any help you can provide!
If you don't want to add references, you could just go with straight vba code.
Take for instance the following file wordlist.txt:
realize
empty
theorize
line
socialize
here
analyze
The following code uses two methods to do as you described (one more common than the other):
Option Explicit
Sub main()
Dim sFileName As String
Dim sMergedLineArray() As String
Dim sTextToFind As String
Dim sReplacementText As String
Dim sOutputFile As String
Const MY_DELIMITER = "|"
sFileName = "C:\deleteme\wordlist.txt"
sMergedLineArray = ReadFileIntoArray(sFileName)
sTextToFind = "ze"
sReplacementText = "se"
'Loop through each value in the array and make a change if you need to
Dim x As Integer
For x = 0 To UBound(sMergedLineArray)
If InStr(1, sMergedLineArray(x), sTextToFind, vbTextCompare) > 0 Then
sMergedLineArray(x) = Replace(sMergedLineArray(x), sTextToFind, sReplacementText, 1, -1, vbTextCompare)
End If
Next x
sOutputFile = "C:\deleteme\UK_Version.txt"
If Not SpitFileOut(sOutputFile, sMergedLineArray) Then
MsgBox "It didn't work :("
End If
'OR...put it all together, make a mass change and split it back out (this seems unlikely, but throwing it in there anyway)
sTextToFind = "se"
sReplacementText = "ze"
Dim sBigString As String
Dim sNewArray As Variant
sBigString = Join(sMergedLineArray, MY_DELIMITER)
sBigString = Replace(sBigString, sTextToFind, sReplacementText, 1, -1, vbTextCompare)
sNewArray = Split(sBigString, MY_DELIMITER, -1, vbTextCompare)
sOutputFile = "C:\deleteme\American_Version.txt"
If Not SpitFileOut(sOutputFile, sNewArray) Then
MsgBox "It didn't work"
End If
MsgBox "Finished!"
End Sub
Function ReadFileIntoArray(sFileName As String) As String()
Dim sText As String
Dim sLocalArray() As String
Dim iFileNum As Integer
Dim iLineCount As Integer
iFileNum = FreeFile
Open sFileName For Input As #iFileNum
Do Until EOF(iFileNum)
Input #iFileNum, sText
ReDim Preserve sLocalArray(iLineCount)
sLocalArray(iLineCount) = sText
iLineCount = iLineCount + 1
Loop
Close #iFileNum
ReadFileIntoArray = sLocalArray
End Function
Function SpitFileOut(sFileName As String, sMyArray As Variant) As Boolean
Dim iFileNum As Integer
Dim iCounter As Integer
SpitFileOut = False
iFileNum = FreeFile
Open sFileName For Output As #iFileNum
For iCounter = 0 To UBound(sMyArray)
Print #iFileNum, sMyArray(iCounter)
Next
Close #iFileNum
SpitFileOut = True
End Function
If you run the main sub, you'll end up with two files:
UK_Version.txt: This is the result of the first method
American_Version.txt: This is the result of the second
There's lesson 1 of VBA, young Padawan; absorb it, learn and change your login name :P
Look into the FileSystemObject (ref: 1, 2, 3)
You have to go to <Tools/References> menu and include the Microsoft Scripting Runtime and create a global variable Global fso as New FileSystemObject. Now anywhere in your code do things like fso.OpenTextFile() which returns a TextStream. Each TextStream has methods loke ReadLine(), ReadAll(), SkipLine(), WriteLine(), etc ...
Here is a quick sample code.
Global fso as New FileSystemObject
Sub TEST()
Dim ts As TextStream
Set ts = fso.OpenTextFile("text_file.txt", ForReading, False)
Dim s As String
s = ts.ReadAll()
End Sub

Resources