Writing To Text File, File In Use By Another Process, Multithreaded Application .NET - vb.net

Afternoon,
I have a program whereby I really need to be keeping a log of some kind to ascertain what the program is doing. Essentially the software monitors for a window on the desktop to live pause a call recording on our Call Recorder Server.
Lets on argument say a Call Recording itself was pulled up by a monitoring agent and they state that a certain sensitive part of the conversation has been recorded when really it should have been silenced, if they were to say the agent hadn't done their job and clicked the pause recording button OR the onfocus action hadn't occurred I would have a situation whereby I would need to prove what the software was doing at the same.
I decided that I would write the actions of the software to a .txt file stored in the users app data.
This works for the most part, however every now and then even though the .txt is never accessed by any other program I get 'This file is in use by another process'.
This application is multithreaded and does make very frequent calls to write to the log, I am using the below code:
Private Sub WriteToLog(ByVal strSubTitle As String, ByVal strLogInfo As String)
Try
If My.Computer.FileSystem.FileExists(strLogFilePath) = True Then
'Delete yesterdays log file
Dim strFileDate As Date = File.GetCreationTime(strLogFilePath)
strFileDate = FormatDateTime(strFileDate, DateFormat.ShortDate)
'If strFileDate < Date.Today Then
' My.Computer.FileSystem.DeleteFile(strLogFilePath)
'End If
Using outFile As IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(strLogFilePath, True)
outFile.WriteLine("" & Date.Today & "," & Date.Now.ToLongTimeString & ", " & strUsername & ", " & strSubTitle & ", " & Replace(strLogInfo, ",", "|") & "")
outFile.Close()
End Using
''CSV File
'outFile.WriteLine("" & Date.Today & "," & Date.Now.ToLongTimeString & ", " & strUsername & ", " & strSubTitle & ", " & Replace(strLogInfo, ",", "|") & "")
'outFile.Close()
Else
Using outFile As IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(strLogFilePath, False)
outFile.WriteLine("" & Date.Today & "," & Date.Now.ToLongTimeString & ", " & strUsername & ", " & strSubTitle & ", " & Replace(strLogInfo, ",", "|") & "")
outFile.Close()
End Using
'CSV File
'outFile.WriteLine("Date, Time, Username, Sub(Process), Information")
'outFile.WriteLine("" & Date.Today & "," & Date.Now.ToLongTimeString & ", " & strUsername & ", " & strSubTitle & ", " & Replace(strLogInfo, ",", "|") & "")
'outFile.Close()
End If
Catch ex As Exception
CreateErrorFile(ex.Message, ex.StackTrace, "Log Write Failure!")
End Try
End Sub
Is there any advice/pointers someone could state as to why this would be saying another process is using the file.
I'm guessing the situation would occur when two separate threads try to do the 'WriteToLog' Sub while one or the other is writing to the file.
Am I on the right tracks? If so how could I rectify this?
Cheers,
James

You will either want to make it so that only one thread has the ability to write to the log file by using the Invoke method from the secondary thread(s) to call the write functionality on the main thread, or you can use one of .NET's various synchronization mechanisms.
Here is a simple example of the first approach:
Private Sub BackgroundMethod()
'do stuff
Me.Invoke(New Action(Of String)(AddressOf WriteToLog), "write a line blah blah")
'do more stuff
End Sub
Private Sub WriteToLog(valueToWrite As String)
System.IO.File.AppendAllLines(MyLogFilePath, {valueToWrite})
End Sub
Here's an example using a SyncLock block:
Private lock As New Object()
Private Sub BackgroundMethod()
'do stuff
WriteToLog("write a line blah blah")
'do more stuff
End Sub
Private Sub WriteToLog(valueToWrite As String)
SyncLock (lock)
System.IO.File.AppendAllLines(MyLogFilePath, {valueToWrite})
End SyncLock
End Sub
MSDN has good information on synchronization mechanisms: http://msdn.microsoft.com/en-us/library/ms228964%28v=vs.110%29.aspx

