Closing Word with VBScript leaves temporary word documents behind - vbscript

I am having trouble closing a Word document with VBScript. When the Word document is opened (by the script) a temporary ~$ file is created by Word. For example, opening test.docx also creates a temporary file called ~$test.docx. I understand this is normal. But the problem is that when I close test.docx, the main document test.docx closes properly but ~$test.docx is left behind still open. With many files to process there is soon a large number of these temporary files. They show up in task manager as a background process. What am I doing wrong in closing the files? The code I am using is:
Set objWord = CreateObject("Word.Application")
objWord.Visible = False
objWord.DisplayAlerts = 0
objWord.Documents.Open FilePath 'FilePath previously set
'Do stuff (reading properties)
objWord.Documents.Close 0 'Close opened documents without saving
objWord.Quit
Set objWord = Nothing

It could be that the objWord variable is a 'global' reference to the Word application, defined somewhere at the top of the script.
This global reference remains in place as long as the calling program is active because the operating system will not end the automated application while the caller is active.
If that is the case here, wrapping the code in a function and defining the word object in there should solve it, because then the object has local scope and does not exist outside the function.
Something like this:
Function DoWordThings(FilePath)
Dim oWord
Set oWord = CreateObject("Word.Application")
oWord.Visible = False
oWord.DisplayAlerts = 0
oWord.Documents.Open FilePath 'FilePath now used as parameter to the function
'Do stuff (reading properties and returning them to the caller of this function)
oWord.Documents.Close 0 'Close opened documents without saving
oWord.Quit
Set oWord = Nothing
End Function

Related

VB script will not traverse every file in a given folder

I am new to VBscript and am looking to write a simple script that changes a couple cells in a few thousand csv files in a given folder location. I have a good start, and it all seems to be working, except for the fact that when I run the script (from a .bat file that just calls the script) it only changes and moves 3-8 files at a time. The number of files it changes is random, its not like it always changes 5 files or something. I am not sure what is wrong in the code as to why it will not edit and move every single file and only does a couple at a time, here is what I have so far, thanks in advance for any help:
Set objFSO = Wscript.CreateObject("Scripting.FileSystemObject")
Set colFiles = ObjFSO.GetFolder("C:\Users\xxx\BadCSVs").Files
Set xl = CreateObject("Excel.Application")
For Each objFile in colFiles
If LCase(objFSO.GetExtensionName(objFile)) = "csv" Then
Set wb = xl.Workbooks.Open(objFile)
Set sht = xl.ActiveSheet
If(sht.Cells(1,11) <> "") Then
sht.Cells(1,8) = sht.Cells(1,8) & sht.Cells(1,9)
sht.Cells(1,9) = sht.Cells(1,10)
sht.Cells(1,10) = sht.Cells(1,11)
sht.Cells(1,11) = Null
wb.Save
wb.Close True
Else
'if file is opened up and has only 10 columns of data, then it makes no changes, then closes it.
wb.Close
End If
End If
Next
xl.Quit
Your EVIL global
On Error Resume Next
hides errors. Disable/Remove it and test again.
Your
wb.Close SaveChanges=True
passes the boolean result of comparing SaveChanges (undefined/empty) vs. the boolean literal True. Perhaps you copied VBA code
wb.Close SaveChanges:=True
(mark the colon) that is not legal in VBScript?
And
Set xl = CreateObject("Excel.Application")
should be paired with an
xl.Quit
If you invoke Excel in the loop, terminate it there. I would try to start/quit Excel out of the loop, but you should test that approach carefully.

Select file by index from Files Collection

I'm trying to access a file by index using vbs but Item does not work as I expected. I would rather not have to loop all files as I'm trying to avoid a treewalk. I recieve the error "Error: Invalid procedure call or argument" on the line with colFiles.Item().
Randomize
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(fFolder.Path)
Set colFiles = objFolder.Files
Set objFile = colFiles.Item(Int(colFiles.Count * Rnd))
I'm sure it's obvious, but my searching only shows example using loops.
VBScript does not support random/index access to the elements of the .Files collection. You'll have to For Each loop, pick a random file, and Exit the loop.
Update:
According to the docs, you can use a 'name' (file name, file spec(?)) as parameter to .Item(), so you could keep an array of file names/specs, pick one randomly, and access the file. Whether that would be an improvement in your case, remains to be seen.

