vb.net MS Word crash cleanup - vb.net

I have a program that takes a template .docx file and populates it with data, saving a copy afterwards. The program itself works fine and I got a nice try..catch in main sub in case something fails, so the file is closed regardless.
The problem is that if it crashes completely, i.e. is forced to close (or manually force closed if it hangs), it will keep the process running with the opened template, so next time it;s launched, you'll get the read only error when trying to open it.
So the question: Is there a way to clean up afterwards, without having to end process via task manager? Or maybe a way of opening it without locking it out? Making a temp copy maybe?

Fixed with this:
Sub KillUnusedWordProcess()
Dim oXlProcess As Process() = Process.GetProcessesByName("Winword")
For Each oXLP As Process In oXlProcess
If Len(oXLP.MainWindowTitle) = 0 Then
oXLP.Kill()
End If
Next
End Sub

Related

Problem handling errors with FileSystemWatcher

To start with it has been many years since I have done much programming, probably about 15 years, in fact VB 6 was still being taught then so I'm not up to date on anything and can get lost in what I'm reading but I am trying. I'm using Visual Studio 2019 and trying to create a VB Windows Forms App that uses the FileSystemWatcher. Unfortunately the only solutions to my problem that I could find are in C#. I tried to transfer them to my app but couldn't get them to work. Actually Visual Studio wasn't happy with what I put in and wouldn't run at all, probably due to incorrect syntax.
This part of my little app is supposed to copy a file that another program creates, while the other program is running, and place the copy in another folder. It seemed simple, detect that the file had been changed and then copy the changed file. Well I came across a few problems.
The first problem is that if I create an empty file the code works most times with no problems but occassionally 2 copies of the file are created. I could live with that but.
If I replace the empty file with a larger file, the file that it is meant to copy, then it can make 3 or 4 copies and most times produces a messagebox telling me that it can't access the file because it is in use. The main program is also minimised to display the error message, which I don't want.
Even if my app is the only program running and I replace the watched file manually with the larger file I still get the file in use error, so I am assuming that the event has been triggered twice and one of them is the process that is using the file and causing the error.
If I tell the error message to cancel then my program continues and produces only 1 copy of the file. Which is what I do want.
The second problem is that the file being copied is also accessed by another program for a short period imediately after it has been changed and this also causes an extra few copies of it to be made and sometimes a file in use error message box appears, and again the main program is minimised to display the error message. I originally had other IO.NotifyFilters in place that weren't really necassary and thought that they may have been triggering the errors so I removed them, but it made no difference.
The most common error is that the file is in use but there has also been the odd "Unhandled exception has occured in your application. could not find file.". The only reason that I can think of for this error is that the file may have been renamed to a backup file and a new version created by the main program at the exact time my program tried to access it.
From what I have read the FileSystemWatcher can trigger multiple times and I presume that this is what is causing the duplicate copies from both problems.
I need my app to make only 1 copy without the error messagebox appearing as this program needs to run in the background with no user input. Essentially this part of my app is an external program to backup a file when the file changes because the main program changes this file often but only seems to back up the file every few hours.
The following code is what I have used but nothing that I have tried has caught the error so I removed the error event handler. This code was copied from something else that was similar and in C# then modified for my purpose. I thought that the {} were used for C# not VB but it seems to work and if I take them out it won't.
My code for the FileSystemwatcher is:-
WatchFile = New System.IO.FileSystemWatcher With {
.Path = Path.GetDirectoryName(strArkMapFileNamePath),
.Filter = Path.GetFileName(strArkMapFileNamePath),
.NotifyFilter = IO.NotifyFilters.LastWrite
}
' add the handler to each event
AddHandler WatchFile.Changed, New FileSystemEventHandler(AddressOf OnLastWrite)
'Set this property to true to start watching
WatchFile.EnableRaisingEvents = True
The event handler is:-
Private Sub OnLastWrite(sender As Object, e As FileSystemEventArgs)
'Copy the Save file to a new folder and rename it.
My.Computer.FileSystem.CopyFile(
strArkMapFileNamePath,
strBackupPath & "\" & strArkMapFileName & "_" &
DateTime.Now.ToString("dd.MM.yyyy_hh.mm.ss") & ".ark",
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
Microsoft.VisualBasic.FileIO.UICancelOption.DoNothing)
End Sub
I added an error event handler after AddHandler WatchFile.Changed, New FileSystemEventHandler(AddressOf OnLastWrite) but that did nothing.
I tried to add an on error statement before the end sub and that did nothing either, I presume because the Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, caught the error first.
I got frustrated and tried to add a statement before the Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, but it didn't like that and I didn't expect it to work.
So how do I catch the errors before the FileSystemWatcher acts on the error?
I don't know what I am doing wrong so any help would be appreciated.
Also if anybody could offer code for what I need to do can it please be code for a Windows Forms App because I don't seem to have much luck in converting C# or anything else.
Edit
I have replaced the My.Computer.FileSystem.CopyFile with the File.Copy method as suggested and added a few extra bits..
Private Sub OnLastWrite(sender As Object, e As FileSystemEventArgs)
'Verify that source file exists
If File.Exists(strArkMapFileNamePath) Then
'Copy the Save file to a new folder and rename it.
Dim i As Integer
For i = 0 To 3
Try
' Overwrite the destination file if it already exists.
File.Copy(strArkMapFileNamePath, strBackupPath & "\" & strArkMapFileName & "_" & DateTime.Now.ToString("dd.MM.yyyy_hh.mm.ss") & ".ark", True)
i = 3
' Catch exception if the file was already copied.
Catch copyError As IOException
'If copy failed reset counter
i += 1
End Try
Next
End If
End Sub
Although this shouldn't be required because this is done before the FileSystemWatcher is enabled I have added an If statement to check that the files exists before attempting to copy the file.
I'm not entirely happy with the loop but I know under normal conditions that it's not going to be an endless loop.
Is there a way to just catch the specific errors that I would need to deal with or will it be fine the way it is?
I will probably still need to work out how to stop the FileSystemWatcher from sending more than 1 event or somehow make multiple events appear as 1.
The If statement was the last thing that I added and for some reason the program now seems to only make 1 copy and appears to be faster.
The other external program that accesses the file being copied must be slower to react than my program because now it displays a message that it's waiting until my program has finished copying the file.
My program is now performing as it was intended but I believe that it is just a matter of timing rather than the correct code.

Perform Macro on Close Event IF File was Saved

I want to run a macro "ExportToExcel" automatically when I close my .mpp file, BUT only if the Project was saved.
Reason: we only save this after we make changes; however, we are constantly opening the files to review. If this ran every time we closed, it would not be efficient.
I toyed with projectaftersave and could not make it work. I can make the following code work, but it runs EVERY time I close the file.
Sub Auto_Close()
Call ExportToExcel
End Sub
Expectations are to run my code when I close the .mpp ONLY if it was saved (not 'Save As') prior to closing.
Try the before_save event in the "ThisProject" project objects ( Microsoft Help Page )
Something along the lines of:
Option Explicit
Private Sub Project_BeforeClose(ByVal pj As Project)
~~ Your code for doing things in excel ~~
End Sub

PowerPoint 2013 macro keeps file locked open after close command

I have a PowerPoint VBA function that opens presentations, copies slides into the active presentation, then closes the source presentation. It worked fine in 2010, but fails in 2013 (all on Windows 7) if it tries to open the same presentation more than once. It appears to me that after the presentation.close command is issued, the window is closed, but the file remains locked open until the VBA code exits. So if the code attempts to open that file again it returns the error:
"Method 'Open' of object 'Presentations' failed"
Here's a simplified form of the function I'm running that behaves the same way. I've had a colleague test this again in PowerPoint 2010 and it runs fine. I've also had a colleague test it under his 2013 to make sure it's not something with my particular installation.
Sub testopen()
Dim ppFile As Presentation
Dim i As Integer
Const fpath = "C:\test.pptx"
For i = 1 To 2
Set ppFile = Application.Presentations.Open(fpath)
ppFile.Close
Set ppFile = Nothing
Next i
End Sub
The file test.pptx is just a blank presentation. In debug mode I can see the file opens and closes on the first loop, then on the second loop the open command fails and I can see in Windows explorer that the hidden temporary file still exists, indicating the file is still open, until I exit the VBA code. I also verified that the file is held open by adding in a function to check the file open status.
I've spent probably an hour googling this and cannot find any other descriptions of this problem. I'm sure I can implement a workaround but it's driving me crazy that I can't find any other reports of seemingly such a simple issue. Any suggestions are greatly appreciated! Thanks.
The Best way that I have achieved this is to simply create a VBS file and in the VBS file I call out the desired VBA code. It's little more hassle than to write the VBA code, but it's the solution that worked for me.
For example in the VBS file:
Dim args, objPP
Set args = WScript.Arguments
Set objPP = CreateObject("Powerpoint.Application")
objPP.Open "C:\path\to\file.ppx"
objPP.Visible = True
objPP.Run "The_Macro"
objPP.Save
objPP.Close(0)
objPP.Quit
Or better yet, have the entire code within the VBS file and have it copy the desired slides.
Hope this helps you achieve your result.
Setting the file as Read Only resolved the issue. The open command is now:
Set ppFile = Application.Presentations.Open(fpath, msoTrue)
Also, saving the file before closing it resolved the issue. For that, add:
ppFile.Save
Interestingly, I had already tried setting the Saved property to True (ppFile.Saved = msoTrue), which does NOT work. Thanks to Michael for his suggestion on the VBS script. That does work and I had never run an external VBS script so I learned something new. In this case, I'd prefer to stick with a VBA solution.

Will A Line Of Code Finish Before Executing The Next Line Of Code?

The following codes is moving a file as long as the file doesn't already exist. If it does, it won't move the file.
My question is regarding the File.Move. When will the msgbox display? Will it display once the file is completely moved or will it display right after the File.Move line is executed.
Depending on the file size, it may take awhile to move the file and thus I don't want the msgbox to display until the file is moved completely.
Is there a better way of doing this?
For Each foundFile As String In My.Computer.FileSystem.GetFiles("C:\Temp\", FileIO.SearchOption.SearchAllSubDirectories, "*.zip")
Dim foundFileInfo As New System.IO.FileInfo(foundFile)
If My.Computer.FileSystem.FileExists("C:\Transfer\" & foundFileInfo.Name) Then
Msgbox("File already exists and will not moved!")
Exit Sub
Else
File.Move(foundFile, "C:\Transfer\" & foundFileInfo.Name)
Msgbox("File has been moved!")
End If
Next
Accordingly to this source, the File.Movecall is synchronous, which means that your msgbox will be shown only after the file is moved, regardless of its size.
For completeness, if you don't want to block the UI, you can try something like this:
' This must be placed outside your sub/function
Delegate Sub MoveDelegate(iSrc As String, iDest As String)
' This line and the following go inside your sub/function
Dim f As MoveDelegate = AddressOf File.Move
' Call File.Move asynchronously
f.BeginInvoke(
foundFile,
"C:\Transfer\" & foundFile,
New AsyncCallback(Sub(r As IAsyncResult)
' this code is executed when the move is complete
MsgBox("File has been moved!")
End Sub), Nothing)
or you can explore the new async / await instructions.
File.Move is a synchronous operation, so the application will not execute the next line of code (your messagebox) until the move is complete.
As you indicated, if the file is large (and you are moving across drives) the messagebox will not show up until the file move is complete. This can create a poor user experience, as your GUI will appear to be non-responsive during this time.
I would recommend taking the time to learn how to utilize background threads or async/await calls to perform the operation in the background.
There is a good article on Asynchronous IO on MSDN: http://msdn.microsoft.com/en-us/library/kztecsys.aspx
Finally you could also use the FileSystem object's MoveFile method, which can pop up a file move UI for you, if you are just worried about keeping your UI responsive:
FileSystem.MoveFile(sourceFileName, destinationFileName, UIOption.AllDialogs)
unfortunately, the code is executed line after the other so the Msgbox will pop up as long as the file has been completely moved.
if you want to monitor the progress, visit this link for more details.
The Message box will be displayed after the file is completely moved irrespective of the file size.
Unless a method is asynchronous, a line of code will always finish executing before proceeding with the next line.
Note, if the file move is slow, and it holding up your program is a Bad Thing, then you could do the move in a background thread using for instance a BackgroundWorker.

Deleting a file causes an untrapable error

In my app i have to copy and then delete image files from memory cards, the problem comes when some of the card inadvertantly have the "Lock" switch engaged turning them to read only.
When trying to delete these files i want to log the failure to delete but not show any UI messages until a time of my choosing.
Here is some sample code i am having trouble with.
Sub Main()
Try
System.IO.File.Delete("K:\BYZTCSQ_0050.JPG")
Catch ex As Exception
'Error would be logged here
End Try
End Sub
This works fine when debuging i.e. it tries to delete the file and if not the error is caught and i can proccess it as nessecary, but when i build and run the app i get an error message telling me that the file cannot be deleted.
To test this code you will need a drive that can be physically set to read only (USB memory key, SD card) and try to delete a file on it while debuging and after a build.
Why would the same code run differently and how can i stop the excess error messages?
You can try to create a file on the memory card. For reasons only known to Microsoft (or not), creating a file on a copy-protected drive will raise the error condition in the Try block while deleting a file will not. Incidentally, I got the same odd result -- catching the delete worked fine in debug mode, but not from the .exe.
Imports System.IO
...
Try
fs = File.Create(drive & "\tmp.~tmp")
Catch ex As Exception
copyprotected = true
End Try
if not copyprotected then
file.delete(drive & "\tmp.~tmp")
file.delete(the file you wanted to in the first place)
end if
Instead of wrapping it in a try/catch block, test to see if the file exists before trying to execute the delete:
Dim strFilePath as String = "K:\BYZTCSQ_0050.JPG"
If File.Exists(strFilePath) Then
System.IO.File.Delete(strFilePath)
End If