I would use a global Queue where new entries get added to and if the log writer is not busy then start it to write down all available lines.
sth like:
Private LogList As New Queue(Of String)
Private WriterBusy As Boolean = False
Private Sub WriteToLog(ByVal strSubTitle As String, ByVal strLogInfo As String)
LogList.Enqueue("" & Date.Today & "," & Date.Now.ToLongTimeString & ", " & strUsername & ", " & strSubTitle & ", " & Replace(strLogInfo, ",", "|") & "")
If WriterBusy = True Then Exit Sub
WriterBusy = True
Try
If My.Computer.FileSystem.FileExists(strLogFilePath) = True Then
...
Using outFile As IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(strLogFilePath, True)
While LogList.Count > 0
outFile.WriteLine(LogList.Dequeue)
End While
outFile.Close()
End Using
...
End Try
WriterBusy = False
End Sub

Related

Application freeze when I use an external library

My application contains a main file allowing to generate an executable using Codedom. This executable is make from my file Source.vb.
I would like to use an external library in the latter. In particular, this is Rebex: this will allow me to download a file from a server sFTP.
Here are my Codedom settings:
Public Shared Function compile_Stub(ByVal input As String, ByVal output As String, ByVal resources As String, ByVal showError As Boolean, Optional ByVal icon_Path As String = Nothing) As Boolean
Dim provider_Args As New Dictionary(Of String, String)()
provider_Args.Add("CompilerVersion", "v3.5")
Dim provider As New Microsoft.VisualBasic.VBCodeProvider(provider_Args)
Dim c_Param As New Compiler.CompilerParameters
Dim c_Args As String = " /target:winexe /platform:x86 /optimize "
If Not icon_Path = Nothing Then
c_Args = c_Args & "/win32icon:" & icon_Path
End If
c_Param.GenerateExecutable = True
c_Param.OutputAssembly = output
c_Param.EmbeddedResources.Add(resources)
c_Param.CompilerOptions = c_Args
c_Param.IncludeDebugInformation = False
c_Param.ReferencedAssemblies.AddRange({"C:\Users\marsh\Desktop\project\Galaxy\packages\Rebex.Common.5.0.7119\lib\net35\Rebex.Common.dll", "C:\Users\marsh\Desktop\project\Galaxy\packages\Rebex.Networking.5.0.7119\lib\net35\Rebex.Networking.dll", "C:\Users\marsh\Desktop\project\Galaxy\packages\Rebex.Sftp.5.0.7119\lib\net35\Rebex.Sftp.dll", "mscorlib.dll", "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Management.dll", "System.Dll", "System.Drawing.Dll", "System.Windows.Forms.Dll", "System.Data.Dll", "System.Xml.Dll"})
c_Param.GenerateInMemory = True
Dim c_Result As Compiler.CompilerResults = provider.CompileAssemblyFromSource(c_Param, input)
If c_Result.Errors.Count = 0 Then
Return True
Else
If showError Then
For Each _Error As Compiler.CompilerError In c_Result.Errors
MessageBox.Show("ERREUR de compilation" & vbNewLine &
"FileName: " & _Error.FileName & vbNewLine &
"Ligne: " & _Error.Line & vbNewLine & "ErrorText: " &
_Error.ErrorText & vbNewLine &
"Column: " &
_Error.Column & vbNewLine &
"Type d'erreur (True = avertissement, False = Erreur ): " &
_Error.IsWarning & vbNewLine & "ErrorNumber: " &
_Error.ErrorNumber)
Next
Return False
End If
Return False
End If
End Function
Despite having correctly implemented the libraries in question in my Codedom settings, nothing happens when I try to use them: no error, my application stops at the execution. The Rebex libraries are provided by the NuGet installation: Rebex.Common.dll, Rebex.Networking.dll, Rebex.Sftp.dll. The parameters that I have written in my code to import these libraries are however in agreement with this documentation.
My executable freeze when I implement this part of the code in my Stub Source.vb:
Dim sftp As New Rebex.Net.Sftp()
sftp.Connect(hostname)
' log in
sftp.Login(username, password)
I really do not understand where this problem comes from. The fact that no error appears blocks me more: I do not know which way to go to solve the error. Could you help me?

Compressing a Folder Using Rar.exe via VB.NET?