How to close an application when vbscript crashes

I'm using VBscript to open Microsoft Excel and convert xls documents to csv.
Here is a quick example that takes an argument and converts the first page
Dim oExcel
Dim oBook
Set oExcel = CreateObject("Excel.Application")
Set oBook = oExcel.Workbooks.Open(Wscript.Arguments.Item(0))
oBook.SaveAs "out.csv", 6
oBook.Close False
oExcel.Quit
If everything works, that's great. But when the script crashes before it can close excel, the process continues to stay around and lock the file until I manually kill the process.
How can I make sure that I perform any clean up routines even when the script fails?
As the question is/was? about closing Excel reliably when the VBScript used to automate it crashes:
If you write
Set oExcel = CreateObject("Excel.Application")
you create a 'simple' variable. VBScript may decrement a ref counter when the variable goes out of scope, but it certainly won't .Quit Excel for you.
If you want a feature like atexit calls or exception handling in VBScript, you'll have to write a class that 'does what I mean' in its Class_Terminate Sub. A simple example:
Option Explicit
Class cExcelWrapper
Private m_oExcel
Public Sub Class_Initialize()
Set m_oExcel = CreateObject("Excel.Application")
End Sub
Public Sub Class_Terminate()
m_oExcel.Quit
End Sub
Public Default Property Get Obj()
Set Obj = m_oExcel
End Property
End Class
Dim oExcel : Set oExcel = New cExcelWrapper
Dim oWBook : Set oWBook = oExcel.Obj.WorkBooks.Add()
oExcel.Obj.Visible = True
oExcel.Obj.DisplayAlerts = False
oWBook.Sheets(1).Cells(1,1) = "Div by Zero"
WScript.Echo "Check TaskManager & Enter!"
WScript.StdIn.ReadLine
WScript.Echo 1 / 0
(meant to be started with "cscript 20381749.vbs")
If you run this script with an open Taskmanager, you'll see Excel popup in the processes list (and on the screen, because of .Visible). If you then hit Enter, the script will abort with an "Division by Zero" error and the Excel process will vanish from the Processes list.
If you remove the .DisplayAlerts setting, Excel will ask you whether to save your work or not - proving thereby that the .Quit from the Class_Terminate() Sub really kicks Excel into byebye mode.
The class needs further work (basic settings, common actions (save?) before .Quit, perhaps a guard against misuse (Set oExcel = Nothing or other cargo cult crap), th .Obj addition isn't nice, and it won't help you if you kill your .vbs in a debugger, but for standard scenarios you won't see Excel zombies anymore.
Add "On Error Goto ErrorHandler" at the top of your script, and at the bottom of it, add "ErrorHandler:". Underneath the ErrorHandler label add code to manage the situation depending on the Err.Number
See Err object on MSDN.
You can also use "On Error Resume Next". Here is an example.
EDIT: My bad. "Goto" does not exist in VBS. The answer below is probably a much tidier approach.

VB script: Message while Unzipping a file

I am using the below vb script to un-zip the files, so while un-zipping is going on, i am seeing a pop up messgae(Copying/extracting), is there any way to get rid of popup message?
FileToGetUnZipped = "InstallDir\UI_Files.zip"
DestPathForUnzippedFile = "InstallDir\system"
Set objFSO = CreateObject("Scripting.FileSystemObject")
If Not objFSO.FolderExists(DestPathForUnzippedFile) Then
objFSO.CreateFolder(DestPathForUnzippedFile)
End If
UnZipFile FileToGetUnZipped, DestPathForUnzippedFile
Sub UnZipFile(strArchive, DestPathForUnzippedFile)
Set objApp = CreateObject( "Shell.Application" )
Set objArchive = objApp.NameSpace(strArchive).Items()
Set objDest = objApp.NameSpace(DestPathForUnzippedFile)
objDest.CopyHere objArchive
End Sub
The CopyHere method takes a second argument which can be a combination of various options, including
(4)
Do not display a progress dialog box.
However, I have not had much success on getting many of these options to work reliably - I think it varies by Windows version as much as anything else.
As a side note, I think you may have issues with the CopyHere method being asynchronous - your script may complete before CopyHere does, which may kill the copying process.

How do I close Word (or other app) if an error occurs in a VBScript?

I wrote a VBScript app to open Word and Excel documents and search and replace blocks of text and various sections, pulling the new text from a plain text file. I purposely avoided any error checking, primarily because I couldn't figure it out at the time (and the script ran reliably anyway). Now months later on my local machine, I am inexplicably getting error messages about Normal.dot being changed and a message box asking what I want to do about it (which requires three more dialogs to finally answer). Of course this kills my ability to run the script and simply walk away, as it causes the script to fail. Currently when this happens, I have to open the Task Manager, find Winword.exe (of which the GUI isn't running) and kill it then re-run my script.
What's a reasonable way of catching the error and successfully shutting down Word (or Excel). Based on this question I'm trying this:
Set objDoc = objWord.Documents.Open(curDir1 + "\docs\template_spec.dot")
If Err.Number <> 0 Then
WScript.Echo "Error in Word Open:" & Err.Description
objWord.Quit
Else
Set objSelection = objWord.Selection
'Do replacement activities'
ReplaceText(objSelection)
objDoc.SaveAs(curDir1 + "\docs\mynewdocument.doc")
objWord.Quit
End If
Set objShell = Nothing
Set objWord = Nothing
Set objExcel = Nothing
Of course, as fate would have it, I cannot replicate the problem, so it works like normal. Does this solution seem reasonable? And a side question: How the heck do I get Word to stop complaining about Normal.dot (or get the script to handle it)? It's as if Word leaves itself open in the background after I have closed the GUI in some cases.
have you considered wrapping everything into an 'On Error Resume Next' statement so that your script ignores all the errors and continues to run as much as possible before calling the objWord.quit regardless of success or fail.
if you want more information on the correct use of 'On Error Resume Next' then go over to the msdn article on it!
Hope this helps!
Paul
I'm afraid that
WScript.Echo "..."
if it ever fires, is going to stall your script. Other than that, everything looks right. I'll play with it when I get home.
Edit: Word does hang out in the background, quite frequently. For one thing, if you use Outlook, and use Word as your Outlook editor, Word won't go away until Outlook is gone.
I'd agree with the use of "on error resume next".
If you really need to forcefully terminate Word, you can use WMI and the Win32_Process class to find and kill the process. This should be a last resort if everything else fails.
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process Where Name = 'winword.exe'")
For Each objProcess in colProcess
objProcess.Terminate()
Next
This was a modified example from:
http://www.computerperformance.co.uk/vbscript/wmi_process_stop.htm
Also, make sure all your references to the Word automation object are closed and/or set to nothing before you terminate the process.
The most reliable way to terminate all ActiveX instances, clean up garbage, and release resources is to put the code for that purpose into Sub Class_Terminate() of a dummy class, created instance of the class allows to handle script quit event.
Option Explicit
Dim objBeforeQuitHandler, objWord
' create a dummy class instance
Set objBeforeQuitHandler = New clsBeforeQuitHandler
' create word app instance
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
objWord.Documents.Add.ActiveWindow.Selection.TypeText "80040000 error was raised. About to terminate the script." & vbCrLf & "Word will be quitted without saving before script termination just you close popped up error message."
' your code here...
' raise an error
Err.Raise vbObjectError
Class clsBeforeQuitHandler
' dummy class for wrapping script quit event handler
Private Sub Class_Terminate()
Dim objDoc
On Error Resume Next ' to prevent errors in case of unexpected word app termination
If TypeName(objWord) <> "Object" Then ' word app has not been closed yet
objWord.DisplayAlerts = False
For Each objDoc In objWord.Documents
objDoc.Saved = True ' to prevent save as dialog popping up
objDoc.Close
Next
objWord.Quit
End If
End Sub
End Class

Resources