VB.NET / First row missing when writing to a CSV file - vb.net

Wrote a program that writes name, postcode, device type, damage type to a CSV file and for some reason it misses the first row.
Thanks
Sub fileWriting(ByVal file As String, ByVal name As String, ByVal postcode As String, ByVal dmgType As String, ByVal devType As String) ' writing to files function
Dim ObjStreamWriter As StreamWriter
ObjStreamWriter = New StreamWriter(file, True)
ObjStreamWriter.Write(name & ",")
ObjStreamWriter.Write(postcode & ",")
ObjStreamWriter.Write(dmgType & ",")
ObjStreamWriter.WriteLine(devType)
ObjStreamWriter.Close()
End Sub

One point on the code above:
ObjStreamWriter = New StreamWriter(file, True)
will append to an existing file, if the file already exists. Therefore the text you're appending may exist further down in the file depending how much you've already written.
Additionally, StreamWriter implements IDisposable so the preferred method of implementation is to instantiate it in a Using block, if you're immediately closing the object.
Using ObjStreamWriter = New StreamWriter(file, True)
ObjStreamWriter.Write(name & ",")
ObjStreamWriter.Write(postcode & ",")
ObjStreamWriter.Write(dmgType & ",")
ObjStreamWriter.WriteLine(devType)
End Using
I'm not sure if these will fix your issue as there's insufficient code to really determine that, but if you're having issues with text not flushing, this ensures that you're automatically doing everything needed to properly close the file. Normally that'd be text missing from the end of the file, not the start, but it might help.

Related

Excel VBA bug accessing HelpFile property from macro-disabled instance?