I already extracted a folder with files from a RAR archive using the Unrar.exe I then want to edit the files within the extracted folder and then Re-rar that folder back into a password protected RAR archive. Either that, or append the existing RAR archive. Either way, the main goal is to update the files within the password protected archive. But I can't seem to figure out how to compress the folder again. My code as follows:
Imports System.IO
Public Class Form1
'establish the application directory and set it as a string to plugin later when needed
Dim MAINDIR As String = AppDomain.CurrentDomain.BaseDirectory
Private Sub UNRAR()
'if extracted folder does NOT exist then
If Not (System.IO.Directory.Exists(MAINDIR & "Credentials\")) Then
'set variables
Dim SourceFile As String = MAINDIR & "Credentials.rar"
Dim PassWord As String = "locker101"
Dim DestinationFolder As String = MAINDIR
'if extracted folder does not exist then
If Not IO.Directory.Exists(DestinationFolder) Then IO.Directory.CreateDirectory(DestinationFolder)
'unrar it and create extracted folder with the files
Dim p As New Process
p.StartInfo.FileName = MAINDIR & "UnRAR.exe"
p.StartInfo.Arguments = "-p" & PassWord & " x " & Chr(34) & SourceFile & Chr(34) & " " & Chr(34) & DestinationFolder & Chr(34)
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
p.Start()
End If
End Sub
Private Sub EDIT(ByVal ACCOUNT, ByVal USER, ByVal PASS, ByVal URL)
Do Until (System.IO.Directory.Exists(MAINDIR & "Credentials\"))
'does nothing on loop and keeps checking for the folder to exist
Loop
'confirms that the folder exists then begins to write to the file(s) inside
If (System.IO.Directory.Exists(MAINDIR & "Credentials\")) Then
Dim file As System.IO.StreamWriter
file = My.Computer.FileSystem.OpenTextFileWriter(MAINDIR & "Credentials\" & ACCOUNT & ".txt", False)
file.WriteLine(USER)
file.WriteLine(PASS)
file.WriteLine(URL)
file.Close()
MsgBox("DONE")
Else
MsgBox("FAILED")
MsgBox("END existing")
MsgBox("END LOOP")
End If
APPENDRAR()
End Sub
Private Sub APPENDRAR()
End Sub
Private Sub DELETE()
'deletes the extracted folder, leaving behind only the password protected rar archive
My.Computer.FileSystem.DeleteDirectory(MAINDIR & "Credentials", False, False)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
UNRAR()
'sets textboxes' texts as strings then sends those values to the EDIT sub
Dim btn As Button = DirectCast(sender, Button)
Dim ACCOUNT As String = TB_account.Text
Dim USER As String = TB_user.Text
Dim PASS As String = TB_pass.Text
Dim URL As String = TB_url.Text
EDIT(ACCOUNT, USER, PASS, URL)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
End Sub
End Class
It should also be mentioned that I have the Unrar.exe and Rar.exe
files in the application folder. The same files found in the Winrar
directory. I added them to my project folder to use them.
A brief explanation of what is expected: The user will fill out 3 textboxes (username, password, website url) then they will click a button. The button takes the text in each textbox as values then creates strings of those values. It then passes these string values onto the sub which UNRARS the RAR archive, then once the folder is extracted, it either overwrites any existing file in that folder or creates a new one. Then after that, it should repack this newly edited folder back into a RAR archive with the same password as before, then deletes the extracted folder and the old RAR archive (UNLESS I can just append them back into the original archive, then I would not have to make an "updated" archive.)
UPDATE:
Dim SourceFile As String = MAINDIR & "Credentials\"
Dim PassWord As String = "locker101"
Dim DestinationFolder As String = MAINDIR
'if extracted folder does not exist then
'If Not IO.Directory.Exists(DestinationFolder) Then IO.Directory.CreateDirectory(DestinationFolder)
'unrar it and create extracted folder with the files
Dim p As New Process
Dim bbs As String = "-p" & PassWord & " u " & Chr(34) & MAINDIR & "ass.rar" & Chr(34) & " " & Chr(34) & SourceFile & Chr(34)
p.StartInfo.FileName = MAINDIR & "Rar.exe"
p.StartInfo.Arguments = bbs
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
p.Start()
This seems to work but it seems to not just add the "Credentials" folder but every folder leading up to it starting from "Projects".
I finally figured it out. Just in case anyone else needed the answer, here it is.
Dim p0 As New Process
Dim createo As String
createo = "-p" & <PASSWORD> & " u -ep " & Chr(34) & <arhive.rar> & Chr(34) & " " & Chr(34) & <FILE TO REPLACE> & Chr(34)
p0.StartInfo.FileName = "Rar.exe"
p0.StartInfo.Arguments = createo
p0.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
p0.Start()
Example for the string
createo = "-p" & TB_BIGKEY.Text & " u -ep " & Chr(34) & Form1.MAINDIR & "Users\" & TB_ID.Text & ".rar" & Chr(34) & " " & Chr(34) & Form1.MAINDIR & "Users\" & TB_ID.Text & ".txt" & Chr(34)
Which is read out 'tostring' as
-ppassyword u -ep "F:\Projects\PG\PG\bin\Debug\Users\Username.rar" "F:\Projects\PG\PG\bin\Debug\Users\Username.txt"
or <PASSWORD> <UPDATE> <EXLUDE PATHS> <RAR TO UPDATE> <FILE TO UPDATE WITH>
To not use a password, just remove the -ppassyword part.

Getting BC31019 excepetion while compiling vb.net at runtime on Windows 10

we are generating a mass of documents very dynamically. Therefore we concatenate source code and build a dll at runtime. This is running since windows XP.
Now we are in tests of windows 10 and it fails compiling this dll with the error "BC31019: Unable to write to output file 'C:\Users[name]AppData\Local\Temp\xyz.dll': The specified image file did not contain a resource section"
For testing purposes we remove all generated source code and replace it by a rudimental class with only one function (throwing an exception with specified text) and no referenced assemblies.
This is also running on all machines except windows 10. Same error.
Can anybody guess why?
This is the rudimental method
Public Sub Compile()
Dim lSourceCode = "Namespace DynamicOutput" & vbCrLf &
" Public Class Template" & vbCrLf &
" Sub New()" & vbCrLf &
" End Sub" & vbCrLf &
" Public Sub Generate(ByVal spoolJob As Object, ByVal print As Object)" & vbCrLf &
" Throw New System.Exception(""Generate reached"")" & vbCrLf &
" End Sub" & vbCrLf &
"" & vbCrLf &
" End Class" & vbCrLf &
"End Namespace"
Dim lParams As CodeDom.Compiler.CompilerParameters = New CodeDom.Compiler.CompilerParameters
lParams.CompilerOptions = "/target:library /rootnamespace:CompanyName /d:TRACE=TRUE /optimize "
lParams.IncludeDebugInformation = True
lParams.GenerateExecutable = False
lParams.TreatWarningsAsErrors = False
lParams.GenerateInMemory = True
Dim lProviderOptions As New Dictionary(Of String, String) From {{"CompilerVersion", "v4.0"}}
Dim lResult As CodeDom.Compiler.CompilerResults = Nothing
Using provider As New VBCodeProvider(lProviderOptions)
lResult = provider.CompileAssemblyFromSource(lParams, lSourceCode)
End Using
' ... check for errors
Dim lInstance As Object = lResult.CompiledAssembly.CreateInstance("CompanyName.DynamicOutput.Template")
lInstance.GetType.GetMethod("Generate").Invoke(lInstance, New Object() {Me.SpoolJob, Me.Print})
End Sub

Move files with certain extensions only

I'm currently using the Directory.Move method to copy files from one location to another. What i would like to do is only move files with certain extensions (.dbf, .ini & .txt). If the original folder doesn't contain any of these files then i just want to create an empty directory
Current code I'm using is...
Dim n As Integer
If lb1.SelectedItems.Count = 0 Then Exit Sub
For n = 0 To UBound(AllDetails)
If AllDetails(n).uName & " - " & AllDetails(n).uCode & " - " & AllDetails(n).uOps = lb1.SelectedItem Then
If Not My.Computer.FileSystem.DirectoryExists(aMailbox & "\" & AllDetails(n).uFile) Then
Directory.Move(zMailbox & AllDetails(n).uFile, aMailbox & "\" & AllDetails(n).uFile)
lb3.Items.Add(AllDetails(n).uName & " - " & AllDetails(n).uCode & " - " & AllDetails(n).uOps)
Else
lb3.Items.Add(AllDetails(n).uName & " - " & AllDetails(n).uCode & " - " & AllDetails(n).uOps)
Exit Sub
End If
End If
Next
All the variables are declared and this works but moves the entire folder contents
One way to approach this is to use each extension as a search pattern for File.GetFiles, then use Directory.Move on each file returned. Something like this might help:
For Each OldFile As String In (From s In {".dbf", ".ini", ".txt"}
From f In Directory.GetFiles(zMailbox & AllDetails(n).uFile, s)
Select f)
Directory.Move(OldFile, aMailbox & "\" & AllDetails(n).uFile & "\" & Path.GetFileName(OldFile))
Next
if what you want is certain file extensions only? well Open File Dialog can filter that for you
Let's say you call this operation with a button click:
Dim fd As OpenFileDialog = New OpenFileDialog()
fd.Title = "Open File Dialog"
fd.InitialDirectory = "the initial directory you want to look at first"
'this filters the available files to be opened!
fd.Filter = "dbf files|*.dbf*|ini files|*.ini*|Text files|*.txt*"
fd.FilterIndex = 2 'set's the default files to open first as .ini
fd.RestoreDirectory = True
If fd.ShowDialog() = Windows.Forms.DialogResult.OK Then
'Copy the file to the location using the copyer subroutin
resulta.Text = fd.FileName.ToString
Copyer(fd.FileName.Tostring, "the location where you want to copy")
End If
End Sub
Now for the copying of the file, have a look at this subroutine. This subroutine checks first if the file exists. If it does, it deletes it, then copies the new file.
Public Sub Copyer(ByVal theFile As String, ByVal Lokasyon As String)
Try
Dim resultpath As String = Lokasyon
If System.IO.File.Exists(resultpath) = True Then
System.IO.File.Delete(resultpath)
'this deletes the file so you can overwrite it.
End If
My.Computer.FileSystem.CopyFile(theFile, resultpath)
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
Please let me know if this has helped you.

Email a table using VB.Net

I need to send an email with a table that has variable values in each cell. I can do this using any method (html via email, an excel/word table, etc.). The only hitch is due to the restrictions of the Emailer program and System.Net.Mail import, it has to be a string.
Here's what I have so far:
Imports DelayEmailer.DelayTrackerWs
Imports System.Configuration
Public Class DelayEmailer
Public Shared Sub Main()
Dim ws As New DelayTrackerWs.DelayUploader
Dim delays As DelayTrackerWs.Delay()
Dim emailer As New Emailer()
Dim delaystring As String
delays = ws.SearchDelaysDate(DelayTrackerWs.AreaEnum.QT, DelayTrackerWs.UnitEnum.QT, Now.AddDays(-1), Now)
delaystring = "Delays" & vbNewLine
delaystring &= "Facilty Start Time Status Category Reason Comment"
For i = 0 To delays.Length - 1
delaystring &= vbNewLine & delays(i).Facility & " "
delaystring &= FormatDateTime(delays(i).DelayStartDateTime, DateFormat.ShortDate) & " "
delaystring &= FormatDateTime(delays(i).DelayStartDateTime, DateFormat.ShortTime) & " "
'delaystring &= delays(i).DelayDuration & " "
delaystring &= delays(i).Status & " "
delaystring &= delays(i).CategoryCode & " "
delaystring &= delays(i).ReasonCode & " "
delaystring &= delays(i).Comment
Next
emailer.Send(ConfigurationManager.AppSettings("EmailList"), "delays", delaystring)
End Sub
As you can see, I currently have just a bunch of concatenated strings that line up if the values of each delays(i) are the same. The other problem is that this needs to be easily viewable via mobile devices and with the strings, it wraps and gets really unreadable. A table here should fix this.
You can send html email from .NET using MailMessage and SmtpClient classes, create an email template as string and set MailMessage's IsBodyHtml property to true:
Dim strHeader As String = "<table><tbody>"
Dim strFooter As String = "</tbody></table>"
Dim sbContent As New StringBuilder()
For i As Integer = 1 To rows
sbContent.Append("<tr>")
For j As Integer = 1 To cols
sbContent.Append(String.Format("<td>{0}</td>", YOUR_TD_VALUE_STRING))
Next j
sbContent.Append("</tr>");
Next i
Dim emailTemplate As String = strHeader & sbContent.ToString() & strFooter
...