IOException issue with asynchronous PDF creation - vb.net

I have an asynchronous method that creates a PDF file from XML retrieved from a database. Everything works great, but occasionally I get an IOException because when I try to cleanup the temporary .fo file after creating the PDF, the file is still in use.
Public Sub FormatObjectToPdf(ByVal intRxNo As Integer, ByVal strSourceFileName As String)
Dim startInfo As New ProcessStartInfo
Dim strPdfFile As String = g_strRootPath & "Paperwork\" & intRxNo & "M.pdf"
' if the PDF file already exists, no need to re-create it
If Not File.Exists(strPdfFile) Then
Try
startInfo.Arguments = "-fo """ & strSourceFileName & """ -pdf """ & strPdfFile & """"
startInfo.FileName = g_strAppPath & "FO.NET\fonet.exe"
startInfo.UseShellExecute = True
startInfo.WindowStyle = ProcessWindowStyle.Hidden
Using proc As Process = Process.Start(startInfo)
proc.WaitForExit()
If proc.HasExited Then
proc.Dispose()
End If
End Using
Catch ex As Exception
Call InsertLog("ErrorLog", "FormatObjectToPdf: " & ex.Message, ex)
MessageBox.Show(ex.Message, "Create PDF", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End Try
End If
' wait 3 seconds to allow file to be released
System.Threading.Thread.Sleep(3000)
' delete the source FO file when processing is complete
If File.Exists(strSourceFileName) Then
Try
File.Delete(strSourceFileName)
Catch iEx As IOException
Call InsertLog("ErrorLog", "Could not delete file '" & strSourceFileName & "': " & iEx.Message, iEx)
Catch ex As Exception
Call InsertLog("ErrorLog", "Error deleting file '" & strSourceFileName & "': " & ex.Message, ex)
End Try
End If
End Sub
The FormatObjectToPdf method is called from another method, AsyncXmlToPdf, which is actually where the IOException is thrown. I had initially thought that the exception was in FormatObjectToPdf since that is where I am deleting the .fo file, so I had added a Sleep(3000) to see if giving it a few seconds would help.
Here is the AsyncXmlToPdf:
Public Sub AsyncXmlToPdf(ByVal state As Object)
Dim intRxNo = state(0)
Dim flgPrintResult As Boolean = state(1)
Dim strFileName As String = g_strAppPath & intRxNo & ".fo"
Dim strOutput As String
Dim strPdfFile As String = g_strRootPath & "Paperwork\" & intRxNo & "M.pdf"
Try
If File.Exists(strPdfFile) Then
File.Delete(strPdfFile)
End If
If Not File.Exists(strPdfFile) AndAlso Not File.Exists(strFileName) Then
strOutput = XmlToFormatObject(intRxNo, g_strAppPath & "FO.NET\immfo.xsl")
Using writer As StreamWriter = New StreamWriter(strFileName)
writer.Write(strOutput)
End Using
Call FormatObjectToPdf(intRxNo, strFileName)
End If
Catch ex As Exception
Call InsertLog("ErrorLog", "AsyncXmlToPdf: " & ex.Message, ex)
End Try
End Sub
The only part of either method other than the declaration of strFileName that even does anything with the .fo file is in FormatObjectToPdf and that method has a Catch block for IOException. Why is the exception being caught in AsyncXmlToPdf?? Here is the actual error message:
3/25/2015 11:15 AM: [IOException] AsyncXmlToPdf: The process cannot access the file 'C:\Users\<username>\AppData\Local\Apps\2.0\1M2D4TCB.REJ\3LH3JZY2.TQC\<clickonce app>\561964.fo' because it is being used by another process.
Everything works as expected, other than the occasional orphaned .fo file when this exception occurs. Anyone have any suggestions on how I might be able to find out where the problem is?

The only part of either method ... that even does anything with the .fo file is in FormatObjectToPdf It appears that AsyncXmlToPdf will also try to open a streamwriter on it.
If there is a chance that some other BackGroundWorker, Thread or Task could also be working on the same set of files, it would be possible for the same file to be in use in AsyncXmlToPdf and FormatObjectToPdf. MSDN warns of this in the File.Exists entry:
Be aware that another process can potentially do something with the file in between the time you call the Exists method and perform another operation on the file, such as Delete.
In your case it looks like it might be a a coin flip which method the Exception will happen in.
If an accidental double click could start the same process twice, it is possible that the same file could be in use in both methods at the same time. You could add another test to see if you can open the file for ReadWrite. Given that the file was not supposed to exist yet, you could at least be somewhat certain as to the reason.
Some sort of flag to prevent more than one set of jobs from starting might be the final solution.

Related

Executing msg.exe from Visual Basic application

I am trying to take text fields for old hostname, new hostname, username, and password and remotely change computer names. That part is working fantastic. It was all great until my manager saw it in action, since we have a policy against downloading and using freeware.
It's not freeware if I made it. Unfortunately, he sent it to my director, and know my director knows I know a little bit about Visual Basic, so he wants to loop the names from a CSV file, change the name, and send a message to the end user instructing them to save their files and reboot.
Unfortunately, net send has gone the way of XP since Vista. However, from Vista - Win8.1, there's a utility called msg.exe in C:\Windows\System32. In order to use it, the target computer has to have the registry value AllowRemoteRPC in HKLM\SYSTEM\CurrentControlSet\Control\Terminal Services set to 1.
So here's what the app does:
Reads the DWORD key AllowRemoteRPC and stores it to a variable (MyVal), changes the key to 1, attempts to send the message alerting the user they need to restart, changes the key back to MyVal, and then executes netdom renamecomputer and renames the PC. Everything works perfectly EXCEPT sending the message. I can open up a command prompt and type:
msg /server:hostname * /v /time:3600 "my message here
And it works perfectly (after manually editing the registry key to the needed value).
However, running it from VB doesn't work. Here's what I've tried:
"msg /server:" & hostname & " * /v /time:3600 ""my message here"""
"cmd.exe /D /c msg /server:" & hostname & " * /v /time:3600 ""my message here"""
Neither seems to work. I know the registry value is being changed. I put message boxes after each step in my and refreshed the regedit to actually see the value of the DWORD key, and it is changing. Everything APPEARS to be going smoothly, the message is just not getting sent.
I do have these commands running as arguments to a function I created in order to create a process so I could output the streamreader to a listbox.
Here's my code. Please keep in mind, I'm barely over 2 months into learning visual basic, so it's probably not the prettiest code out there:
Imports System
Imports System.IO
Imports System.Diagnostics
Imports System.Security.Permissions
Imports Microsoft.Win32
Public Class applicationMain
Private Sub btnExecute_Click(sender As Object, e As EventArgs) Handles btnExecute.Click
Dim oldPC As String = txtOldPC.Text
Dim newPC As String = txtNewPC.Text
Dim username As String = txtUsername.Text
Dim password As String = txtPassword.Text
If oldPC <> "" And newPC <> "" And username <> "" And password <> "" Then
Dim MyReg As Microsoft.Win32.RegistryKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, oldPC)
Dim MyRegKey As Microsoft.Win32.RegistryKey
Dim MyVal As String
lbOutput.Items.Clear()
MyRegKey = MyReg.OpenSubKey("System\CurrentControlSet\Control\Terminal Server")
MyVal = MyRegKey.GetValue("AllowRemoteRPC", RegistryValueKind.DWord)
MyRegKey.Close()
lbOutput.Items.Add("Processing registry changes...")
Try
MyRegKey = MyReg.OpenSubKey("System\CurrentControlSet\Control\Terminal Server", True)
MyRegKey.SetValue("AllowRemoteRPC", &H1, RegistryValueKind.DWord)
Catch ex As Exception
MessageBox.Show("An Error Has Occured:" & vbCrLf & vbCrLf & ex.ToString())
lbOutput.Items.Add("")
lbOutput.Items.Add("ABORTED!")
Exit Sub
End Try
lbOutput.Items.Add("Success!")
lbOutput.Items.Add("Sending message to user:")
Try
ExecuteCommand("cmd.exe", "/D /c msg /SERVER:" & oldPC & ".na.int.grp * /v /TIME:3600 ""Changes have been made by IS to your computer that require a restart. Please save your files and restart your computer to avoid service interruption.""")
Catch ex As Exception
MessageBox.Show("An Error Has Occured:" & vbCrLf & vbCrLf & ex.ToString())
lbOutput.Items.Add("")
lbOutput.Items.Add("ABORTED!")
MyRegKey = MyReg.OpenSubKey("System\CurrentControlSet\Control\Terminal Server", True)
MyRegKey.SetValue("AllowRemoteRPC", MyVal, RegistryValueKind.DWord)
Exit Sub
End Try
lbOutput.Items.Add(" Message: ""Changes have been made by IS to your computer that require a restart. Please save your files and restart your computer to avoid service interruption."" ")
lbOutput.Items.Add("Reverting registry changes...")
Try
MyRegKey = MyReg.OpenSubKey("System\CurrentControlSet\Control\Terminal Server", True)
MyRegKey.SetValue("AllowRemoteRPC", MyVal, RegistryValueKind.DWord)
Catch ex As Exception
MessageBox.Show("An Error Has Occured:" & vbCrLf & vbCrLf & ex.ToString())
lbOutput.Items.Add("")
lbOutput.Items.Add("ABORTED!")
Exit Sub
End Try
Try
ExecuteCommand("netdom", "renamecomputer " & oldPC & " /newname:" & newPC & " /userD:na\" & username & " /passwordd:" & password & " /usero:na\" & username & " /passwordo:" & password & " /Force")
Catch ex As Exception
MessageBox.Show("An Error Has Occured:" & vbCrLf & vbCrLf & ex.ToString())
lbOutput.Items.Add("")
lbOutput.Items.Add("ABORTED!")
Exit Sub
End Try
lbOutput.Items.Add("Success!")
lbOutput.Items.Add("")
lbOutput.Items.Add("Rename successful for " & oldPC & "!")
lbOutput.Items.Add("******************************************************************")
lbOutput.Items.Add("")
End If
End Sub
Private Function ExecuteCommand(ByVal cmd As String, ByVal arguments As String)
Dim cmdProcess As New Process()
Dim cmdProcessStartInfo As New ProcessStartInfo()
Dim cmdStreamReader As IO.StreamReader
Dim output As String
cmdProcessStartInfo.UseShellExecute = False
cmdProcessStartInfo.CreateNoWindow = True
cmdProcessStartInfo.RedirectStandardOutput = True
cmdProcessStartInfo.FileName = cmd
cmdProcessStartInfo.Arguments = arguments
cmdProcess.StartInfo = cmdProcessStartInfo
cmdProcess.Start()
cmdStreamReader = cmdProcess.StandardOutput
Do While cmdStreamReader.EndOfStream = False
output = cmdStreamReader.ReadLine()
lbOutput.SelectedIndex = lbOutput.Items.Count - 1
lbOutput.Items.Add(output)
Loop
cmdProcess.WaitForExit()
cmdProcess.Close()
Return vbNull
End Function
End Class
What do you know. There's actually nothing wrong with my code at all. While trying to play around with the paths variable, I decided "Fuhgeddaboudit, I'll just add the executable to the project!". Right clicked the project, Add -> Existing Item. Selected Executable as the type, and went to C:\Windows\System32 and, get this now, msg.exe wasn't there. At all. Opened Explorer and went to System32, msg.exe was there. For whatever reason, Visual Studio cannot see the program at all. Which is in and of itself weird.
So I copied msg.exe to my desktop, added it to source, the program works like a charm now.

How to log Catch exception to a text file

I have a vb.net program that runs a Try Catch. What i'd like to have happen, if there is an exception is have it log that exception to a text file. I have:
Catch ex As Exception
MsgBox(ex.Message)
My.Application.Log.WriteException(ex, TraceEventType.Error, "Exception " & "with argument " & "C:\Log.txt" & ".")
End Try
But it's not sending the exception to the logfile. What am I missing?
Adding a timestamp in the log file
Catch ex As Exception
IO.File.AppendAllText("C:\Log.txt", String.Format("{0}[{1}]{2}", Environment.NewLine, DateTime.Now.ToString("MM-dd-yyyy hh:mm:ss"), ex.ToString()))
End Try
I think the example here: http://msdn.microsoft.com/en-us/library/dsxzceby(v=vs.90).aspx might have mislead you a bit. I find it a bit odd.
I think this is probably better:
Public Sub SomeMethodWhichMightGenerateException(byval someArg as String)
Try
' Code that might generate an exception goes here.
' For example:
' Dim x As Object
' MsgBox(x.ToString)
Catch ex As Exception
My.Application.Log.WriteException(ex, _
TraceEventType.Error, _
"Exception with argument " & someArg & ".")
End Try
End Sub
fileName is just a random variable in the msdn example and nothing to to do with where the Exception is logged...
Look here http://msdn.microsoft.com/en-us/library/7fx0fexe(v=vs.90).aspx to see where My.Application.Log.WriteException writes data to.
Alternatively you could do something like this:
Catch ex As Exception
IO.File.AppendAllText("C:\Log.txt", String.Format("{0}{1}", Environment.NewLine, ex.ToString()))
End Try
As others have said, however, you're probably better off using an existing error logging framework.
Public Function logerrors(ByVal [error] As String)
Dim filename As String = "Log_" + DateTime.Now.ToString("dd-MM-yyyy") + ".txt"
Dim filepath As String = HttpContext.Current.Server.MapPath(Convert.ToString("~/ErrorLog/") & filename)
If File.Exists(filepath) Then
Using stwriter As New StreamWriter(filepath, True)
stwriter.WriteLine("-------------------START-------------" + DateTime.Now)
stwriter.WriteLine(Convert.ToString("Page :"))
stwriter.WriteLine([error])
stwriter.WriteLine("-------------------END-------------" + DateTime.Now)
End Using
Else
Dim stwriter As StreamWriter = File.CreateText(filepath)
stwriter.WriteLine("-------------------START-------------" + DateTime.Now)
stwriter.WriteLine(Convert.ToString("Page :"))
stwriter.WriteLine([error])
stwriter.WriteLine("-------------------END-------------" + DateTime.Now)
stwriter.Close()
End If
Return [error]
End Function

My VB project fails to download an exe file

I have an update dialog and when the "Direct Download" button is pressed it used to download an exe file from a dropbox location. However, now it just doesn't download the file anymore, and it's meant to launch the file after download but it will say: File Not found.
The code is:
Dim TempPath As String = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\Temp"
Try
My.Computer.FileSystem.DeleteFile(TempPath & "\animeygocardmaker.exe")
My.Computer.Network.DownloadFile("https://dl.dropboxusercontent.com/u/72383434/animeygocardmaker.exe", TempPath & "\animeygocardmaker.exe")
Catch ex As Exception
MsgBox("An error occured while trying to download the file")
End Try
System.Diagnostics.Process.Start(TempPath & "\animeygocardmaker.exe")
Dim CloseAllWindows As New CloseAll
Form1.Pause()
CloseAllWindows.CloseAll()
And like I said it always used to work. Could the problem be that the file takes too long to download?
Dim TempPath As String = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\Temp"
Try
My.Computer.Network.DownloadFile("https://dl.dropboxusercontent.com/u/72383434/animeygocardmaker.exe", TempPath & "\animeygocardmaker.exe")
System.Diagnostics.Process.Start(TempPath & "\animeygocardmaker.exe")
Dim CloseAllWindows As New CloseAll
Form1.Pause()
Catch ex As Exception
MsgBox("An error occured while trying to download the file" + ex.ToString)
End Try
CloseAllWindows.CloseAll()

DirectoryNotFoundException was unhandled

I'm trying to make a new file and write binary in it but the below error occurred
Could not find a part of the path 'C:\Users\user\Documents...\bin\Debug\data\binary\admin.bin'.
Here is my code
Dim bw As BinaryWriter
dim pathBin As String = Application.StartupPath & "\binary"
Dim fileExist As Boolean
Try
bw = New BinaryWriter(New FileStream(pathBin & "\admin.bin", FileMode.create))
fileExist = True
Catch ex As IOException
MessageBox.Show(ex.Message & vbNewLine & "Cannot create file.")
End Try
bw.Close()
Your issue is the path you are using does not exist which will make FileStream quite upset. Try something like this before you use the FileStream:
If Not Directory.Exists(pathBin) Then
Directory.CreateDirectory(pathBin)
End If
This will ensure that if the directory does not exist then it is created ahead of time.

Creating and appending text to txt file in VB.NET

Using VB.NET, I am trying to create a text file if it doesn't exist or append text to it if exists.
For some reason, though it is creating the text file I am getting an error saying process cannot access file.
And when I run the program it is writing text, but how can I make it write on a new line?
Dim strFile As String = "C:\ErrorLog_" & DateTime.Today.ToString("dd-MMM-yyyy") & ".txt"
Dim sw As StreamWriter
Dim fs As FileStream = Nothing
If (Not File.Exists(strFile)) Then
Try
fs = File.Create(strFile)
sw = File.AppendText(strFile)
sw.WriteLine("Start Error Log for today")
Catch ex As Exception
MsgBox("Error Creating Log File")
End Try
Else
sw = File.AppendText(strFile)
sw.WriteLine("Error Message in Occured at-- " & DateTime.Now)
sw.Close()
End If
Try this:
Dim strFile As String = "yourfile.txt"
Dim fileExists As Boolean = File.Exists(strFile)
Using sw As New StreamWriter(File.Open(strFile, FileMode.OpenOrCreate))
sw.WriteLine( _
IIf(fileExists, _
"Error Message in Occured at-- " & DateTime.Now, _
"Start Error Log for today"))
End Using
Don't check File.Exists() like that. In fact, the whole thing is over-complicated. This should do what you need:
Dim strFile As String = $#"C:\ErrorLog_{DateTime.Today:dd-MMM-yyyy}.txt"
File.AppendAllText(strFile, $"Error Message in Occured at-- {DateTime.Now}{Environment.NewLine}")
Got it all down to two lines of code :)
This should work for you without changing program logic (by not outputting "Start error" on the top of each file) like the other answers do :)
Remember to add exception handling code.
Dim filePath As String = String.Format("C:\ErrorLog_{0}.txt", DateTime.Today.ToString("dd-MMM-yyyy"))
Dim fileExists As Boolean = File.Exists(filePath)
Using writer As New StreamWriter(filePath, True)
If Not fileExists Then
writer.WriteLine("Start Error Log for today")
End If
writer.WriteLine("Error Message in Occured at-- " & DateTime.Now)
End Using
You didn't close the file after creating it, so when you write to it, it's in use by yourself. The Create method opens the file and returns a FileStream object. You either write to the file using the FileStream or close it before writing to it. I would suggest that you use the CreateText method instead in this case, as it returns a StreamWriter.
You also forgot to close the StreamWriter in the case where the file didn't exist, so it would most likely still be locked when you would try to write to it the next time. And you forgot to write the error message to the file if it didn't exist.
Dim strFile As String = "C:\ErrorLog_" & DateTime.Today.ToString("dd-MMM-yyyy") & ".txt"
Dim sw As StreamWriter
Try
If (Not File.Exists(strFile)) Then
sw = File.CreateText(strFile)
sw.WriteLine("Start Error Log for today")
Else
sw = File.AppendText(strFile)
End If
sw.WriteLine("Error Message in Occured at-- " & DateTime.Now)
sw.Close()
Catch ex As IOException
MsgBox("Error writing to log file.")
End Try
Note: When you catch exceptions, don't catch the base class Exception, catch only the ones that are releveant. In this case it would be the ones inheriting from IOException.
Why not just use the following simple call (with any exception handling added)?
File.AppendAllText(strFile, "Start Error Log for today")
EDITED ANSWER
This should answer the question fully!
If File.Exists(strFile)
File.AppendAllText(strFile, String.Format("Error Message in Occured at-- {0:dd-MMM-yyyy}{1}", Date.Today, Environment.NewLine))
Else
File.AppendAllText(strFile, "Start Error Log for today{0}Error Message in Occured at-- {1:dd-MMM-yyyy}{0}", Environment.NewLine, Date.Today)
End If
While I realize this is an older thread, I noticed the if block above is out of place with using:
Following is corrected:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim filePath As String =
String.Format("C:\ErrorLog_{0}.txt", DateTime.Today.ToString("dd-MMM-yyyy"))
Using writer As New StreamWriter(filePath, True)
If File.Exists(filePath) Then
writer.WriteLine("Error Message in Occured at-- " & DateTime.Now)
Else
writer.WriteLine("Start Error Log for today")
End If
End Using
End Sub
Try it this way:
Dim filePath As String =
String.Format("C:\ErrorLog_{0}.txt", DateTime.Today.ToString("dd-MMM-yyyy"))
if File.Exists(filePath) then
Using writer As New StreamWriter(filePath, True)
writer.WriteLine("Error Message in Occured at-- " & DateTime.Now)
Else
writer.WriteLine("Start Error Log for today")
End Using
end if