I think I've stumbled upon a bug in Excel - I'd really like to verify it with someone else though.
The bug occurs when reading the Workbook.VBProject.HelpFile property when the workbook has been opened with the opening application's .AutomationSecurity property set to ForceDisable. In that case this string property returns a (probably) malformed Unicode string, which VBA in turn displays with question marks. Running StrConv(..., vbUnicode) on it makes it readable again, but it sometimes looses the last character this way; this might indicate that the unicode string is indeed malformed or such, and that VBA therefore tries to convert it first and fails.
Steps to reproduce this behaviour:
Create a new Excel workbook
Go to it's VBA project (Alt-F11)
Add a new code module and add some code to it (like e.g. Dim a As Long)
Enter the project's properties (menu Tools... properties)
Enter "description" as Project description and "abc.hlp" as Help file name
Save the workbook as a .xlsb or .xlsm
Close the workbook
Create a new Excel workbook
Go to it's VBA project (Alt-F11)
Add a fresh new code module
Paste the code below in it
Adjust the path on the 1st line so it points to the file you created above
Run the Test routine
The code to use:
Const csFilePath As String = "<path to your test workbook>"
Sub TestSecurity(testType As String, secondExcel As Application, security As MsoAutomationSecurity)
Dim theWorkbook As Workbook
secondExcel.AutomationSecurity = security
Set theWorkbook = secondExcel.Workbooks.Open(csFilePath)
Call MsgBox(testType & " - helpfile: " & theWorkbook.VBProject.HelpFile)
Call MsgBox(testType & " - helpfile converted: " & StrConv(theWorkbook.VBProject.HelpFile, vbUnicode))
Call MsgBox(testType & " - description: " & theWorkbook.VBProject.Description)
Call theWorkbook.Close(False)
End Sub
Sub Test()
Dim secondExcel As Excel.Application
Set secondExcel = New Excel.Application
Dim oldSecurity As MsoAutomationSecurity
oldSecurity = secondExcel.AutomationSecurity
Call TestSecurity("enabled macros", secondExcel, msoAutomationSecurityLow)
Call TestSecurity("disabled macros", secondExcel, msoAutomationSecurityForceDisable)
secondExcel.AutomationSecurity = oldSecurity
Call secondExcel.Quit
Set secondExcel = Nothing
End Sub
Conclusion when working from Excel 2010:
.Description is always readable, no matter what (so it's not like all string properties behave this way)
xlsb and xlsm files result in an unreadable .HelpFile only when macros are disabled
xls files result in an unreadable .HelpFile in all cases (!)
It might be even weirder than that, since I swear I once even saw the questionmarks-version pop up in the VBE GUI when looking at such a project's properties, though I'm unable to reproduce that now.
I realize this is an edge case if ever there was one (except for the .xls treatment though), so it might just have been overlooked by Microsoft's QA department, but for my current project I have to get this working properly and consistently across Excel versions and workbook formats...
Could anyone else test this as well to verify my Excel installation isn't hosed? Preferably also with another Excel version, to see if that makes a difference?
Hopefully this won't get to be a tumbleweed like some of my other posts here :) Maybe "Tumbleweed generator" might be a nice badge to add...
UPDATE
I've expanded the list of properties to test just to see what else I could find, and of all the VBProject's properties (BuildFileName, Description, Filename, HelpContextID, HelpFile, Mode, Name, Protection and Type) only .HelpFile has this problem of being mangled when macros are off.
UPDATE 2
Porting the sample code to Word 2010 and running that exhibits exactly the same behaviour - the .HelpFile property is malformed when macros are disabled. Seems like the code responsible for this is Office-wide, probably in a shared VBA library module (as was to be expected TBH).
UPDATE 3
Just tested it on Excel 2007 and 2003, and both contain this bug as well. I haven't got an Excel XP installation to test it out on, but I can safely say that this issue already has a long history :)
I've messed with the underlying binary representation of the strings in question, and found out that the .HelpFile string property indeed returns a malformed string.
The BSTR representation (underwater binary representation for VB(A) strings) returned by the .HelpFile property lists the string size in the 4 bytes in front of the string, but the following content is filled with the ASCII representation and not the Unicode (UTF16) representation as VBA expects.
Parsing the content of the BSTR returned and deciding for ourselves which format is most likely used fixes this issue in some circumstances. Another issue is unfortunately at play here as well: it only works for even-length strings... Odd-length strings get their last character chopped off, their BSTR size is reported one short, and the ASCII representation just doesn't include the last character either... In that case, the string cannot be recovered fully.
The following code is the example code in the question augmented with this fix. The same usage instructions apply to it as for the original sample code. The RecoverString function performs the needed magic to, well, recover the string ;) DumpMem returns a 50-byte memory dump of the string you pass to it; use this one to see how the memory is layed out exactly for the passed-in string.
Const csFilePath As String = "<path to your test workbook>"
Private Declare Sub CopyMemoryByte Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByVal Source As Long, ByVal Length As Integer)
Private Declare Sub CopyMemoryWord Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByVal Source As Long, ByVal Length As Integer)
Private Declare Sub CopyMemoryDWord Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Long, ByVal Source As Long, ByVal Length As Integer)
Function DumpMem(text As String) As String
Dim textAddress As LongPtr
textAddress = StrPtr(text)
Dim dump As String
Dim offset As Long
For offset = -4 To 50
Dim nextByte As Byte
Call CopyMemoryByte(nextByte, textAddress + offset, 1)
dump = dump & Right("00" & Hex(nextByte), 2) & " "
Next
DumpMem = dump
End Function
Function RecoverString(text As String) As String
Dim textAddress As LongPtr
textAddress = StrPtr(text)
If textAddress <> 0 Then
Dim textSize As Long
Call CopyMemoryDWord(textSize, textAddress - 4, 4)
Dim recovered As String
Dim foundNulls As Boolean
foundNulls = False
Dim offset As Long
For offset = 0 To textSize - 1
Dim nextByte As Byte
Call CopyMemoryByte(nextByte, textAddress + offset, 1)
recovered = recovered & Chr(CLng(nextByte) + IIf(nextByte < 0, &H80, 0))
If nextByte = 0 Then
foundNulls = True
End If
Next
Dim isNotUnicode As Boolean
isNotUnicode = isNotUnicode Mod 2 = 1
If foundNulls And Not isNotUnicode Then
recovered = ""
For offset = 0 To textSize - 1 Step 2
Dim nextWord As Integer
Call CopyMemoryWord(nextWord, textAddress + offset, 2)
recovered = recovered & ChrW(CLng(nextWord) + IIf(nextWord < 0, &H8000, 0))
Next
End If
End If
RecoverString = recovered
End Function
Sub TestSecurity(testType As String, secondExcel As Application, security As MsoAutomationSecurity)
Dim theWorkbook As Workbook
secondExcel.AutomationSecurity = security
Set theWorkbook = secondExcel.Workbooks.Open(csFilePath)
Call MsgBox(testType & " - helpfile: " & theWorkbook.VBProject.HelpFile & " - " & RecoverString(theWorkbook.VBProject.HelpFile))
Call MsgBox(testType & " - description: " & theWorkbook.VBProject.Description & " - " & RecoverString(theWorkbook.VBProject.Description))
Call theWorkbook.Close(False)
End Sub
Sub Test()
Dim secondExcel As Excel.Application
Set secondExcel = New Excel.Application
Dim oldSecurity As MsoAutomationSecurity
oldSecurity = secondExcel.AutomationSecurity
Call TestSecurity("disabled macros", secondExcel, msoAutomationSecurityForceDisable)
Call TestSecurity("enabled macros", secondExcel, msoAutomationSecurityLow)
secondExcel.AutomationSecurity = oldSecurity
Call secondExcel.Quit
Set secondExcel = Nothing
End Sub

How to find or get files in Directory with Specific word in the file name Visual Basic.net?

I need to get files from a directory containing specific characters in it's name:
The following code below will return any file with the .csv extension. The problem is there are other csv file I need to leave alone or not get.
Dim FileLocation As DirectoryInfo = _
New DirectoryInfo("C:\Folder\Subfolder\Data\Input\")
Dim fi As FileInfo() = FileLocation.GetFiles("*.csv")
Instead of getting any csv file, I would like to get a file with the word data, so any file name containing the word data. Example: *my_data_file.csv*
How do I do this with the code above?
You can update the filter with the string you want to account for (caps will automatically be taken care of):
Dim fi As FileInfo() = FileLocation.GetFiles("*data*.csv")
In any case, bear in mind that this filtering is not "too accurate". For example, the code above would also account for any file (including "data"), whose extension includes csv (e.g., *.csva, *.csvb, etc.). If you want a 100%-reliable approach you should better set up a loop and carry out the filtering "manually"; loops are pretty fast and you wouldn't even notice the difference.
Example of a loop:
Dim fi As List(Of FileInfo) = New List(Of FileInfo)
For Each File In FileLocation.GetFiles()
If (File IsNot Nothing) Then
If (Path.GetExtension(File.ToString.ToLower) = ".csv") Then
If (File.ToString.ToLower.Contains("data")) Then fi.Add(File)
End If
End If
Next
This code will work for sure under your exact requirements and might take care of more complex requests. I have accounted for a List just to show the point clearer.
If you can use LINQ extensions then you can do it this way:
' Get Files {directory} {recursive} {ext} {word in filename}
Private Function Get_Files(ByVal directory As String, _
ByVal recursive As IO.SearchOption, _
ByVal ext As String, _
ByVal with_word_in_filename As String) As List(Of IO.FileInfo)
Return IO.Directory.GetFiles(directory, "*" & If(ext.StartsWith("*"), ext.Substring(1), ext), recursive) _
.Where(Function(o) o.ToLower.Contains(with_word_in_filename.ToLower)) _
.Select(Function(p) New IO.FileInfo(p)).ToList
End Function
Usage example:
For Each file As IO.FileInfo In Get_Files("C:\Folder\Subfolder\Data\Input\", _
IO.SearchOption.TopDirectoryOnly, _
"csv", _
"data")
MsgBox(file.Name)
Next
Replace the wildcard search below "." with your search criteria, for example you want all files that start with name "Hospital*"
Dim Folder As New IO.DirectoryInfo("C:\SampleFolder")
For Each File as IO.FileInfo in Folder.GetFiles("*.*",IO.SearchOption.AllDirectories)
ListBox1.Items.Add(File.FullName)
Next
I would have added this as a comment to the accepted answer, but I do not have enough points to do so:
I just wanted to add varocarbas's answer that, if anyone was wondering (as I was) if this would work in a web scenario as well, it will. Just place the web path inside Server.MapPath() like this:
Dim FileLocation As DirectoryInfo =
New DirectoryInfo(Server.MapPath("/Folder/SubFolder/Data/Input/"))
NOTE: Will NOT work with full url's (no 'http://www.123.com').
Dim Folder As New IO.DirectoryInfo("C:\SampleFolder")
For Each File as IO.FileInfo in Folder.GetFiles("*.*",IO.SearchOption.AllDirectories)
ListBox1.Items.Add(File.FullName)
Application.DoEvents()
Next

How to get the file name of a file in VB?

I make a search program for searching a list of files in a computer and then copy the file into a store folder. The file name could be "*11*2.txt" As long as the program find this pattern, it should copy to the store folder. The problem is that I don't know the exactly name of the file before the search and I don't want to rename the file, I don't know how to save the file. Please help
I use the following to find the file, which does its work
Public Sub DirSearch(ByVal sDir As String, ByVal FileName As String)
Dim To_Path As String
To_Path = Form1.TextBox5.Text
For Each foundFile As String In My.Computer.FileSystem.GetFiles(sDir, FileIO.SearchOption.SearchAllSubDirectories, FileName)
Copy2Local(foundFile, To_Path)
Next
End Sub
Here is the current version of the Copy2Local (Note: it is not working right)
Public Sub Copy2Local(ByVal Copy_From_Path As String, ByVal Copy_To_Path As String)
' Specify the directories you want to manipulate.
Try
Dim fs As FileStream = File.Create(Copy_From_Path)
fs.Close()
' Copy the file.
File.Copy(Copy_From_Path, Copy_To_Path)
Catch
End Try
End Sub
First, you should check if ToPath is a valid directory since it's coming from a TextBox:
Dim isValidDir = Directory.Exists(ToPath)
Second, you can use Path.Combine to create a path from separate (sub)directories or file-names:
Dim copyToDir = Path.GetDirectoryName(Copy_To_Path)
Dim file = Path.GetFileName(Copy_From_Path)
Dim newPath = Path.Combine(copyToDir, file)
http://msdn.microsoft.com/en-us/library/system.io.path.aspx
(disclaimer: typed from a mobile)
To answer your question: You can get the file name with Path.GetFileName. Example:
Dim fileName As String = Path.GetFileName(foundFile)
However, there's a bunch of other things wrong with your code:
Here,
Dim fs As FileStream = File.Create(Copy_From_Path)
fs.Close()
you are overwriting your source file. This does not seem like a good idea. ;-)
And here,
Try
...
Catch
' Do Nothing
End Try
You are throwing away exceptions that would help you find and diagnose problems. Don't do that. It makes debugging a nightmare.
In vb.net, I'm using the following code to find the filename
Textbox1.Text = New FileInfo(OpenFileDialog.FileName).Name
this code work fine with open file dialog box

Write a variable to a file that has a different type than the function assigned to the variable

I have the following code that I am using to parse out a test file. I am getting variable conversion error in Sub Main() when I assign file = Read(). The return value of Read() is a TextFieldParser type. How do I assign the proper variable type to "file" so I can write the output to a text file?
Thanks!
Module Module1
Function Read()
Using MyReader As New FileIO.TextFieldParser("C:\Users\Colin\Desktop\Parse_Me.txt")
Dim currentRow As String
While Not MyReader.EndOfData
Try
currentRow = MyReader.ReadLine()
Console.WriteLine(Parse_me(currentRow))
Catch ex As FileIO.MalformedLineException
MsgBox("Line " & ex.Message &
" is invalid. Skipping")
End Try
End While
Return MyReader
MyReader.Close()
End Using
End Function
Function Parse_me(ByVal test As String)
Dim Set_1, Set_2, Set_3, Set_4, Set_5 As String
Dim new_string As String
Set_1 = test.Substring(0, 4)
Set_2 = test.Substring(7, 2)
Set_3 = test.Substring(11, 1)
Set_4 = test.Substring(14, 4)
Set_5 = test.Substring(20, 4)
new_string = Set_1 & " " & Set_2 & " " & Set_3 & " " & Set_4 & " " & Set_5
Return new_string
End Function
Sub Main()
Dim file As Object
file = Read()
FilePutObject("C:\Users\Colin\Desktop\Parse_Meoutput.txt", file)
End Sub
End Module
Here's how FilePutObject is supposed to work (example taken from MSDN documentation for FilePutObject):
Sub WriteData()
Dim text As String = "test"
FileOpen(1, "test.bin", OpenMode.Binary)
FilePutObject(1, text)
FileClose(1)
End Sub
The 1 act as an identifier for the file. Note also that the file name is passed to FileOpen before calling FilePutObject, and that FileClose is called afterwards. Also note that a string is being written to the file. I don't know which types of data are valid for being passed to FilePutObject, but FileIO.TextFieldParser is definitely not one of them (I just tried it).
Correct me if I'm wrong, but I'm pretty sure that FilePutObject is one of those carry-overs from VB6. If you're writing new code, I would rather use a Stream object for my I/O. For one, it's a lot more .Net-ish (i.e., type-safe, object-oriented, etc). And as far as usability goes, it's a lot clearer how a Stream works, not to mention it doesn't involve passing arbitrary integers as handles to functions in order to identify which file you'd like to work with. And to top it all off, a Stream works whether you want to write to a file, to the console, or send the data to another machine. To sum up, I would definitely look up the Stream class, some of its child classes (like FileStream, and whatever else appeals to you), and some associated types (such as the TextWriter class for conveniently writing text).
Change the definition of the function "read" to:
Function Read() as FileIO.TextFieldParser
and change the declaration of "file" in sub main to:
Dim file as FileIO.TextFieldParser
That way the data type of the function and assignment match.

how do I put contents of C: into an array?

Am learning arrays at the moment and I have the below piece of code that goes through drive C: and displays the files in in a list box.
I want to try and expand it to use array.sort so that it gets the files, puts them into an array, and then I can sort by filename or file size. I have been rattling my brain over this - as to how do I put the files into an array.
Would like an explanation if possible as more interested in learning it rather than the answer.
Thanks!
Private Sub btnclick_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnclick.Click
Call Clearlist()
Dim strFilesinfo As System.IO.FileInfo
Dim strlength As Double = 0
Dim strname As String = ""
For Each strFiles As String In My.Computer.FileSystem.GetFiles("c:\")
strFilesinfo = My.Computer.FileSystem.GetFileInfo(strFiles)
strlength = strFilesinfo.Length
strname = strFilesinfo.Name
lstData.Items.Add(strname & " " & strlength.ToString("N0"))
Next
End Sub
End Class
To allow the data to be sortable, you'd need to be displaying something that could treat that information separately (i.e. a class or structure). You might also find that a different type of control, such as a DataGridView might be easier to get to grips with.
The .Net framework does define an interface, IBindingList which collections can implement to show that they report, amongst other things, sorting.
I'm providing this as a sample for learning purposes but it should not be used as-is. Getting every file from the entire C:\ should not be done like this. Aside from the performance issues there are windows security limitations that won't actually let you do this.
The FileList being populated here is getting just the TopDirectoryOnly. If you change that input to "AllDirectories" it will get all the subdirectories but it will fail as I stated before.
Dim path As String = "C:\"
Dim dir As New System.IO.DirectoryInfo(path)
Dim fileList = dir.GetFiles("*.*", IO.SearchOption.TopDirectoryOnly)
Dim fileSort = (From file In fileList _
Order By file.Name _
Select file.Name, file.Length).ToList
For Each file In fileSort
With file
lstData.Items.Add(String.Format("{0} {1}", .Name, .Length.ToString("N0")))
End With
Next file
Just change the Order By in the LINQ query to change how the sorting is done. There are many other ways to do the sorting but LINQ will handle it for you with very little code.